From 46fd10a760012ced5be15ceab42637e0a2221567 Mon Sep 17 00:00:00 2001 From: Michael Albinus Date: Wed, 29 Mar 2023 10:55:18 +0200 Subject: [PATCH 01/32] * doc/misc/tramp.texi (Remote shell setup): Clarify use of ssh RemoteCommand. --- doc/misc/tramp.texi | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi index 468bdfdbcba..f9617f7a5cd 100644 --- a/doc/misc/tramp.texi +++ b/doc/misc/tramp.texi @@ -1,5 +1,5 @@ \input texinfo @c -*- mode: texinfo; coding: utf-8 -*- -@setfilename ../info/tramp +@setfilename ../../info/tramp.info @c %**start of header @include docstyle.texi @c In the Tramp GIT, the version number and the bug report address @@ -2372,10 +2372,11 @@ This uses also the settings in @code{tramp-sh-extra-args}. @vindex RemoteCommand@r{, ssh option} @strong{Note}: If you use an @option{ssh}-based method for connection, do @emph{not} set the @option{RemoteCommand} option in your -@command{ssh} configuration, for example to @command{screen}. On the -other hand, some @option{ssh}-based methods, like @option{sshx} or -@option{scpx}, silently overwrite a @option{RemoteCommand} option of -the configuration file. +@command{ssh} configuration to something like @command{screen}. If +used, @option{RemoteCommand} must open an interactive shell on the +remote host. On the other hand, some @option{ssh}-based methods, like +@option{sshx} or @option{scpx}, silently overwrite a +@option{RemoteCommand} option of the configuration file. @subsection Other remote shell setup hints From e45bd10a3d9cc4be676e43714ea6fb2068b8e614 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Wed, 29 Mar 2023 14:56:20 +0300 Subject: [PATCH 02/32] Fix indentation regression in 'C-h l' * lisp/help.el (view-lossage): Fix indentation of commands when the key sequence includes a semicolon. (Bug#62453) --- lisp/help.el | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lisp/help.el b/lisp/help.el index 3e94b5046e5..6eac037df2c 100644 --- a/lisp/help.el +++ b/lisp/help.el @@ -689,6 +689,10 @@ To record all your input, use `open-dribble-file'." (with-current-buffer standard-output (goto-char (point-min)) (let ((comment-start ";; ") + ;; Prevent 'comment-indent' from handling a single + ;; semicolon as the beginning of a comment. + (comment-start-skip ";; ") + (comment-use-syntax nil) (comment-column 24)) (while (not (eobp)) (comment-indent) From 09fece5722f6a6235936526991092fa444e0bd8c Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Wed, 29 Mar 2023 21:27:09 +0300 Subject: [PATCH 03/32] Fix duplicate defcustom in eww.el * lisp/net/eww.el (eww-default-download-directory): Renamed back from 'eww-download-directory'; all users changed. Doc fix. (Bug#62531) --- lisp/net/eww.el | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lisp/net/eww.el b/lisp/net/eww.el index 9451083f396..bc5f3e38ed5 100644 --- a/lisp/net/eww.el +++ b/lisp/net/eww.el @@ -64,8 +64,9 @@ The action to be taken can be further customized via :version "28.1" :type 'regexp) -(defcustom eww-download-directory "~/Downloads/" - "Default directory where `eww' saves downloaded files." +(defcustom eww-default-download-directory "~/Downloads/" + "Default directory where `eww' saves downloaded files. +Used by `eww--download-directory', which see." :version "29.1" :group 'eww :type 'directory) @@ -76,10 +77,10 @@ The default is specified by `eww-download-directory'; however, if that directory doesn't exist and the DOWNLOAD XDG user directory is defined, use the latter instead." (or (and (file-exists-p eww-download-directory) - eww-download-directory) + eww-default-download-directory) (when-let ((dir (xdg-user-dir "DOWNLOAD"))) (file-name-as-directory dir)) - eww-download-directory)) + eww-default-download-directory)) (defcustom eww-download-directory 'eww--download-directory "Directory where files will downloaded. From a14c3f62a67d1a5fa423cd3818ede870f2596a09 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Wed, 29 Mar 2023 21:28:02 +0300 Subject: [PATCH 04/32] ; Fix last change --- lisp/net/eww.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/net/eww.el b/lisp/net/eww.el index bc5f3e38ed5..d42429e53aa 100644 --- a/lisp/net/eww.el +++ b/lisp/net/eww.el @@ -76,7 +76,7 @@ Used by `eww--download-directory', which see." The default is specified by `eww-download-directory'; however, if that directory doesn't exist and the DOWNLOAD XDG user directory is defined, use the latter instead." - (or (and (file-exists-p eww-download-directory) + (or (and (file-exists-p eww-default-download-directory) eww-default-download-directory) (when-let ((dir (xdg-user-dir "DOWNLOAD"))) (file-name-as-directory dir)) From c98929c7e184740a7d68e63a2a619a436e00d813 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Wed, 29 Mar 2023 21:28:53 +0300 Subject: [PATCH 05/32] ; Fix last change --- lisp/net/eww.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/net/eww.el b/lisp/net/eww.el index d42429e53aa..22b078217bd 100644 --- a/lisp/net/eww.el +++ b/lisp/net/eww.el @@ -73,7 +73,7 @@ Used by `eww--download-directory', which see." (defun eww--download-directory () "Return the name of the EWW download directory. -The default is specified by `eww-download-directory'; however, +The default is specified by `eww-default-download-directory'; however, if that directory doesn't exist and the DOWNLOAD XDG user directory is defined, use the latter instead." (or (and (file-exists-p eww-default-download-directory) From ab4273056e0ab68a27fe807b16e2995bf84b72ec Mon Sep 17 00:00:00 2001 From: Andrea Corallo Date: Wed, 29 Mar 2023 18:02:30 +0200 Subject: [PATCH 06/32] Comp fix calls to redefined primtives with op-bytecode (bug#61917) * test/src/comp-tests.el (61917-1): New test. * src/comp.c (syms_of_comp): New variable. * lisp/loadup.el: Store primitive arities before dumping. * lisp/emacs-lisp/comp.el (comp--func-arity): New function. (comp-emit-set-call-subr): Make use of `comp--func-arity'. --- lisp/emacs-lisp/comp.el | 41 +++++++++++++++++++++++------------------ lisp/loadup.el | 6 ++++++ src/comp.c | 8 ++++++++ test/src/comp-tests.el | 18 +++++++++++++++++- 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/lisp/emacs-lisp/comp.el b/lisp/emacs-lisp/comp.el index 283c00103b5..e97832455b9 100644 --- a/lisp/emacs-lisp/comp.el +++ b/lisp/emacs-lisp/comp.el @@ -1763,27 +1763,32 @@ Return value is the fall-through block name." (_ (signal 'native-ice "missing previous setimm while creating a switch")))) +(defun comp--func-arity (subr-name) + "Like `func-arity' but invariant against primitive redefinitions. +SUBR-NAME is the name of function." + (or (gethash subr-name comp-subr-arities-h) + (func-arity subr-name))) + (defun comp-emit-set-call-subr (subr-name sp-delta) "Emit a call for SUBR-NAME. SP-DELTA is the stack adjustment." - (let ((subr (symbol-function subr-name)) - (nargs (1+ (- sp-delta)))) - (let* ((arity (func-arity subr)) - (minarg (car arity)) - (maxarg (cdr arity))) - (when (eq maxarg 'unevalled) - (signal 'native-ice (list "subr contains unevalled args" subr-name))) - (if (eq maxarg 'many) - ;; callref case. - (comp-emit-set-call (comp-callref subr-name nargs (comp-sp))) - ;; Normal call. - (unless (and (>= maxarg nargs) (<= minarg nargs)) - (signal 'native-ice - (list "incoherent stack adjustment" nargs maxarg minarg))) - (let* ((subr-name subr-name) - (slots (cl-loop for i from 0 below maxarg - collect (comp-slot-n (+ i (comp-sp)))))) - (comp-emit-set-call (apply #'comp-call (cons subr-name slots)))))))) + (let* ((nargs (1+ (- sp-delta))) + (arity (comp--func-arity subr-name)) + (minarg (car arity)) + (maxarg (cdr arity))) + (when (eq maxarg 'unevalled) + (signal 'native-ice (list "subr contains unevalled args" subr-name))) + (if (eq maxarg 'many) + ;; callref case. + (comp-emit-set-call (comp-callref subr-name nargs (comp-sp))) + ;; Normal call. + (unless (and (>= maxarg nargs) (<= minarg nargs)) + (signal 'native-ice + (list "incoherent stack adjustment" nargs maxarg minarg))) + (let* ((subr-name subr-name) + (slots (cl-loop for i from 0 below maxarg + collect (comp-slot-n (+ i (comp-sp)))))) + (comp-emit-set-call (apply #'comp-call (cons subr-name slots))))))) (eval-when-compile (defun comp-op-to-fun (x) diff --git a/lisp/loadup.el b/lisp/loadup.el index 46b26750cd5..1cc70348267 100644 --- a/lisp/loadup.el +++ b/lisp/loadup.el @@ -476,7 +476,13 @@ lost after dumping"))) ;; At this point, we're ready to resume undo recording for scratch. (buffer-enable-undo "*scratch*") +(defvar comp-subr-arities-h) (when (featurep 'native-compile) + ;; Save the arity for all primitives so the compiler can always + ;; retrive it even in case of redefinition. + (mapatoms (lambda (f) + (when (subr-primitive-p (symbol-function f)) + (puthash f (func-arity f) comp-subr-arities-h)))) ;; Fix the compilation unit filename to have it working when ;; installed or if the source directory got moved. This is set to be ;; a pair in the form of: diff --git a/src/comp.c b/src/comp.c index 1fce108fea4..3f72d088a66 100644 --- a/src/comp.c +++ b/src/comp.c @@ -5910,6 +5910,14 @@ For internal use. */); Vcomp_loaded_comp_units_h = CALLN (Fmake_hash_table, QCweakness, Qvalue, QCtest, Qequal); + DEFVAR_LISP ("comp-subr-arities-h", Vcomp_subr_arities_h, + doc: /* Hash table recording the arity of Lisp primitives. +This is in case they are redefined so the compiler still knows how to +compile calls to them. +subr-name -> arity +For internal use. */); + Vcomp_subr_arities_h = CALLN (Fmake_hash_table, QCtest, Qequal); + Fprovide (intern_c_string ("native-compile"), Qnil); #endif /* #ifdef HAVE_NATIVE_COMP */ diff --git a/test/src/comp-tests.el b/test/src/comp-tests.el index 926ba27e563..c5e5b346adb 100644 --- a/test/src/comp-tests.el +++ b/test/src/comp-tests.el @@ -446,7 +446,7 @@ https://lists.gnu.org/archive/html/bug-gnu-emacs/2020-03/msg00914.html." (should (equal comp-test-primitive-advice '(3 4)))) (advice-remove #'+ f)))) -(defvar comp-test-primitive-redefine-args) +(defvar comp-test-primitive-redefine-args nil) (comp-deftest primitive-redefine () "Test effectiveness of primitive redefinition." (cl-letf ((comp-test-primitive-redefine-args nil) @@ -532,6 +532,22 @@ https://lists.gnu.org/archive/html/bug-gnu-emacs/2020-03/msg00914.html." (should (subr-native-elisp-p (symbol-function 'comp-test-48029-nonascii-žžž-f)))) +(comp-deftest 61917-1 () + "Verify we can compile calls to redefined primitives with +dedicated byte-op code." + (let (x + (f (lambda (fn &rest args) + (setq comp-test-primitive-redefine-args args)))) + (advice-add #'delete-region :around f) + (unwind-protect + (setf x (native-compile + '(lambda () + (delete-region 1 2)))) + (should (subr-native-elisp-p x)) + (funcall x) + (advice-remove #'delete-region f) + (should (equal comp-test-primitive-redefine-args '(1 2)))))) + ;;;;;;;;;;;;;;;;;;;;; ;; Tromey's tests. ;; From 9b32bc134c4b4d8928df1bdc39907d9b1d6c73b5 Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Thu, 30 Mar 2023 09:09:43 +0300 Subject: [PATCH 07/32] Improve documentation of 'defcustom's :set keyword * lisp/custom.el (defcustom): * doc/lispref/customize.texi (Variable Definitions): Improve the documentation of the :set keyword in 'defcustom'. --- doc/lispref/customize.texi | 9 ++++++++- lisp/custom.el | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/lispref/customize.texi b/doc/lispref/customize.texi index 308145def55..6015c134d21 100644 --- a/doc/lispref/customize.texi +++ b/doc/lispref/customize.texi @@ -378,8 +378,15 @@ the option as a Lisp variable); preferably, though, it should not modify its value argument destructively. The default for @var{setfunction} is @code{set-default-toplevel-value}. +If defined, @var{setfunction} will also be called when evaluating a +@code{defcustom} form with @kbd{C-M-x} in Emacs Lisp mode and when the +@var{option}'s value is changed via the @code{setopt} macro +(@pxref{Setting Variables, setopt}). + If you specify this keyword, the variable's documentation string -should describe how to do the same job in hand-written Lisp code. +should describe how to do the same job in hand-written Lisp code, +either by invoking @var{setfunction} directly or by using +@code{setopt}. @item :get @var{getfunction} @kindex get@r{, @code{defcustom} keyword} diff --git a/lisp/custom.el b/lisp/custom.el index 0522bdd447b..df6db181615 100644 --- a/lisp/custom.el +++ b/lisp/custom.el @@ -280,7 +280,9 @@ The following keywords are meaningful: when using the Customize user interface. It takes two arguments, the symbol to set and the value to give it. The function should not modify its value argument destructively. The default choice - of function is `set-default-toplevel-value'. + of function is `set-default-toplevel-value'. If this keyword is + defined, modifying the value of SYMBOL via `setopt' will call the + function specified by VALUE to install the new value. :get VALUE should be a function to extract the value of symbol. The function takes one argument, a symbol, and should return the current value for that symbol. The default choice of function From 10918fc9d249fb829a363a4b73847289b8f2bce9 Mon Sep 17 00:00:00 2001 From: Shynur Date: Thu, 30 Mar 2023 01:29:17 +0800 Subject: [PATCH 08/32] Fix scrolling window when point moves up This fixes the case when both 'scroll-conservatively' and 'scroll-step' are customized to non-default values. * src/xdisp.c (try_scrolling): Fix precedence between 'scroll-step' and 'scroll-conservatively' when scrolling with 'previous-line'. (Bug#62530) Copyright-paperwork-exempt: yes --- src/xdisp.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/xdisp.c b/src/xdisp.c index 0b190529404..8e265fb5a49 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -18546,8 +18546,9 @@ try_scrolling (Lisp_Object window, bool just_this_one_p, start_display (&it, w, startp); if (arg_scroll_conservatively) - amount_to_scroll = max (dy, frame_line_height - * max (scroll_step, temp_scroll_step)); + amount_to_scroll + = min (max (dy, frame_line_height), + frame_line_height * arg_scroll_conservatively); else if (scroll_step || temp_scroll_step) amount_to_scroll = scroll_max; else From d2e82817a3f341e426c220e98048e1784d1e3076 Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Fri, 24 Mar 2023 16:45:15 -0700 Subject: [PATCH 09/32] Add two typescript-ts-mode faces (bug#62429) * lisp/progmodes/typescript-ts-mode.el: (typescript-ts-mode-jsx-tag-face) (typescript-ts-mode-jsx-attribute-face): New faces. (typescript-ts-mode--font-lock-settings): Use new faces. (tsx-ts-mode): Mention the new faces in the docstring. --- lisp/progmodes/typescript-ts-mode.el | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lisp/progmodes/typescript-ts-mode.el b/lisp/progmodes/typescript-ts-mode.el index a9ca85d5d35..23815c79906 100644 --- a/lisp/progmodes/typescript-ts-mode.el +++ b/lisp/progmodes/typescript-ts-mode.el @@ -41,6 +41,16 @@ :safe 'integerp :group 'typescript) +(defface typescript-ts-jsx-tag-face + '((t . (:inherit font-lock-function-call-face))) + "Face for HTML tags like
and

in JSX." + :group 'typescript) + +(defface typescript-ts-jsx-attribute-face + '((t . (:inherit font-lock-constant-face))) + "Face for HTML attributes like name and id in JSX." + :group 'typescript) + (defvar typescript-ts-mode--syntax-table (let ((table (make-syntax-table))) ;; Taken from the cc-langs version @@ -284,17 +294,17 @@ Argument LANGUAGE is either `typescript' or `tsx'." :feature 'jsx `((jsx_opening_element [(nested_identifier (identifier)) (identifier)] - @font-lock-function-call-face) + @typescript-ts-jsx-tag-face) (jsx_closing_element [(nested_identifier (identifier)) (identifier)] - @font-lock-function-call-face) + @typescript-ts-jsx-tag-face) (jsx_self_closing_element [(nested_identifier (identifier)) (identifier)] - @font-lock-function-call-face) + @typescript-ts-jsx-tag-face) - (jsx_attribute (property_identifier) @font-lock-constant-face)) + (jsx_attribute (property_identifier) @typescript-ts-jsx-attribute-face)) :language language :feature 'number @@ -381,7 +391,12 @@ Argument LANGUAGE is either `typescript' or `tsx'." ;;;###autoload (define-derived-mode tsx-ts-mode typescript-ts-base-mode "TypeScript[TSX]" - "Major mode for editing TypeScript." + "Major mode for editing TSX and JSX documents. + +This major mode defines two additional JSX-specific faces: +`typescript-ts-jsx-attribute-face' and +`typescript-ts-jsx-attribute-face' that are used for HTML tags +and attributes, respectively." :group 'typescript :syntax-table typescript-ts-mode--syntax-table From 4508a024e81834cfb01c6f7984182e1a6cbb91ea Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Thu, 30 Mar 2023 16:34:41 +0300 Subject: [PATCH 10/32] ; Clarify documentation of 'cursor' text property * doc/lispref/text.texi (Special Properties): Clarify that 'cursor' property is only considered when the overlay hides buffer text on display. (Bug#62540) --- doc/lispref/text.texi | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi index 0a48beab8b8..4c13185b0dd 100644 --- a/doc/lispref/text.texi +++ b/doc/lispref/text.texi @@ -3765,18 +3765,19 @@ Consecutive characters with the same @code{field} property constitute a @item cursor @kindex cursor @r{(text property)} Normally, the cursor is displayed at the beginning or the end of any -overlay and text property strings present at the current buffer -position. You can instead tell Emacs to place the cursor on any -desired character of these strings by giving that character a -non-@code{nil} @code{cursor} text property. In addition, if the value -of the @code{cursor} property is an integer, it specifies the number -of buffer's character positions, starting with the position where the -overlay or the @code{display} property begins, for which the cursor -should be displayed on that character. Specifically, if the value of -the @code{cursor} property of a character is the number @var{n}, the -cursor will be displayed on this character for any buffer position in -the range @code{[@var{ovpos}..@var{ovpos}+@var{n})}, where @var{ovpos} -is the overlay's starting position given by @code{overlay-start} +overlay and text property strings that ``hide'' (i.e., are displayed +instead of) the current buffer position. You can instead tell Emacs +to place the cursor on any desired character of these strings by +giving that character a non-@code{nil} @code{cursor} text property. +In addition, if the value of the @code{cursor} property is an integer, +it specifies the number of buffer's character positions, starting with +the position where the overlay or the @code{display} property begins, +for which the cursor should be displayed on that character. +Specifically, if the value of the @code{cursor} property of a +character is the number @var{n}, the cursor will be displayed on this +character for any buffer position in the range +@code{[@var{ovpos}..@var{ovpos}+@var{n})}, where @var{ovpos} is the +overlay's starting position given by @code{overlay-start} (@pxref{Managing Overlays}), or the position where the @code{display} text property begins in the buffer. From d23dc3dd7e31526e5748332614ab4501984ad625 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Thu, 30 Mar 2023 23:24:08 +0200 Subject: [PATCH 11/32] ; * lisp/emacs-lisp/package-vc.el (package-vc): Fix manual reference --- lisp/emacs-lisp/package-vc.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/emacs-lisp/package-vc.el b/lisp/emacs-lisp/package-vc.el index 253b35f1f1a..d57095a83d1 100644 --- a/lisp/emacs-lisp/package-vc.el +++ b/lisp/emacs-lisp/package-vc.el @@ -58,7 +58,7 @@ (defgroup package-vc nil "Manage packages from VC checkouts." :group 'package - :link '(custom-manual "(emacs) Package from Source") + :link '(custom-manual "(emacs) Fetching Package Sources") :prefix "package-vc-" :version "29.1") From 59f66ea3027fc7b6c1eb977b47de0bd8d610d3c3 Mon Sep 17 00:00:00 2001 From: Philip Kaludercic Date: Thu, 30 Mar 2023 23:25:20 +0200 Subject: [PATCH 12/32] ; * lisp/emacs-lisp/package-vc.el: Remove completed item from TODO --- lisp/emacs-lisp/package-vc.el | 3 --- 1 file changed, 3 deletions(-) diff --git a/lisp/emacs-lisp/package-vc.el b/lisp/emacs-lisp/package-vc.el index d57095a83d1..2b73e187b14 100644 --- a/lisp/emacs-lisp/package-vc.el +++ b/lisp/emacs-lisp/package-vc.el @@ -41,9 +41,6 @@ ;; - Allow maintaining patches that are ported back onto regular ;; packages and maintained between versions. -;; -;; - Add a heuristic for guessing a `:lisp-dir' when cloning directly -;; from a URL. ;;; Code: From 0622e1f29f6e4c7a361f5e78ac09bed6466c4b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Thu, 30 Mar 2023 19:47:00 +0100 Subject: [PATCH 13/32] Eglot: ensure server shutdown turns off eglot-inlay-hints-mode * lisp/progmodes/eglot.el (eglot--managed-mode-off): Turn off eglot-inlay-hints-mode. --- lisp/progmodes/eglot.el | 1 + 1 file changed, 1 insertion(+) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index b4116dc4aaf..3f5245397a0 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -1886,6 +1886,7 @@ Use `eglot-managed-p' to determine if current buffer is managed.") (defun eglot--managed-mode-off () "Turn off `eglot--managed-mode' unconditionally." (remove-overlays nil nil 'eglot--overlay t) + (eglot-inlay-hints-mode -1) (eglot--managed-mode -1)) (defun eglot-current-server () From 131ec049db03a3a90c887edf67a8de86ab47008a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Thu, 30 Mar 2023 23:52:27 +0100 Subject: [PATCH 14/32] Eglot: unbreak eglot-extend-to-xref on w32 Because of a drive-letter casing mismatch between 'buffer-file-name' and the return value of 'url-generic-parse-url', the hash-table test 'equal' in 'eglot-current-server' failed. This failed to recognize that the file xref landed us on really is managed by the language server that facilitated that jump. The function w32-long-file-name seems to convert "C:/Users/" to "c:/Users" consistently and so is a good addition to eglot--uri-to-path. * lisp/progmodes/eglot.el (eglot--uri-to-path): Use w32-long-file-name. --- lisp/progmodes/eglot.el | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 3f5245397a0..8f64f849d72 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -1620,10 +1620,9 @@ If optional MARKER, return a marker instead" (normalized (if (and (not remote-prefix) (eq system-type 'windows-nt) (cl-plusp (length retval))) - (substring retval 1) + (w32-long-file-name (substring retval 1)) retval))) (concat remote-prefix normalized)) - uri))) (defun eglot--snippet-expansion-fn () From b2fbec37f3909cb3274689b1f10bee6b187a759f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Thu, 30 Mar 2023 23:55:57 +0100 Subject: [PATCH 15/32] ; * etc/EGLOT-NEWS: Clarify scope of topmost section --- etc/EGLOT-NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/EGLOT-NEWS b/etc/EGLOT-NEWS index dc77e4fe624..d10412afd33 100644 --- a/etc/EGLOT-NEWS +++ b/etc/EGLOT-NEWS @@ -18,7 +18,7 @@ That is, to look up issue github#1234, go to https://github.com/joaotavora/eglot/issues/1234. -* Changes in Eglot 1.12 (13/03/2023) +* Changes in Eglot bundled with Emacs 29 ** LSP inlay hints are now supported. Inlay hints are small text annotations not unlike diagnostics, but From d0eb12e8d3c9d6f95b8493e05857d583c29dd0fe Mon Sep 17 00:00:00 2001 From: Shynur Date: Fri, 31 Mar 2023 19:49:39 +0800 Subject: [PATCH 16/32] Fix typo in section 14.1 of Emacs Manual * doc/emacs/display.texi (Scrolling): Fix typo. (Bug#62569) Copyright-paperwork-exempt: yes --- doc/emacs/display.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/emacs/display.texi b/doc/emacs/display.texi index 7ec843180b8..6b2eb014c82 100644 --- a/doc/emacs/display.texi +++ b/doc/emacs/display.texi @@ -154,7 +154,7 @@ the buffer will be momentarily unfontified. @vindex redisplay-skip-fontification-on-input Finally, a third alternative to these variables is @code{redisplay-skip-fontification-on-input}. If this variable is -non-@code{nil}, skip some fontifications is there's input pending. +non-@code{nil}, skip some fontifications if there's input pending. This usually does not affect the display because redisplay is completely skipped anyway if input was pending, but it can make scrolling smoother by avoiding unnecessary fontification. From 3bdbb66efb9895b8ed55270075fa7d8329f8d36b Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Fri, 31 Mar 2023 16:18:09 +0300 Subject: [PATCH 17/32] ; CONTRIBUTE: Minor stylistic changes. --- CONTRIBUTE | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTE b/CONTRIBUTE index 674b4e5b18c..82e5b0a1018 100644 --- a/CONTRIBUTE +++ b/CONTRIBUTE @@ -110,7 +110,7 @@ admin/notes/bug-triage. Any change that matters to end-users should have an entry in etc/NEWS. Try to start each NEWS entry with a sentence that summarizes the entry -and takes just one line -- this will allow to read NEWS in Outline +and takes just one line -- this will allow reading NEWS in Outline mode after hiding the body of each entry. Doc-strings should be updated together with the code. @@ -123,7 +123,7 @@ Think about whether your change requires updating the manuals. If you know it does not, mark the NEWS entry with "---". If you know that *all* the necessary documentation updates have been made as part of your changes or those by others, mark the entry with "+++". -Otherwise do not mark it. +Otherwise, do not mark it. If your change requires updating the manuals to document new functions/commands/variables/faces, then use the proper Texinfo @@ -400,7 +400,7 @@ the commit to master, by starting the commit message with "Backport:". The gitmerge function excludes these commits from the merge to the master. Some changes should not be merged to master at all, for whatever -reasons. These should be marked by including something like "Do not +reason. These should be marked by including something like "Do not merge to master" or anything that matches gitmerge-skip-regexp (see admin/gitmerge.el) in the commit message. @@ -449,8 +449,8 @@ files intended for use only with Emacs version 24.5 and later. *** Useful files in the admin/ directory -See all the files in admin/notes/* . In particular, see -admin/notes/newfile, see admin/notes/repo. +See all the files in 'admin/notes/*'. In particular, see +'admin/notes/newfile' and 'admin/notes/repo'. The file admin/MAINTAINERS records the areas of interest of frequent Emacs contributors. If you are making changes in one of the files From 00144fa287eb168c1ba8e411e43fe13b9d2732ac Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Fri, 31 Mar 2023 21:32:44 -0700 Subject: [PATCH 18/32] ; Add tests for synchronous processes in Eshell Normally, Eshell only uses synchronous processes on MS-DOS, so this is hard to test. To get around this, let the tests explicitly request synchronous processes. * lisp/eshell/esh-proc.el (eshell-supports-asynchronous-processes): New variable... (eshell-gather-process-output): ... use it, and remove some incorrect code updating Eshell's internal markers (the async code path doesn't do this, so neither should the sync path). * lisp/eshell/esh-cmd.el (eshell-execute-pipeline): Use 'eshell-supports-asynchronous-processes'. * test/lisp/eshell/esh-proc-tests.el (esh-proc-test/emacs-command): New function. (esh-proc-test/emacs-echo, esh-proc-test/emacs-upcase): New variables. (esh-proc-test/synchronous-proc/simple/interactive) (esh-proc-test/synchronous-proc/simple/command-result) (esh-proc-test/synchronous-proc/pipeline/interactive) (esh-proc-test/synchronous-proc/pipeline/command-result): New tests. --- lisp/eshell/esh-cmd.el | 2 +- lisp/eshell/esh-proc.el | 11 ++++--- test/lisp/eshell/esh-proc-tests.el | 53 ++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index f0c6a146dfd..a96ce9fab51 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -893,7 +893,7 @@ This is used on systems where async subprocesses are not supported." (set headproc nil) (set tailproc nil) (progn - ,(if (fboundp 'make-process) + ,(if eshell-supports-asynchronous-processes `(eshell-do-pipelines ,pipeline) `(let ((tail-handles (eshell-duplicate-handles eshell-current-handles))) diff --git a/lisp/eshell/esh-proc.el b/lisp/eshell/esh-proc.el index a86e7502795..00e0c8014e1 100644 --- a/lisp/eshell/esh-proc.el +++ b/lisp/eshell/esh-proc.el @@ -97,6 +97,9 @@ information, for example." ;;; Internal Variables: +(defvar eshell-supports-asynchronous-processes (fboundp 'make-process) + "Non-nil if Eshell can create asynchronous processes.") + (defvar eshell-current-subjob-p nil) (defvar eshell-process-list nil @@ -296,7 +299,7 @@ Used only on systems which do not support async subprocesses.") (coding-system-change-eol-conversion locale-coding-system 'unix)))) (cond - ((fboundp 'make-process) + (eshell-supports-asynchronous-processes (unless (or ;; FIXME: It's not currently possible to use a ;; stderr process for remote files. (file-remote-p default-directory) @@ -367,6 +370,8 @@ Used only on systems which do not support async subprocesses.") (erase-buffer) (set-buffer oldbuf) (run-hook-with-args 'eshell-exec-hook command) + ;; XXX: This doesn't support sending stdout and stderr to + ;; separate places. (setq exit-status (apply #'call-process-region (append (list eshell-last-sync-output-start (point) @@ -392,10 +397,6 @@ Used only on systems which do not support async subprocesses.") (setq lbeg lend) (set-buffer proc-buf)) (set-buffer oldbuf)) - (require 'esh-mode) - (declare-function eshell-update-markers "esh-mode" (pmark)) - (defvar eshell-last-output-end) ;Defined in esh-mode.el. - (eshell-update-markers eshell-last-output-end) ;; Simulate the effect of eshell-sentinel. (eshell-close-handles (if (numberp exit-status) exit-status -1) diff --git a/test/lisp/eshell/esh-proc-tests.el b/test/lisp/eshell/esh-proc-tests.el index 8e02fbb5497..fa20efa71e1 100644 --- a/test/lisp/eshell/esh-proc-tests.el +++ b/test/lisp/eshell/esh-proc-tests.el @@ -191,6 +191,59 @@ pipeline." (unless (eq system-type 'windows-nt) "stdout\nstderr\n")))) + +;; Synchronous processes + +;; These tests check that synchronous subprocesses (only used on +;; MS-DOS by default) work correctly. To help them run on MS-DOS as +;; well, we use the Emacs executable as our subprocess to test +;; against; that way, users don't need to have GNU coreutils (or +;; similar) installed. + +(defsubst esh-proc-test/emacs-command (command) + "Evaluate COMMAND in a new Emacs batch instance." + (mapconcat #'shell-quote-argument + `(,(expand-file-name invocation-name invocation-directory) + "-Q" "--batch" "--eval" ,(prin1-to-string command)) + " ")) + +(defvar esh-proc-test/emacs-echo + (esh-proc-test/emacs-command '(princ "hello\n")) + "A command that prints \"hello\" to stdout using Emacs.") + +(defvar esh-proc-test/emacs-upcase + (esh-proc-test/emacs-command + '(princ (upcase (concat (read-string "") "\n")))) + "A command that upcases the text from stdin using Emacs.") + +(ert-deftest esh-proc-test/synchronous-proc/simple/interactive () + "Test that synchronous processes work in an interactive shell." + (let ((eshell-supports-asynchronous-processes nil)) + (with-temp-eshell + (eshell-match-command-output esh-proc-test/emacs-echo + "\\`hello\n")))) + +(ert-deftest esh-proc-test/synchronous-proc/simple/command-result () + "Test that synchronous processes work via `eshell-command-result'." + (let ((eshell-supports-asynchronous-processes nil)) + (eshell-command-result-equal esh-proc-test/emacs-echo + "hello\n"))) + +(ert-deftest esh-proc-test/synchronous-proc/pipeline/interactive () + "Test that synchronous pipelines work in an interactive shell." + (let ((eshell-supports-asynchronous-processes nil)) + (with-temp-eshell + (eshell-match-command-output (concat esh-proc-test/emacs-echo " | " + esh-proc-test/emacs-upcase) + "\\`HELLO\n")))) + +(ert-deftest esh-proc-test/synchronous-proc/pipeline/command-result () + "Test that synchronous pipelines work via `eshell-command-result'." + (let ((eshell-supports-asynchronous-processes nil)) + (eshell-command-result-equal (concat esh-proc-test/emacs-echo " | " + esh-proc-test/emacs-upcase) + "HELLO\n"))) + ;; Killing processes From 5223762e02ac84eee984cd1f7a17865766cdad9a Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Sun, 2 Apr 2023 17:45:58 -0400 Subject: [PATCH 19/32] src/eval.c: Fix bug#62419 Yup, almost 40 years after ELisp first combined them, buffer-local and let bindings still don't work quite right :-( The "automatically buffer-local if set" semantics should follow the principle that it becomes buffer-local iff the var's current binding refers to the top-level/global/non-let binding. * src/eval.c (let_shadows_buffer_binding_p): Disregard non-global let-bindings. * test/src/eval-tests.el (eval-test--bug62419): New test. --- src/eval.c | 3 ++- test/src/eval-tests.el | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/eval.c b/src/eval.c index eb40c953f96..1a4d3ad0307 100644 --- a/src/eval.c +++ b/src/eval.c @@ -3400,7 +3400,7 @@ DEFUN ("fetch-bytecode", Ffetch_bytecode, Sfetch_bytecode, return object; } -/* Return true if SYMBOL currently has a let-binding +/* Return true if SYMBOL's default currently has a let-binding which was made in the buffer that is now current. */ bool @@ -3415,6 +3415,7 @@ let_shadows_buffer_binding_p (struct Lisp_Symbol *symbol) struct Lisp_Symbol *let_bound_symbol = XSYMBOL (specpdl_symbol (p)); eassert (let_bound_symbol->u.s.redirect != SYMBOL_VARALIAS); if (symbol == let_bound_symbol + && p->kind != SPECPDL_LET_LOCAL /* bug#62419 */ && EQ (specpdl_where (p), buf)) return 1; } diff --git a/test/src/eval-tests.el b/test/src/eval-tests.el index 1e7edca3bac..e0a27439ba2 100644 --- a/test/src/eval-tests.el +++ b/test/src/eval-tests.el @@ -247,4 +247,23 @@ expressions works for identifiers starting with period." (should (equal (string-trim (buffer-string)) expected-messages)))))))) +(defvar-local eval-test--local-var 'global) + +(ert-deftest eval-test--bug62419 () + (with-temp-buffer + (setq eval-test--local-var 'first-local) + (let ((eval-test--local-var t)) + (kill-local-variable 'eval-test--local-var) + (setq eval-test--local-var 'second-local) + (should (eq eval-test--local-var 'second-local))) + ;; FIXME: It's not completely clear if exiting the above `let' + ;; should restore the buffer-local binding to `first-local' + ;; (i.e. reset the value of the second buffer-local binding to the + ;; first's initial value) or should do nothing (on the principle that + ;; the first buffer-local binding doesn't exists any more so there's + ;; nothing to restore). I think both semantics make sense. + ;;(should (eq eval-test--local-var 'first-local)) + ) + (should (eq eval-test--local-var 'global))) + ;;; eval-tests.el ends here From 6df2941c1b0d965afc40f8c50ce08e45e060d64c Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Sun, 2 Apr 2023 17:54:02 -0400 Subject: [PATCH 20/32] lisp/simple.el (inhibit-auto-fill): New var * lisp/simple.el (inhibit-auto-fill): New var. (internal-auto-fill): Obey it. (newline): Use it instead of binding `auto-fill-function`, so as to avoid messing up the state if something wants to enable/disable `auto-fill-mode` during the course of the let binding (bug#62419). --- etc/NEWS | 2 ++ lisp/simple.el | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 67e1a02d587..d20d9f65ac9 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -319,6 +319,8 @@ hooks named after the feature name, like 'esh-mode-unload-hook'. * Lisp Changes in Emacs 30.1 +** New var 'inhibit-auto-fill' to temporarily prevent auto-fill. + ** Functions and variables to transpose sexps +++ diff --git a/lisp/simple.el b/lisp/simple.el index 1447c7e53ff..b621e1603bd 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -623,7 +623,7 @@ A non-nil INTERACTIVE argument means to run the `post-self-insert-hook'." (beforepos (point)) (last-command-event ?\n) ;; Don't auto-fill if we have a prefix argument. - (auto-fill-function (if arg nil auto-fill-function)) + (inhibit-auto-fill (or inhibit-auto-fill arg)) (arg (prefix-numeric-value arg)) (procsym (make-symbol "newline-postproc")) ;(bug#46326) (postproc @@ -8919,11 +8919,15 @@ unless optional argument SOFT is non-nil." ;; If we're not inside a comment, just try to indent. (t (indent-according-to-mode)))))) +(defvar inhibit-auto-fill nil + "Non-nil means to do as if `auto-fill-mode' was disabled.") + (defun internal-auto-fill () "The function called by `self-insert-command' to perform auto-filling." - (when (or (not comment-start) - (not comment-auto-fill-only-comments) - (nth 4 (syntax-ppss))) + (unless (or inhibit-auto-fill + (and comment-start + comment-auto-fill-only-comments + (not (nth 4 (syntax-ppss))))) (funcall auto-fill-function))) (defvar normal-auto-fill-function 'do-auto-fill From 267fca267fe858d8a8f34d15de21051e3b6fff41 Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Thu, 30 Mar 2023 19:31:30 -0700 Subject: [PATCH 21/32] Fix using background commands in 'eshell-command' This regressed due to the patch for bug#53715, which changed how Eshell pipelines return the processes in the pipeline (bug#62556). * lisp/eshell/esh-cmd.el (eshell-parse-command): When creating background commands, wrap the process(es) in a cons cell whose CAR is ':eshell-background'. This lets us use fewer heuristics... (eshell-eval-command): ... here. Additionally, keep the result and the incomplete delimiter separate. * lisp/eshell/eshell.el (eshell-command): Check ':eshell-background' and use a more-robust method for setting the output target. * test/lisp/eshell/eshell-tests.el (eshell-test/eshell-command/simple) (eshell-test/eshell-command/pipeline) (eshell-test/eshell-command/background) (eshell-test/eshell-command/background-pipeline): New tests. --- lisp/eshell/esh-cmd.el | 30 ++++++++++------------ lisp/eshell/eshell.el | 21 +++++---------- test/lisp/eshell/eshell-tests.el | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 30 deletions(-) diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index a96ce9fab51..94aa2ed8906 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -421,7 +421,8 @@ hooks should be run before and after the command." (string= (car eshell--sep-terms) ";")) (eshell-parse-pipeline cmd) `(eshell-do-subjob - (list ,(eshell-parse-pipeline cmd))))) + (cons :eshell-background + ,(eshell-parse-pipeline cmd))))) (setq eshell--sep-terms (cdr eshell--sep-terms)) (if eshell-in-pipeline-p cmd @@ -1036,7 +1037,12 @@ produced by `eshell-parse-command'." (cadr result))) (defun eshell-eval-command (command &optional input) - "Evaluate the given COMMAND iteratively." + "Evaluate the given COMMAND iteratively. +Return the process (or head and tail processes) created by +COMMAND, if any. If COMMAND is a background command, return the +process(es) in a cons cell like: + + (:eshell-background . PROCESS)" (if eshell-current-command ;; We can just stick the new command at the end of the current ;; one, and everything will happen as it should. @@ -1052,20 +1058,12 @@ produced by `eshell-parse-command'." (erase-buffer) (insert "command: \"" input "\"\n"))) (setq eshell-current-command command) - (let* ((delim (catch 'eshell-incomplete - (eshell-resume-eval))) - (val (car-safe delim))) - ;; If the return value of `eshell-resume-eval' is wrapped in a - ;; list, it indicates that the command was run asynchronously. - ;; In that case, unwrap the value before checking the delimiter - ;; value. - (if (and val - (not (eshell-processp val)) - (not (eq val t))) - (error "Unmatched delimiter: %S" val) - ;; Eshell-command expect a list like () to know if the - ;; command should be async or not. - (or (and (eshell-processp val) delim) val))))) + (let* (result + (delim (catch 'eshell-incomplete + (ignore (setq result (eshell-resume-eval)))))) + (when delim + (error "Unmatched delimiter: %S" delim)) + result))) (defun eshell-resume-command (proc status) "Resume the current command when a process ends." diff --git a/lisp/eshell/eshell.el b/lisp/eshell/eshell.el index 7d2c0335db2..b71f283bf9f 100644 --- a/lisp/eshell/eshell.el +++ b/lisp/eshell/eshell.el @@ -290,25 +290,18 @@ With prefix ARG, insert output into the current buffer at point." (eshell-add-input-to-history command))))) (unless command (error "No command specified!")) - ;; redirection into the current buffer is achieved by adding an - ;; output redirection to the end of the command, of the form - ;; 'COMMAND >>> #'. This will not interfere with - ;; other redirections, since multiple redirections merely cause the - ;; output to be copied to multiple target locations - (if arg - (setq command - (concat command - (format " >>> #" - (buffer-name (current-buffer)))))) (save-excursion - (let ((buf (set-buffer (generate-new-buffer " *eshell cmd*"))) + (let ((stdout (if arg (current-buffer) t)) + (buf (set-buffer (generate-new-buffer " *eshell cmd*"))) (eshell-non-interactive-p t)) (eshell-mode) (let* ((proc (eshell-eval-command - (list 'eshell-commands - (eshell-parse-command command)))) + `(let ((eshell-current-handles + (eshell-create-handles ,stdout 'insert)) + (eshell-current-subjob-p)) + ,(eshell-parse-command command)))) intr - (bufname (if (and proc (listp proc)) + (bufname (if (eq (car-safe proc) :eshell-background) "*Eshell Async Command Output*" (setq intr t) "*Eshell Command Output*"))) diff --git a/test/lisp/eshell/eshell-tests.el b/test/lisp/eshell/eshell-tests.el index 743cc28b9b5..390f75cfbb9 100644 --- a/test/lisp/eshell/eshell-tests.el +++ b/test/lisp/eshell/eshell-tests.el @@ -107,6 +107,50 @@ (format template "format \"%s\" eshell-in-pipeline-p") "nil"))) +(ert-deftest eshell-test/eshell-command/simple () + "Test that the `eshell-command' function writes to the current buffer." + (skip-unless (executable-find "echo")) + (ert-with-temp-directory eshell-directory-name + (let ((eshell-history-file-name nil)) + (with-temp-buffer + (eshell-command "*echo hi" t) + (should (equal (buffer-string) "hi\n")))))) + +(ert-deftest eshell-test/eshell-command/pipeline () + "Test that the `eshell-command' function writes to the current buffer. +This test uses a pipeline for the command." + (skip-unless (and (executable-find "echo") + (executable-find "cat"))) + (ert-with-temp-directory eshell-directory-name + (let ((eshell-history-file-name nil)) + (with-temp-buffer + (eshell-command "*echo hi | *cat" t) + (should (equal (buffer-string) "hi\n")))))) + +(ert-deftest eshell-test/eshell-command/background () + "Test that `eshell-command' works for background commands." + (skip-unless (executable-find "echo")) + (ert-with-temp-directory eshell-directory-name + (let ((orig-processes (process-list)) + (eshell-history-file-name nil)) + (with-temp-buffer + (eshell-command "*echo hi &" t) + (eshell-wait-for (lambda () (equal (process-list) orig-processes))) + (should (equal (buffer-string) "hi\n")))))) + +(ert-deftest eshell-test/eshell-command/background-pipeline () + "Test that `eshell-command' works for background commands. +This test uses a pipeline for the command." + (skip-unless (and (executable-find "echo") + (executable-find "cat"))) + (ert-with-temp-directory eshell-directory-name + (let ((orig-processes (copy-tree (process-list))) + (eshell-history-file-name nil)) + (with-temp-buffer + (eshell-command "*echo hi | *cat &" t) + (eshell-wait-for (lambda () (equal (process-list) orig-processes))) + (should (equal (buffer-string) "hi\n")))))) + (ert-deftest eshell-test/command-running-p () "Modeline should show no command running" (with-temp-eshell From 093a360251afdbdc6fe6cf22e72ccbc79fc22e2e Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Thu, 30 Mar 2023 19:38:30 -0700 Subject: [PATCH 22/32] Use the 'interactive' spec to set arguments for 'eshell-command' * lisp/eshell/eshell.el (eshell-read-command): New function... (eshell-command): ... use it. Additionally, require the COMMAND argument, and rename ARG to TO-CURRENT-BUFFER. --- lisp/eshell/eshell.el | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/lisp/eshell/eshell.el b/lisp/eshell/eshell.el index b71f283bf9f..15fc2ae6310 100644 --- a/lisp/eshell/eshell.el +++ b/lisp/eshell/eshell.el @@ -272,26 +272,28 @@ information on Eshell, see Info node `(eshell)Top'." (declare-function eshell-add-input-to-history "em-hist" (input)) -;;;###autoload -(defun eshell-command (&optional command arg) - "Execute the Eshell command string COMMAND. -With prefix ARG, insert output into the current buffer at point." - (interactive) - (unless arg - (setq arg current-prefix-arg)) - (let ((eshell-non-interactive-p t)) +(defun eshell-read-command (&optional prompt) + "Read an Eshell command from the minibuffer, prompting with PROMPT." + (let ((prompt (or prompt "Emacs shell command: ")) + (eshell-non-interactive-p t)) ;; Enable `eshell-mode' only in this minibuffer. (minibuffer-with-setup-hook (lambda () (eshell-mode) (eshell-command-mode +1)) - (unless command - (setq command (read-from-minibuffer "Emacs shell command: ")) - (if (eshell-using-module 'eshell-hist) - (eshell-add-input-to-history command))))) - (unless command - (error "No command specified!")) + (let ((command (read-from-minibuffer prompt))) + (when (eshell-using-module 'eshell-hist) + (eshell-add-input-to-history command)) + command)))) + +;;;###autoload +(defun eshell-command (command &optional to-current-buffer) + "Execute the Eshell command string COMMAND. +If TO-CURRENT-BUFFER is non-nil (interactively, with the prefix +argument), then insert output into the current buffer at point." + (interactive (list (eshell-read-command) + current-prefix-arg)) (save-excursion - (let ((stdout (if arg (current-buffer) t)) + (let ((stdout (if to-current-buffer (current-buffer) t)) (buf (set-buffer (generate-new-buffer " *eshell cmd*"))) (eshell-non-interactive-p t)) (eshell-mode) @@ -319,7 +321,7 @@ With prefix ARG, insert output into the current buffer at point." (while (and (bolp) (not (bobp))) (delete-char -1))) (cl-assert (and buf (buffer-live-p buf))) - (unless arg + (unless to-current-buffer (let ((len (if (not intr) 2 (count-lines (point-min) (point-max))))) (cond From a8c1559a663d9bc21776e33303251e244f86f0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Wed, 29 Mar 2023 22:48:54 +0100 Subject: [PATCH 23/32] Eglot: remove hacky advice of jsonrpc-request The vast majority of Eglot sync requests to the server need to inform the server of any pending changes to the buffer beforehand, so that the server has up-to-date information to do its job. But doing so at the expense of ugly advice of jsonrpc-request is ill advised. Introduce eglot--request helper instead and use that. Use this opportunity to conduct a review of Eglot sync requests and come to the conclusion that all need to send any changes beforehand. Nevertheless, an IMMEDIATE kwarg to eglot--request was added to bypassing this. * lisp/progmodes/eglot.el (eglot--request): New helper. (eglot-shutdown) (eglot-execute-command) (eglot-workspace-configuration) (eglot--signal-textDocument/willSave) (eglot--workspace-symbols) (eglot--lsp-xrefs-for-method) (xref-backend-apropos) (eglot-format) (eglot-completion-at-point) (eglot-imenu) (eglot-rename) (eglot-code-actions): Use eglot--request. --- lisp/progmodes/eglot.el | 77 ++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 3072095aeb2..e52a40b7f22 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -916,7 +916,7 @@ SERVER." (unwind-protect (progn (setf (eglot--shutdown-requested server) t) - (jsonrpc-request server :shutdown nil :timeout (or timeout 1.5)) + (eglot--request server :shutdown nil :timeout (or timeout 1.5)) (jsonrpc-notify server :exit nil)) ;; Now ask jsonrpc.el to shut down the server. (jsonrpc-shutdown server (not preserve-buffers)) @@ -1469,6 +1469,18 @@ CONNECT-ARGS are passed as additional arguments to (line-beginning-position n)))) "Return position of first character in current line.") +(cl-defun eglot--request (server method params &key + immediate + timeout cancel-on-input + cancel-on-input-retval) + "Like `jsonrpc-request', but for Eglot LSP requests. +Unless IMMEDIATE, send pending changes before making request." + (unless immediate (eglot--signal-textDocument/didChange)) + (jsonrpc-request server method params + :timeout timeout + :cancel-on-input cancel-on-input + :cancel-on-input-retval cancel-on-input-retval)) + ;;; Encoding fever ;;; @@ -2148,8 +2160,8 @@ still unanswered LSP requests to the server\n"))) (server command arguments) "Execute COMMAND on SERVER with `:workspace/executeCommand'. COMMAND is a symbol naming the command." - (jsonrpc-request server :workspace/executeCommand - `(:command ,(format "%s" command) :arguments ,arguments))) + (eglot--request server :workspace/executeCommand + `(:command ,(format "%s" command) :arguments ,arguments))) (cl-defmethod eglot-handle-notification (_server (_method (eql window/showMessage)) &key type message) @@ -2453,16 +2465,6 @@ Records BEG, END and PRE-CHANGE-LENGTH locally." (run-hooks 'eglot--document-changed-hook) (setq eglot--change-idle-timer nil)))))))) -;; HACK! Launching a deferred sync request with outstanding changes is a -;; bad idea, since that might lead to the request never having a -;; chance to run, because `jsonrpc-connection-ready-p'. -(advice-add #'jsonrpc-request :before - (cl-function (lambda (_proc _method _params &key - deferred &allow-other-keys) - (when (and eglot--managed-mode deferred) - (eglot--signal-textDocument/didChange)))) - '((name . eglot--signal-textDocument/didChange))) - (defvar-local eglot-workspace-configuration () "Configure LSP servers specifically for a given project. @@ -2615,8 +2617,8 @@ When called interactively, use the currently active server" (when (eglot--server-capable :textDocumentSync :willSaveWaitUntil) (ignore-errors (eglot--apply-text-edits - (jsonrpc-request server :textDocument/willSaveWaitUntil params - :timeout 0.5)))))) + (eglot--request server :textDocument/willSaveWaitUntil params + :timeout 0.5)))))) (defun eglot--signal-textDocument/didSave () "Maybe send textDocument/didSave to server." @@ -2728,8 +2730,8 @@ If BUFFER, switch to it before." (propertize (alist-get kind eglot--symbol-kind-names "Unknown") 'face 'shadow)) 'eglot--lsp-workspaceSymbol wss))) - (jsonrpc-request (eglot--current-server-or-lose) :workspace/symbol - `(:query ,pat))))) + (eglot--request (eglot--current-server-or-lose) :workspace/symbol + `(:query ,pat))))) (cl-defmethod xref-backend-identifier-completion-table ((_backend (eql eglot))) "Yet another tricky connection between LSP and Elisp completion semantics." @@ -2785,7 +2787,7 @@ If BUFFER, switch to it before." (cadr (split-string (symbol-name method) "/")))))) (let ((response - (jsonrpc-request + (eglot--request (eglot--current-server-or-lose) method (append (eglot--TextDocumentPositionParams) extra-params)))) (eglot--collecting-xrefs (collect) @@ -2848,9 +2850,9 @@ If BUFFER, switch to it before." (eglot--lambda ((SymbolInformation) name location) (eglot--dbind ((Location) uri range) location (collect (eglot--xref-make-match name uri range)))) - (jsonrpc-request (eglot--current-server-or-lose) - :workspace/symbol - `(:query ,pattern)))))) + (eglot--request (eglot--current-server-or-lose) + :workspace/symbol + `(:query ,pattern)))))) (defun eglot-format-buffer () "Format contents of current buffer." @@ -2882,7 +2884,7 @@ for which LSP on-type-formatting should be requested." '(:textDocument/formatting :documentFormattingProvider nil))))) (eglot--server-capable-or-lose cap) (eglot--apply-text-edits - (jsonrpc-request + (eglot--request (eglot--current-server-or-lose) method (cl-list* @@ -2891,8 +2893,7 @@ for which LSP on-type-formatting should be requested." :insertSpaces (if indent-tabs-mode :json-false t) :insertFinalNewline (if require-final-newline t :json-false) :trimFinalNewlines (if delete-trailing-lines t :json-false)) - args) - :deferred method)))) + args))))) (defun eglot-completion-at-point () "Eglot's `completion-at-point' function." @@ -2914,10 +2915,9 @@ for which LSP on-type-formatting should be requested." (lambda () (if (listp cached-proxies) cached-proxies (setq resp - (jsonrpc-request server + (eglot--request server :textDocument/completion (eglot--CompletionParams) - :deferred :textDocument/completion :cancel-on-input t)) (setq items (append (if (vectorp resp) resp (plist-get resp :items)) @@ -2954,8 +2954,8 @@ for which LSP on-type-formatting should be requested." (if (and (eglot--server-capable :completionProvider :resolveProvider) (plist-get lsp-comp :data)) - (jsonrpc-request server :completionItem/resolve - lsp-comp :cancel-on-input t) + (eglot--request server :completionItem/resolve + lsp-comp :cancel-on-input t) lsp-comp))))) (bounds (bounds-of-thing-at-point 'symbol))) (list @@ -3255,11 +3255,11 @@ Returns a list as described in docstring of `imenu--index-alist'." (seq-group-by (lambda (obj) (plist-get obj :kind)) (mapcan #'unfurl - (jsonrpc-request (eglot--current-server-or-lose) - :textDocument/documentSymbol - `(:textDocument - ,(eglot--TextDocumentIdentifier)) - :cancel-on-input non-essential)))))) + (eglot--request (eglot--current-server-or-lose) + :textDocument/documentSymbol + `(:textDocument + ,(eglot--TextDocumentIdentifier)) + :cancel-on-input non-essential)))))) (cl-defun eglot--apply-text-edits (edits &optional version) "Apply EDITS for current buffer if at VERSION, or if it's nil." @@ -3330,9 +3330,9 @@ Returns a list as described in docstring of `imenu--index-alist'." (symbol-name (symbol-at-point))))) (eglot--server-capable-or-lose :renameProvider) (eglot--apply-workspace-edit - (jsonrpc-request (eglot--current-server-or-lose) - :textDocument/rename `(,@(eglot--TextDocumentPositionParams) - :newName ,newname)) + (eglot--request (eglot--current-server-or-lose) + :textDocument/rename `(,@(eglot--TextDocumentPositionParams) + :newName ,newname)) current-prefix-arg)) (defun eglot--region-bounds () @@ -3358,7 +3358,7 @@ at point. With prefix argument, prompt for ACTION-KIND." (eglot--server-capable-or-lose :codeActionProvider) (let* ((server (eglot--current-server-or-lose)) (actions - (jsonrpc-request + (eglot--request server :textDocument/codeAction (list :textDocument (eglot--TextDocumentIdentifier) @@ -3370,8 +3370,7 @@ at point. With prefix argument, prompt for ACTION-KIND." when (cdr (assoc 'eglot-lsp-diag (eglot--diag-data diag))) collect it)] - ,@(when action-kind `(:only [,action-kind])))) - :deferred t)) + ,@(when action-kind `(:only [,action-kind])))))) ;; Redo filtering, in case the `:only' didn't go through. (actions (cl-loop for a across actions when (or (not action-kind) From 66c48f9e46abab869333eaf1de574711bcaf601e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Fri, 24 Mar 2023 22:16:49 +0000 Subject: [PATCH 24/32] Eglot: define eglot--ensure-list with defalias This avoids annoying obsoletion warnings when compiling the whole buffer. * lisp/progmodes/eglot.el (eglot--ensure-list): define with defalias. --- lisp/progmodes/eglot.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index e52a40b7f22..3458fbc7cb2 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -1779,9 +1779,9 @@ and just return it. PROMPT shouldn't end with a question mark." (defun eglot--plist-keys (plist) "Get keys of a plist." (cl-loop for (k _v) on plist by #'cddr collect k)) -(defun eglot--ensure-list (x) (if (listp x) x (list x))) -(when (fboundp 'ensure-list) ; Emacs 28 or later - (define-obsolete-function-alias 'eglot--ensure-list #'ensure-list "29.1")) +(defalias 'eglot--ensure-list + (if (fboundp 'ensure-list) #'ensure-list + (lambda (x) (if (listp x) x (list x))))) ;;; Minor modes From d69d0b1a296c17508663afc9d0301b8ccaa7115e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Fri, 24 Mar 2023 22:17:44 +0000 Subject: [PATCH 25/32] Eglot: declare support for markdown also for signatures * lisp/progmodes/eglot.el (eglot--accepted-formats): new helper. (eglot-client-capabilities): use it. --- lisp/progmodes/eglot.el | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 3458fbc7cb2..8e47c397ca8 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -452,6 +452,10 @@ This can be useful when using docker to run a language server.") (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"])) + ;;; Message verification helpers ;;; @@ -782,15 +786,12 @@ treated as in `eglot--dbind'." :tagSupport (:valueSet [1])) :contextSupport t) :hover (list :dynamicRegistration :json-false - :contentFormat - (if (and (not eglot-prefer-plaintext) - (fboundp 'gfm-view-mode)) - ["markdown" "plaintext"] - ["plaintext"])) + :contentFormat (eglot--accepted-formats)) :signatureHelp (list :dynamicRegistration :json-false :signatureInformation `(:parameterInformation (:labelOffsetSupport t) + :documentationFormat ,(eglot--accepted-formats) :activeParameterSupport t)) :references `(:dynamicRegistration :json-false) :definition (list :dynamicRegistration :json-false From d00e05daa96700860dbb9dc6527105e464ffb960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Sat, 25 Mar 2023 19:53:14 +0000 Subject: [PATCH 26/32] Eglot: take advantage of new Eldoc options for signature doc Only echo the "active signature", send all the other signatures for the *eldoc* buffer. * lisp/progmodes/eglot.el (eglot--sig-info): Rework protocol. (eglot-signature-eldoc-function): Rework. --- lisp/progmodes/eglot.el | 116 +++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 8e47c397ca8..04fc7235919 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -3096,62 +3096,57 @@ for which LSP on-type-formatting should be requested." (mapconcat #'eglot--format-markup (if (vectorp contents) contents (list contents)) "\n")) -(defun eglot--sig-info (sigs active-sig sig-help-active-param) - (cl-loop - for (sig . moresigs) on (append sigs nil) for i from 0 - concat - (eglot--dbind ((SignatureInformation) label documentation parameters activeParameter) sig - (with-temp-buffer - (save-excursion (insert label)) - (let ((active-param (or activeParameter sig-help-active-param)) - params-start params-end) - ;; Ad-hoc attempt to parse label as () - (when (looking-at "\\([^(]*\\)(\\([^)]+\\))") - (setq params-start (match-beginning 2) params-end (match-end 2)) - (add-face-text-property (match-beginning 1) (match-end 1) - 'font-lock-function-name-face)) - (when (eql i active-sig) - ;; Decide whether to add one-line-summary to signature line - (when (and (stringp documentation) - (string-match "[[:space:]]*\\([^.\r\n]+[.]?\\)" - documentation)) - (setq documentation (match-string 1 documentation)) - (unless (string-prefix-p (string-trim documentation) label) - (goto-char (point-max)) - (insert ": " (eglot--format-markup documentation)))) - ;; Decide what to do with the active parameter... - (when (and (eql i active-sig) active-param - (< -1 active-param (length parameters))) - (eglot--dbind ((ParameterInformation) label documentation) - (aref parameters active-param) - ;; ...perhaps highlight it in the formals list - (when params-start - (goto-char params-start) - (pcase-let - ((`(,beg ,end) +(defun eglot--sig-info (sig &optional _activep sig-help-active-param) + (eglot--dbind ((SignatureInformation) label documentation parameters activeParameter) + sig + (with-temp-buffer + (save-excursion (insert label)) + (let ((active-param (or activeParameter sig-help-active-param)) + params-start params-end) + ;; Ad-hoc attempt to parse label as () + (when (looking-at "\\([^(]*\\)(\\([^)]+\\))") + (setq params-start (match-beginning 2) params-end (match-end 2)) + (add-face-text-property (match-beginning 1) (match-end 1) + 'font-lock-function-name-face)) + ;; Decide whether to add one-line-summary to signature line + (when (and (stringp documentation) + (string-match "[[:space:]]*\\([^.\r\n]+[.]?\\)" + documentation)) + (setq documentation (match-string 1 documentation)) + (unless (string-prefix-p (string-trim documentation) label) + (goto-char (point-max)) + (insert ": " (eglot--format-markup documentation)))) + ;; Decide what to do with the active parameter... + (when (and active-param (< -1 active-param (length parameters))) + (eglot--dbind ((ParameterInformation) label documentation) + (aref parameters active-param) + ;; ...perhaps highlight it in the formals list + (when params-start + (goto-char params-start) + (pcase-let + ((`(,beg ,end) + (if (stringp label) + (let ((case-fold-search nil)) + (and (re-search-forward + (concat "\\<" (regexp-quote label) "\\>") + params-end t) + (list (match-beginning 0) (match-end 0)))) + (mapcar #'1+ (append label nil))))) + (if (and beg end) + (add-face-text-property + beg end + 'eldoc-highlight-function-argument)))) + ;; ...and/or maybe add its doc on a line by its own. + (when documentation + (goto-char (point-max)) + (insert "\n" + (propertize (if (stringp label) - (let ((case-fold-search nil)) - (and (re-search-forward - (concat "\\<" (regexp-quote label) "\\>") - params-end t) - (list (match-beginning 0) (match-end 0)))) - (mapcar #'1+ (append label nil))))) - (if (and beg end) - (add-face-text-property - beg end - 'eldoc-highlight-function-argument)))) - ;; ...and/or maybe add its doc on a line by its own. - (when documentation - (goto-char (point-max)) - (insert "\n" - (propertize - (if (stringp label) - label - (apply #'buffer-substring (mapcar #'1+ label))) - 'face 'eldoc-highlight-function-argument) - ": " (eglot--format-markup documentation)))))) - (buffer-string)))) - when moresigs concat "\n")) + label + (apply #'buffer-substring (mapcar #'1+ label))) + 'face 'eldoc-highlight-function-argument) + ": " (eglot--format-markup documentation)))))) + (buffer-string)))) (defun eglot-signature-eldoc-function (cb) "A member of `eldoc-documentation-functions', for signatures." @@ -3164,11 +3159,12 @@ for which LSP on-type-formatting should be requested." (eglot--lambda ((SignatureHelp) signatures activeSignature activeParameter) (eglot--when-buffer-window buf - (funcall cb - (unless (seq-empty-p signatures) - (eglot--sig-info signatures - activeSignature - activeParameter))))) + (let ((active-sig (and (cl-plusp (length signatures)) + (aref signatures (or activeSignature 0))))) + (if (not active-sig) (funcall cb nil) + (funcall cb + (mapconcat #'eglot--sig-info signatures "\n") + :echo (eglot--sig-info active-sig t activeParameter)))))) :deferred :textDocument/signatureHelp)) t)) From ad1efe5e675216e1f1f342fc9d48018fac718b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Wed, 29 Mar 2023 19:30:04 +0100 Subject: [PATCH 27/32] Eglot: improve caching in eglot-completion-at-point When answering the :textDocument/completion request, LSP servers provide a :isIncomplete flag in the response, which allows Eglot to know if "further typing should result in recomputing [the completions] list. If :isIncomplete is false (i.e. the full set was returned), Eglot caches the response in a global variable eglot--capf-cache that persists for the duration of the "completion session", taken to be the interval between two calls to completion-in-region-mode. If the cache has been set, and Eglot detects that "further typing" has happened, it is safe to use the cache instead of making a request to the server. Thus eglot--capf-cache-flush, added to completion-in-region-mode-hook, is used to flush this cache. Since the popular Company completion package doesn't use completion-in-region-mode, eglot--capf-cache-flush is also added to its company-after-completion-hook. * lisp/progmodes/eglot.el (eglot--managed-mode): Set 'completion-in-region-mode-hook and company-after-completion-hook. (eglot--capf-cache): New variable. (eglot--capf-cache-flush): New function. (eglot-completion-at-point): Rework. * etc/EGLOT-NEWS: Update. --- etc/EGLOT-NEWS | 22 ++++++++ lisp/progmodes/eglot.el | 121 ++++++++++++++++++++++++---------------- 2 files changed, 95 insertions(+), 48 deletions(-) diff --git a/etc/EGLOT-NEWS b/etc/EGLOT-NEWS index dd04e677285..09772a1e71a 100644 --- a/etc/EGLOT-NEWS +++ b/etc/EGLOT-NEWS @@ -17,6 +17,28 @@ This refers to https://github.com/joaotavora/eglot/issues/. That is, to look up issue github#1234, go to https://github.com/joaotavora/eglot/issues/1234. + +* Changes in upcoming Eglot 1.14 + +** Faster, more responsive completion + +Eglot takes advantage of LSP's "isIncomplete" flag in responses to +completion requests to drive new completion-caching mechanism for the +duration of each completion session. Once a full set of completions +is obtained for a given position, the server needn't be contacted in +many scenarios, resulting in significantly less communication +overhead. This works with the popular Company package and stock +completion-at-point interfaces. + +A variable 'eglot-cache-session-completions', t by default, controls +this. The mechanism was tested with ccls, jdtls, pylsp, golsp and +clangd. Notably, the C/C++ language server Clangd version 15 has a +bug in its "isIcomplete" flag (it is fixed in later versions). If you +run into problems, disable this mechanism like so: + +(add-hook 'c-common-mode-hook + (lambda () (setq-local eglot-cache-session-completions nil))) + * Changes in Eglot 1.13 (15/03/2023) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 04fc7235919..7e329d2e26a 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -1863,6 +1863,8 @@ Use `eglot-managed-p' to determine if current buffer is managed.") (unless (eglot--stay-out-of-p 'xref) (add-hook 'xref-backend-functions 'eglot-xref-backend nil t)) (add-hook 'completion-at-point-functions #'eglot-completion-at-point nil t) + (add-hook 'completion-in-region-mode-hook #'eglot--capf-session-flush nil t) + (add-hook 'company-after-completion-hook #'eglot--capf-session-flush nil t) (add-hook 'change-major-mode-hook #'eglot--managed-mode-off nil t) (add-hook 'post-self-insert-hook 'eglot--post-self-insert-hook nil t) (add-hook 'pre-command-hook 'eglot--pre-command-hook nil t) @@ -1894,6 +1896,8 @@ Use `eglot-managed-p' to determine if current buffer is managed.") (remove-hook 'after-save-hook 'eglot--signal-textDocument/didSave t) (remove-hook 'xref-backend-functions 'eglot-xref-backend t) (remove-hook 'completion-at-point-functions #'eglot-completion-at-point t) + (remove-hook 'completion-in-region-mode-hook #'eglot--capf-session-flush t) + (remove-hook 'company-after-completion-hook #'eglot--capf-session-flush t) (remove-hook 'change-major-mode-hook #'eglot--managed-mode-off t) (remove-hook 'post-self-insert-hook 'eglot--post-self-insert-hook t) (remove-hook 'pre-command-hook 'eglot--pre-command-hook t) @@ -2896,6 +2900,13 @@ for which LSP on-type-formatting should be requested." :trimFinalNewlines (if delete-trailing-lines t :json-false)) args))))) +(defvar eglot-cache-session-completions t + "If non-nil Eglot caches data during completion sessions.") + +(defvar eglot--capf-session :none "A cache used by `eglot-completion-at-point'.") + +(defun eglot--capf-session-flush (&optional _) (setq eglot--capf-session :none)) + (defun eglot-completion-at-point () "Eglot's `completion-at-point' function." ;; Commit logs for this function help understand what's going on. @@ -2911,40 +2922,50 @@ for which LSP on-type-formatting should be requested." :sortText))))) (metadata `(metadata (category . eglot) (display-sort-function . ,sort-completions))) - resp items (cached-proxies :none) + (local-cache :none) + (bounds (bounds-of-thing-at-point 'symbol)) + (orig-pos (point)) + (resolved (make-hash-table)) (proxies (lambda () - (if (listp cached-proxies) cached-proxies - (setq resp - (eglot--request server - :textDocument/completion - (eglot--CompletionParams) - :cancel-on-input t)) - (setq items (append - (if (vectorp resp) resp (plist-get resp :items)) - nil)) - (setq cached-proxies - (mapcar - (jsonrpc-lambda - (&rest item &key label insertText insertTextFormat - textEdit &allow-other-keys) - (let ((proxy - ;; Snippet or textEdit, it's safe to - ;; display/insert the label since - ;; it'll be adjusted. If no usable - ;; insertText at all, label is best, - ;; too. - (cond ((or (eql insertTextFormat 2) - textEdit - (null insertText) - (string-empty-p insertText)) - (string-trim-left label)) - (t insertText)))) - (unless (zerop (length proxy)) - (put-text-property 0 1 'eglot--lsp-item item proxy)) - proxy)) - items))))) - (resolved (make-hash-table)) + (if (listp local-cache) local-cache + (let* ((resp (eglot--request server + :textDocument/completion + (eglot--CompletionParams) + :cancel-on-input t)) + (items (append + (if (vectorp resp) resp (plist-get resp :items)) + nil)) + (cachep (and (listp resp) items + eglot-cache-session-completions + (eq (plist-get resp :isIncomplete) :json-false))) + (bounds (or bounds + (cons (point) (point)))) + (proxies + (mapcar + (jsonrpc-lambda + (&rest item &key label insertText insertTextFormat + textEdit &allow-other-keys) + (let ((proxy + ;; Snippet or textEdit, it's safe to + ;; display/insert the label since + ;; it'll be adjusted. If no usable + ;; insertText at all, label is best, + ;; too. + (cond ((or (eql insertTextFormat 2) + textEdit + (null insertText) + (string-empty-p insertText)) + (string-trim-left label)) + (t insertText)))) + (unless (zerop (length proxy)) + (put-text-property 0 1 'eglot--lsp-item item proxy)) + proxy)) + items))) + ;; (trace-values "Requested" (length proxies) cachep bounds) + (setq eglot--capf-session + (if cachep (list bounds proxies resolved orig-pos) :none)) + (setq local-cache proxies))))) (resolve-maybe ;; Maybe completion/resolve JSON object `lsp-comp' into ;; another JSON object, if at all possible. Otherwise, @@ -2957,11 +2978,19 @@ for which LSP on-type-formatting should be requested." (plist-get lsp-comp :data)) (eglot--request server :completionItem/resolve lsp-comp :cancel-on-input t) - lsp-comp))))) - (bounds (bounds-of-thing-at-point 'symbol))) + lsp-comp)))))) + (unless bounds (setq bounds (cons (point) (point)))) + (when (and (consp eglot--capf-session) + (= (car bounds) (car (nth 0 eglot--capf-session))) + (>= (cdr bounds) (cdr (nth 0 eglot--capf-session)))) + (setq local-cache (nth 1 eglot--capf-session) + resolved (nth 2 eglot--capf-session) + orig-pos (nth 3 eglot--capf-session)) + ;; (trace-values "Recalling cache" (length local-cache) bounds orig-pos) + ) (list - (or (car bounds) (point)) - (or (cdr bounds) (point)) + (car bounds) + (cdr bounds) (lambda (probe pred action) (cond ((eq action 'metadata) metadata) ; metadata @@ -3032,7 +3061,7 @@ for which LSP on-type-formatting should be requested." :company-require-match 'never :company-prefix-length (save-excursion - (when (car bounds) (goto-char (car bounds))) + (goto-char (car bounds)) (when (listp completion-capability) (looking-back (regexp-opt @@ -3040,6 +3069,7 @@ for which LSP on-type-formatting should be requested." (eglot--bol)))) :exit-function (lambda (proxy status) + (eglot--capf-session-flush) (when (memq status '(finished exact)) ;; To assist in using this whole `completion-at-point' ;; function inside `completion-in-region', ensure the exit @@ -3063,17 +3093,12 @@ for which LSP on-type-formatting should be requested." (let ((snippet-fn (and (eql insertTextFormat 2) (eglot--snippet-expansion-fn)))) (cond (textEdit - ;; Undo (yes, undo) the newly inserted completion. - ;; If before completion the buffer was "foo.b" and - ;; now is "foo.bar", `proxy' will be "bar". We - ;; want to delete only "ar" (`proxy' minus the - ;; symbol whose bounds we've calculated before) - ;; (github#160). - (delete-region (+ (- (point) (length proxy)) - (if bounds - (- (cdr bounds) (car bounds)) - 0)) - (point)) + ;; Revert buffer back to state when the edit + ;; was obtained from server. If a `proxy' + ;; "bar" was obtained from a buffer with + ;; "foo.b", the LSP edit applies to that' + ;; state, _not_ the current "foo.bar". + (delete-region orig-pos (point)) (eglot--dbind ((TextEdit) range newText) textEdit (pcase-let ((`(,beg . ,end) (eglot--range-region range))) From bdb400912e01b9cfd06d393c14d8ceb08b6838ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Sun, 2 Apr 2023 23:31:10 +0100 Subject: [PATCH 28/32] Eglot: load built-in GNU ELPA dependencies explicitly Because of outstanding bug#62576, it's way to easy for users to get confused by the M-x package-install process for Eglot. Whenever an Eglot release depends on a new version of a GNU ELPA :core package which is _also_ provided built-in, M-x package-install will install that dependency, but will not always load it on top of the built-in one. The solution is to not use "require" for now. This may potentially lead to double "loads", but that should in principle be idempotent. * lisp/progmodes/eglot.el (project, eldoc, seq, flymake, xref, jsonrpc) (external-completion): Load, don't require. --- lisp/progmodes/eglot.el | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 7e329d2e26a..ae2ba1351dc 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -97,34 +97,30 @@ (require 'imenu) (require 'cl-lib) -(require 'project) + (require 'url-parse) (require 'url-util) (require 'pcase) (require 'compile) ; for some faces (require 'warnings) -(require 'flymake) -(require 'xref) (eval-when-compile (require 'subr-x)) -(require 'jsonrpc) (require 'filenotify) (require 'ert) (require 'array) -(require 'external-completion) -;; ElDoc is preloaded in Emacs, so `require'-ing won't guarantee we are -;; using the latest version from GNU Elpa when we load eglot.el. Use an -;; heuristic to see if we need to `load' it in Emacs < 28. -(if (and (< emacs-major-version 28) - (not (boundp 'eldoc-documentation-strategy))) - (load "eldoc") - (require 'eldoc)) - -;; Similar issue as above for Emacs 26.3 and seq.el. -(if (< emacs-major-version 27) - (load "seq") - (require 'seq)) +;; These dependencies are also GNU ELPA core packages. Because of +;; bug#62576, since there is a risk that M-x package-install, despite +;; having installed them, didn't correctly re-load them over the +;; built-in versions. +(eval-and-compile + (load "project") + (load "eldoc") + (load "seq") + (load "flymake") + (load "xref") + (load "jsonrpc") + (load "external-completion")) ;; forward-declare, but don't require (Emacs 28 doesn't seem to care) (defvar markdown-fontify-code-blocks-natively) From ecf53a50037e66d0f1775c7046d54e218ddd26af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Sun, 2 Apr 2023 23:33:23 +0100 Subject: [PATCH 29/32] ; Eglot: removed unused dependency on 'array.el' * lisp/progmodes/eglot.el (array): Don't require --- lisp/progmodes/eglot.el | 1 - 1 file changed, 1 deletion(-) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index ae2ba1351dc..0112af52c01 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -107,7 +107,6 @@ (require 'subr-x)) (require 'filenotify) (require 'ert) -(require 'array) ;; These dependencies are also GNU ELPA core packages. Because of ;; bug#62576, since there is a risk that M-x package-install, despite From 87f025117b8bafc0780a6ef4a6308dfdc2be0859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Sun, 2 Apr 2023 23:38:37 +0100 Subject: [PATCH 30/32] ; Eldoc: fix doc of e-d-functions w.r.t. :origin keyword * lisp/emacs-lisp/eldoc.el (eldoc-documentation-functions): Fix. --- lisp/emacs-lisp/eldoc.el | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index ef4cda4650f..8b427d6a825 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -448,17 +448,17 @@ documentation-displaying frontends. For example, KEY can be: `eldoc-display-in-echo-area' and `eldoc-display-in-buffer' will use when displaying `:thing''s value. -* `:origin', VALUE being the member of - `eldoc-documentation-functions' where DOCSTRING - originated. `eldoc-display-in-buffer' may use this organize the - documentation buffer accordingly. - * `:echo', controlling how `eldoc-display-in-echo-area' should present this documentation item in the echo area, to save space. If VALUE is a string, echo it instead of DOCSTRING. If a number, only echo DOCSTRING up to that character position. If `skip', don't echo DOCSTRING at all. +The additional KEY `:origin' is always added by ElDoc, its VALUE +being the member of `eldoc-documentation-functions' where +DOCSTRING originated. `eldoc-display-functions' may use this +information to organize display of multiple docstrings. + Finally, major modes should modify this hook locally, for example: (add-hook \\='eldoc-documentation-functions #\\='foo-mode-eldoc nil t) From f2357df91f0262949618bd4da571d3267c1b1dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Sun, 2 Apr 2023 23:40:31 +0100 Subject: [PATCH 31/32] Eldoc: bump package version to 1.14.0 * lisp/emacs-lisp/eldoc.el (Version): Bump to 1.14.0 --- lisp/emacs-lisp/eldoc.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 8b427d6a825..1eb0d38c5ce 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -5,7 +5,7 @@ ;; Author: Noah Friedman ;; Keywords: extensions ;; Created: 1995-10-06 -;; Version: 1.13.0 +;; Version: 1.14.0 ;; Package-Requires: ((emacs "26.3")) ;; This is a GNU ELPA :core package. Avoid functionality that is not From f886ae5cf07bb40ad3fd0262942bdc74efca0277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Mon, 3 Apr 2023 00:00:18 +0100 Subject: [PATCH 32/32] ; * etc/EGLOT-NEWS (Upcoming 1.14): Update. --- etc/EGLOT-NEWS | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/etc/EGLOT-NEWS b/etc/EGLOT-NEWS index 09772a1e71a..2872cdd05cf 100644 --- a/etc/EGLOT-NEWS +++ b/etc/EGLOT-NEWS @@ -39,6 +39,32 @@ run into problems, disable this mechanism like so: (add-hook 'c-common-mode-hook (lambda () (setq-local eglot-cache-session-completions nil))) +** At-point documentation less obtrusive in echo area + +Eglot takes advantage of new features of ElDoc to separate short +documentation strings from large ones, sending the former to be shown in +the ElDoc's echo area and the latter to be shown in other outlets, +such as the *eldoc* buffer obtainable with 'C-h .'. + +** New variable 'eglot-prefer-plaintext' + +Customize this to t to opt-in to docstrings in plain text instead of +Markdown. + +(bug#61373) + +** Progress indicators inhabit the mode-line by default + +To switch to the echo area, customize 'eglot-report-progress' to +'messages'. To switch off progress reporting completely, set to nil. + +** Snippet support is easier to enable + +The user needn't manually activate 'yas-minor-mode' or +'yas-global-mode'. If YASnippet is installed and the server supports +snippets, it is used automatically, unless the symbol 'yasnippet' has +been added to 'eglot-stay-out-of'. + * Changes in Eglot 1.13 (15/03/2023)