commit a27b0f7f307ccf54fd5910a0655bd72b23e69fcf (HEAD, refs/remotes/origin/master) Author: Andrew G Cohen Date: Sun Mar 26 08:26:18 2023 +0800 * lisp/gnus/nnselect.el (nnselect-request-thread): Correctly parse queries. diff --git a/lisp/gnus/nnselect.el b/lisp/gnus/nnselect.el index 87cb1275313..66577282a0f 100644 --- a/lisp/gnus/nnselect.el +++ b/lisp/gnus/nnselect.el @@ -64,6 +64,7 @@ nnselect (defvar gnus-inhibit-demon) (defvar gnus-message-group-art) +(defvar gnus-search-use-parsed-queries) ;; For future use (defvoo nnselect-directory gnus-directory @@ -677,7 +678,8 @@ nnselect-request-thread ;; If so we perform the query, massage the result, and return ;; the new headers back to the caller to incorporate into the ;; current summary buffer. - (let* ((group-spec + (let* ((gnus-search-use-parsed-queries t) + (group-spec (list (delq nil (list (or server (gnus-group-server artgroup)) (unless gnus-refer-thread-use-search commit e2ff4dbf3db5d3eae463660651753a127620fbe8 Author: Michael Albinus Date: Sat Mar 25 18:29:25 2023 +0100 * test/infra/Dockerfile.emba (emacs-eglot): Adapt software selection. diff --git a/test/infra/Dockerfile.emba b/test/infra/Dockerfile.emba index 5b14384ceb3..520fcb7e15e 100644 --- a/test/infra/Dockerfile.emba +++ b/test/infra/Dockerfile.emba @@ -64,11 +64,17 @@ FROM emacs-base as emacs-eglot RUN apt-get update && \ apt-get install -y --no-install-recommends -o=Dpkg::Use-Pty=0 \ - wget \ + snapd wget \ && rm -rf /var/lib/apt/lists/* -# We install a recent clangd for Eglot tests. -RUN bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" +# A recent clangd. It must be at least clangd 14, which is in Debian +# bookworm. +RUN bash -c "$(wget --no-check-certificate -O - https://apt.llvm.org/llvm.sh)" + +# A recent pylsp. Since Debian bookworm there is the package +# python3-pylsp. +RUN snap install core +RUN snap install pylsp COPY . /checkout WORKDIR /checkout commit 267d9d5e3d1b1c5e1c9a61c5370581f26efda010 Author: Michael Albinus Date: Sat Mar 25 18:28:55 2023 +0100 Connection-local variables are applied in buffers visiting a remote file * etc/NEWS: Connection-local variables are applied in buffers visiting a remote file. * test/lisp/net/tramp-tests.el (tramp-test34-connection-local-variables): Fix test. diff --git a/etc/NEWS b/etc/NEWS index 2a87bf08406..6c558553c58 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -279,11 +279,17 @@ their customization options. * Incompatible Lisp Changes in Emacs 30.1 --- -** The escape sequence \x not followed by hex digits is now an error. -Previously, \x without at least one hex digit denoted character code +** The escape sequence '\x' not followed by hex digits is now an error. +Previously, '\x' without at least one hex digit denoted character code zero (NUL) but as this was neither intended nor documented or even known by anyone, it is now treated as an error by the Lisp reader. +--- +** Connection-local variables are applied in buffers visiting a remote file. +This overrides possible directory-local or file-local variables with +the same name. + +--- ** User option 'tramp-completion-reread-directory-timeout' has been removed. This user option has been obsoleted in Emacs 27, use 'remote-file-name-inhibit-cache' instead. diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el index 677dd35d796..835763e0237 100644 --- a/test/lisp/net/tramp-tests.el +++ b/test/lisp/net/tramp-tests.el @@ -6005,22 +6005,42 @@ tramp-test34-connection-local-variables (should (eq local-variable 'connect)) (kill-buffer (current-buffer))) - ;; `local-variable' is dir-local due to existence of .dir-locals.el. + ;; `local-variable' is still connection-local due to Tramp. + ;; `find-file-hook' overrides dir-local settings. (write-region "((nil . ((local-variable . dir))))" nil (expand-file-name ".dir-locals.el" tmp-name1)) (should (file-exists-p (expand-file-name ".dir-locals.el" tmp-name1))) - (with-current-buffer (find-file-noselect tmp-name2) - (should (eq local-variable 'dir)) - (kill-buffer (current-buffer))) - - ;; `local-variable' is file-local due to specifying as file variable. + (when (memq #'tramp-set-connection-local-variables-for-buffer + find-file-hook) + (with-current-buffer (find-file-noselect tmp-name2) + (should (eq local-variable 'connect)) + (kill-buffer (current-buffer)))) + ;; `local-variable' is dir-local due to existence of .dir-locals.el. + (let ((find-file-hook + (remq #'tramp-set-connection-local-variables-for-buffer + find-file-hook))) + (with-current-buffer (find-file-noselect tmp-name2) + (should (eq local-variable 'dir)) + (kill-buffer (current-buffer)))) + + ;; `local-variable' is still connection-local due to Tramp. + ;; `find-file-hook' overrides dir-local settings. (write-region "-*- mode: comint; local-variable: file; -*-" nil tmp-name2) (should (file-exists-p tmp-name2)) - (with-current-buffer (find-file-noselect tmp-name2) - (should (eq local-variable 'file)) - (kill-buffer (current-buffer)))) + (when (memq #'tramp-set-connection-local-variables-for-buffer + find-file-hook) + (with-current-buffer (find-file-noselect tmp-name2) + (should (eq local-variable 'connect)) + (kill-buffer (current-buffer)))) + ;; `local-variable' is file-local due to specifying as file variable. + (let ((find-file-hook + (remq #'tramp-set-connection-local-variables-for-buffer + find-file-hook))) + (with-current-buffer (find-file-noselect tmp-name2) + (should (eq local-variable 'file)) + (kill-buffer (current-buffer))))) ;; Cleanup. (custom-set-variables commit d02f0221c41bf8b248d79e3aaebdec68789001da Author: Mattias Engdegård Date: Sat Mar 25 17:43:43 2023 +0100 Fix shortdoc-tests when Unicode arrows can be displayed New shortdoc functions use Unicode arrows when possible, which caused some tests to fail if run under such circumstances. * test/lisp/emacs-lisp/shortdoc-tests.el (shortdoc-tests--to-ascii): New function. (shortdoc-function-examples-test) (shortdoc-help-fns-examples-function-test): Call it. diff --git a/test/lisp/emacs-lisp/shortdoc-tests.el b/test/lisp/emacs-lisp/shortdoc-tests.el index d2dfbc66864..596b47d2543 100644 --- a/test/lisp/emacs-lisp/shortdoc-tests.el +++ b/test/lisp/emacs-lisp/shortdoc-tests.el @@ -65,30 +65,48 @@ shortdoc-all-groups-work (when buf (kill-buffer buf)))))) +(defun shortdoc-tests--to-ascii (x) + "Translate Unicode arrows to ASCII for making the test work everywhere." + (cond ((consp x) + (cons (shortdoc-tests--to-ascii (car x)) + (shortdoc-tests--to-ascii (cdr x)))) + ((stringp x) + (thread-last x + (string-replace "⇒" "=>") + (string-replace "→" "->"))) + (t x))) + (ert-deftest shortdoc-function-examples-test () "Test the extraction of usage examples of some Elisp functions." (should (equal '((list . "(delete 2 (list 1 2 3 4))\n => (1 3 4)\n (delete \"a\" (list \"a\" \"b\" \"c\" \"d\"))\n => (\"b\" \"c\" \"d\")")) - (shortdoc-function-examples 'delete))) + (shortdoc-tests--to-ascii + (shortdoc-function-examples 'delete)))) (should (equal '((alist . "(assq 'foo '((foo . bar) (zot . baz)))\n => (foo . bar)") (list . "(assq 'b '((a . 1) (b . 2)))\n => (b . 2)")) - (shortdoc-function-examples 'assq))) + (shortdoc-tests--to-ascii + (shortdoc-function-examples 'assq)))) (should (equal '((regexp . "(string-match-p \"^[fo]+\" \"foobar\")\n => 0")) - (shortdoc-function-examples 'string-match-p)))) + (shortdoc-tests--to-ascii + (shortdoc-function-examples 'string-match-p))))) (ert-deftest shortdoc-help-fns-examples-function-test () "Test that `shortdoc-help-fns-examples-function' correctly prints ELisp function examples." (with-temp-buffer (shortdoc-help-fns-examples-function 'string-fill) (should (equal "\n Examples:\n\n (string-fill \"Three short words\" 12)\n => \"Three short\\nwords\"\n (string-fill \"Long-word\" 3)\n => \"Long-word\"\n\n" - (buffer-substring-no-properties (point-min) (point-max)))) + (shortdoc-tests--to-ascii + (buffer-substring-no-properties (point-min) (point-max))))) (erase-buffer) (shortdoc-help-fns-examples-function 'assq) (should (equal "\n Examples:\n\n (assq 'foo '((foo . bar) (zot . baz)))\n => (foo . bar)\n\n (assq 'b '((a . 1) (b . 2)))\n => (b . 2)\n\n" - (buffer-substring-no-properties (point-min) (point-max)))) + (shortdoc-tests--to-ascii + (buffer-substring-no-properties (point-min) (point-max))))) (erase-buffer) (shortdoc-help-fns-examples-function 'string-trim) (should (equal "\n Example:\n\n (string-trim \" foo \")\n => \"foo\"\n\n" - (buffer-substring-no-properties (point-min) (point-max)))))) + (shortdoc-tests--to-ascii + (buffer-substring-no-properties (point-min) + (point-max))))))) (provide 'shortdoc-tests) commit 52b67740d10df8ca539fdc2c7d50283997683141 Author: Mattias Engdegård Date: Sat Mar 25 15:38:00 2023 +0100 Generalise a LAP optimisation rule * lisp/emacs-lisp/byte-opt.el (byte-optimize-lapcode): Accept (stack-set 1) as equivalent to (discardN-preserve-tos 1) in a rule previously overlooked. This is usually beneficial in code size and almost always shortens dynamic paths. diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 3c7aeb89525..0891ec80beb 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -2765,7 +2765,9 @@ byte-optimize-lapcode (or (memq (caar tmp) '(byte-discard byte-discardN)) ;; Make sure we don't hoist a discardN-preserve-tos ;; that really should be merged or deleted instead. - (and (eq (caar tmp) 'byte-discardN-preserve-tos) + (and (or (eq (caar tmp) 'byte-discardN-preserve-tos) + (and (eq (caar tmp) 'byte-stack-set) + (eql (cdar tmp) 1))) (let ((next (cadr tmp))) (not (or (memq (car next) '(byte-discardN-preserve-tos commit db7e95531ac36ae842787b6c5f2859d0642c78cc Author: Yuan Fu Date: Fri Mar 24 12:25:19 2023 -0700 Switch buffer in Ftreesit_query_capture This way both #pred and #match predicates runs in the node's buffer by default. * src/treesit.c: (treesit_predicate_match): No need to switch buffer anymore. (Ftreesit_query_capture): Switch buffer. * doc/lispref/parsing.texi (Pattern Matching): Update manual. diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi index cba323d3a56..86a5d9f2e52 100644 --- a/doc/lispref/parsing.texi +++ b/doc/lispref/parsing.texi @@ -1311,7 +1311,8 @@ Pattern Matching @deffn Predicate pred fn &rest nodes Matches if function @var{fn} returns non-@code{nil} when passed each -node in @var{nodes} as arguments. +node in @var{nodes} as arguments. The function runs with the current +buffer set to the buffer of node being queried. @end deffn Note that a predicate can only refer to capture names that appear in diff --git a/src/treesit.c b/src/treesit.c index 7f4e6cb0e13..36a297ec7da 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -2533,10 +2533,6 @@ treesit_predicate_match (Lisp_Object args, struct capture_range captures, signal_data)) return false; - struct buffer *old_buffer = current_buffer; - struct buffer *buffer = XBUFFER (XTS_PARSER (XTS_NODE (node)->parser)->buffer); - set_buffer_internal (buffer); - TSNode treesit_node = XTS_NODE (node)->node; ptrdiff_t visible_beg = XTS_PARSER (XTS_NODE (node)->parser)->visible_beg; uint32_t start_byte_offset = ts_node_start_byte (treesit_node); @@ -2563,8 +2559,6 @@ treesit_predicate_match (Lisp_Object args, struct capture_range captures, ZV = old_zv; ZV_BYTE = old_zv_byte; - set_buffer_internal (old_buffer); - return (val > 0); } @@ -2870,6 +2864,10 @@ DEFUN ("treesit-query-capture", Lisp_Object prev_result = result; Lisp_Object predicates_table = make_vector (patterns_count, Qt); Lisp_Object predicate_signal_data = Qnil; + + struct buffer *old_buf = current_buffer; + set_buffer_internal (buf); + while (ts_query_cursor_next_match (cursor, &match)) { /* Record the checkpoint that we may roll back to. */ @@ -2925,6 +2923,7 @@ DEFUN ("treesit-query-capture", ts_query_delete (treesit_query); ts_query_cursor_delete (cursor); } + set_buffer_internal (old_buf); /* Some capture predicate signaled an error. */ if (!NILP (predicate_signal_data)) commit a37f19b14a6b51fa794178095cf56c112a1396e8 Author: Yuan Fu Date: Fri Mar 24 12:19:25 2023 -0700 Handle signals gracefully in tree-sitter query predicates Before this change, predicate functions can signal, which will cause Ftreesit_query_capture to skip freeing the query and cursor object. We make predicate functions return the signal data rather than directly signal. * src/treesit.c (treesit_predicate_capture_name_to_node) (treesit_predicate_capture_name_to_text) (treesit_predicate_equal) (treesit_predicate_match) (treesit_predicate_pred) (treesit_eval_predicates): Return signal rather than signaling directly. (Ftreesit_query_capture): Check for returned signal data. diff --git a/src/treesit.c b/src/treesit.c index cd98ff38293..7f4e6cb0e13 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -2407,87 +2407,111 @@ treesit_predicates_for_pattern (TSQuery *query, uint32_t pattern_index) return Fnreverse (result); } -/* Translate a capture NAME (symbol) to a node. - Signals treesit-query-error if such node is not captured. */ -static Lisp_Object +/* Translate a capture NAME (symbol) to a node. If everything goes + fine, set NODE and return true; if error occurs (e.g., when there + is no node for the capture name), set NODE to Qnil, SIGNAL_DATA to + a suitable signal data, and return false. */ +static bool treesit_predicate_capture_name_to_node (Lisp_Object name, - struct capture_range captures) + struct capture_range captures, + Lisp_Object *node, + Lisp_Object *signal_data) { - Lisp_Object node = Qnil; + *node = Qnil; for (Lisp_Object tail = captures.start; !EQ (tail, captures.end); tail = XCDR (tail)) { if (EQ (XCAR (XCAR (tail)), name)) { - node = XCDR (XCAR (tail)); + *node = XCDR (XCAR (tail)); break; } } - if (NILP (node)) - xsignal3 (Qtreesit_query_error, - build_string ("Cannot find captured node"), - name, build_string ("A predicate can only refer" - " to captured nodes in the " - "same pattern")); - return node; + if (NILP (*node)) + { + *signal_data = list3 (build_string ("Cannot find captured node"), + name, build_string ("A predicate can only refer" + " to captured nodes in the " + "same pattern")); + return false; + } + return true; } /* Translate a capture NAME (symbol) to the text of the captured node. - Signals treesit-query-error if such node is not captured. */ -static Lisp_Object + If everything goes fine, set TEXT to the text and return true; + otherwise set TEXT to Qnil and set SIGNAL_DATA to a suitable signal + data. */ +static bool treesit_predicate_capture_name_to_text (Lisp_Object name, - struct capture_range captures) + struct capture_range captures, + Lisp_Object *text, + Lisp_Object *signal_data) { - Lisp_Object node = treesit_predicate_capture_name_to_node (name, captures); + Lisp_Object node = Qnil; + if (!treesit_predicate_capture_name_to_node (name, captures, &node, signal_data)) + return false; struct buffer *old_buffer = current_buffer; set_buffer_internal (XBUFFER (XTS_PARSER (XTS_NODE (node)->parser)->buffer)); - Lisp_Object text = Fbuffer_substring (Ftreesit_node_start (node), - Ftreesit_node_end (node)); + *text = Fbuffer_substring (Ftreesit_node_start (node), + Ftreesit_node_end (node)); set_buffer_internal (old_buffer); - return text; + return true; } /* Handles predicate (#equal A B). Return true if A equals B; return false otherwise. A and B can be either string, or a capture name. The capture name evaluates to the text its captured node spans in - the buffer. */ + the buffer. If everything goes fine, don't touch SIGNAL_DATA; if + error occurs, set it to a suitable signal data. */ static bool -treesit_predicate_equal (Lisp_Object args, struct capture_range captures) +treesit_predicate_equal (Lisp_Object args, struct capture_range captures, + Lisp_Object *signal_data) { if (XFIXNUM (Flength (args)) != 2) - xsignal2 (Qtreesit_query_error, - build_string ("Predicate `equal' requires " - "two arguments but only given"), - Flength (args)); - + { + *signal_data = list2 (build_string ("Predicate `equal' requires " + "two arguments but only given"), + Flength (args)); + return false; + } Lisp_Object arg1 = XCAR (args); Lisp_Object arg2 = XCAR (XCDR (args)); - Lisp_Object text1 = (STRINGP (arg1) - ? arg1 - : treesit_predicate_capture_name_to_text (arg1, - captures)); - Lisp_Object text2 = (STRINGP (arg2) - ? arg2 - : treesit_predicate_capture_name_to_text (arg2, - captures)); + Lisp_Object text1 = arg1; + Lisp_Object text2 = arg2; + if (SYMBOLP (arg1)) + { + if (!treesit_predicate_capture_name_to_text (arg1, captures, &text1, + signal_data)) + return false; + } + if (SYMBOLP (arg2)) + { + if (!treesit_predicate_capture_name_to_text (arg2, captures, &text2, + signal_data)) + return false; + } return !NILP (Fstring_equal (text1, text2)); } /* Handles predicate (#match "regexp" @node). Return true if "regexp" - matches the text spanned by @node; return false otherwise. Matching - is case-sensitive. */ + matches the text spanned by @node; return false otherwise. + Matching is case-sensitive. If everything goes fine, don't touch + SIGNAL_DATA; if error occurs, set it to a suitable signal data. */ static bool -treesit_predicate_match (Lisp_Object args, struct capture_range captures) +treesit_predicate_match (Lisp_Object args, struct capture_range captures, + Lisp_Object *signal_data) { if (XFIXNUM (Flength (args)) != 2) - xsignal2 (Qtreesit_query_error, - build_string ("Predicate `match' requires two " - "arguments but only given"), - Flength (args)); - + { + *signal_data = list2 (build_string ("Predicate `match' requires two " + "arguments but only given"), + Flength (args)); + return false; + } Lisp_Object regexp = XCAR (args); Lisp_Object capture_name = XCAR (XCDR (args)); @@ -2504,8 +2528,10 @@ treesit_predicate_match (Lisp_Object args, struct capture_range captures) build_string ("The second argument to `match' should " "be a capture name, not a string")); - Lisp_Object node = treesit_predicate_capture_name_to_node (capture_name, - captures); + Lisp_Object node = Qnil; + if (!treesit_predicate_capture_name_to_node (capture_name, captures, &node, + signal_data)) + return false; struct buffer *old_buffer = current_buffer; struct buffer *buffer = XBUFFER (XTS_PARSER (XTS_NODE (node)->parser)->buffer); @@ -2544,54 +2570,66 @@ treesit_predicate_match (Lisp_Object args, struct capture_range captures) /* Handles predicate (#pred FN ARG...). Return true if FN returns non-nil; return false otherwise. The arity of FN must match the - number of ARGs */ + number of ARGs. If everything goes fine, don't touch SIGNAL_DATA; + if error occurs, set it to a suitable signal data. */ static bool -treesit_predicate_pred (Lisp_Object args, struct capture_range captures) +treesit_predicate_pred (Lisp_Object args, struct capture_range captures, + Lisp_Object *signal_data) { if (XFIXNUM (Flength (args)) < 2) - xsignal2 (Qtreesit_query_error, - build_string ("Predicate `pred' requires " - "at least two arguments, " - "but was only given"), - Flength (args)); + { + *signal_data = list2 (build_string ("Predicate `pred' requires " + "at least two arguments, " + "but was only given"), + Flength (args)); + return false; + } Lisp_Object fn = Fintern (XCAR (args), Qnil); Lisp_Object nodes = Qnil; Lisp_Object tail = XCDR (args); FOR_EACH_TAIL (tail) - nodes = Fcons (treesit_predicate_capture_name_to_node (XCAR (tail), - captures), - nodes); + { + Lisp_Object node = Qnil; + if (!treesit_predicate_capture_name_to_node (XCAR (tail), captures, &node, + signal_data)) + return false; + nodes = Fcons (node, nodes); + } nodes = Fnreverse (nodes); return !NILP (CALLN (Fapply, fn, nodes)); } /* If all predicates in PREDICATES passes, return true; otherwise - return false. */ + return false. If everything goes fine, don't touch SIGNAL_DATA; if + error occurs, set it to a suitable signal data. */ static bool -treesit_eval_predicates (struct capture_range captures, Lisp_Object predicates) +treesit_eval_predicates (struct capture_range captures, Lisp_Object predicates, + Lisp_Object *signal_data) { bool pass = true; /* Evaluate each predicates. */ for (Lisp_Object tail = predicates; - !NILP (tail); tail = XCDR (tail)) + pass && !NILP (tail); tail = XCDR (tail)) { Lisp_Object predicate = XCAR (tail); Lisp_Object fn = XCAR (predicate); Lisp_Object args = XCDR (predicate); if (!NILP (Fstring_equal (fn, Vtreesit_str_equal))) - pass &= treesit_predicate_equal (args, captures); + pass &= treesit_predicate_equal (args, captures, signal_data); else if (!NILP (Fstring_equal (fn, Vtreesit_str_match))) - pass &= treesit_predicate_match (args, captures); + pass &= treesit_predicate_match (args, captures, signal_data); else if (!NILP (Fstring_equal (fn, Vtreesit_str_pred))) - pass &= treesit_predicate_pred (args, captures); + pass &= treesit_predicate_pred (args, captures, signal_data); else - xsignal3 (Qtreesit_query_error, - build_string ("Invalid predicate"), - fn, build_string ("Currently Emacs only supports" - " equal, match, and pred" - " predicate")); + { + *signal_data = list3 (build_string ("Invalid predicate"), + fn, build_string ("Currently Emacs only supports" + " equal, match, and pred" + " predicates")); + pass = false; + } } /* If all predicates passed, add captures to result list. */ return pass; @@ -2831,11 +2869,12 @@ DEFUN ("treesit-query-capture", Lisp_Object result = Qnil; Lisp_Object prev_result = result; Lisp_Object predicates_table = make_vector (patterns_count, Qt); + Lisp_Object predicate_signal_data = Qnil; while (ts_query_cursor_next_match (cursor, &match)) { /* Record the checkpoint that we may roll back to. */ prev_result = result; - /* Get captured nodes. */ + /* 1. Get captured nodes. */ const TSQueryCapture *captures = match.captures; for (int idx = 0; idx < match.capture_count; idx++) { @@ -2858,7 +2897,8 @@ DEFUN ("treesit-query-capture", result = Fcons (cap, result); } - /* Get predicates. */ + /* 2. Get predicates and check whether this match can be + included in the result list. */ Lisp_Object predicates = AREF (predicates_table, match.pattern_index); if (EQ (predicates, Qt)) { @@ -2869,15 +2909,27 @@ DEFUN ("treesit-query-capture", /* captures_lisp = Fnreverse (captures_lisp); */ struct capture_range captures_range = { result, prev_result }; - if (!treesit_eval_predicates (captures_range, predicates)) - /* Predicates didn't pass, roll back. */ + bool match = treesit_eval_predicates (captures_range, predicates, + &predicate_signal_data); + if (!NILP (predicate_signal_data)) + break; + + /* Predicates didn't pass, roll back. */ + if (!match) result = prev_result; } + + /* Final clean up. */ if (needs_to_free_query_and_cursor) { ts_query_delete (treesit_query); ts_query_cursor_delete (cursor); } + + /* Some capture predicate signaled an error. */ + if (!NILP (predicate_signal_data)) + xsignal (Qtreesit_query_error, predicate_signal_data); + return Fnreverse (result); } commit f446bfc8198f34d3fc1fa08150383865929ff61c Author: Yuan Fu Date: Fri Mar 24 11:34:39 2023 -0700 ; * lisp/emacs-lisp/eldoc.el (eldoc-doc-buffer-separator): Fix doc. diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 43e768054a7..ef4cda4650f 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -500,8 +500,8 @@ eldoc-doc-buffer (t (current-buffer))))) (defvar eldoc-doc-buffer-separator - "String used to separate items in Eldoc documentation buffer." - (concat "\n" (propertize "\n" 'face '(:inherit separator-line :extend t)) "\n")) + (concat "\n" (propertize "\n" 'face '(:inherit separator-line :extend t)) "\n") + "String used to separate items in Eldoc documentation buffer.") (defun eldoc--format-doc-buffer (docs) "Ensure DOCS are displayed in an *eldoc* buffer." commit 455412dd89ddb1bf39b4ca8f2befc940a350f9d0 Author: Yuan Fu Date: Tue Mar 21 16:13:23 2023 -0700 ; Minor refactor of Ftreesit_query_capture * src/treesit.c (Ftreesit_query_capture): Move around some variable initialization. diff --git a/src/treesit.c b/src/treesit.c index e728d697c9d..cd98ff38293 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -2770,12 +2770,9 @@ DEFUN ("treesit-query-capture", Lisp_Object lisp_node = treesit_resolve_node (node); /* Extract C values from Lisp objects. */ - TSNode treesit_node - = XTS_NODE (lisp_node)->node; - Lisp_Object lisp_parser - = XTS_NODE (lisp_node)->parser; - ptrdiff_t visible_beg - = XTS_PARSER (XTS_NODE (lisp_node)->parser)->visible_beg; + TSNode treesit_node = XTS_NODE (lisp_node)->node; + Lisp_Object lisp_parser = XTS_NODE (lisp_node)->parser; + const TSLanguage *lang = ts_parser_language (XTS_PARSER (lisp_parser)->parser); @@ -2804,6 +2801,8 @@ DEFUN ("treesit-query-capture", /* Set query range. */ if (!NILP (beg) && !NILP (end)) { + ptrdiff_t visible_beg + = XTS_PARSER (XTS_NODE (lisp_node)->parser)->visible_beg; ptrdiff_t beg_byte = CHAR_TO_BYTE (XFIXNUM (beg)); ptrdiff_t end_byte = CHAR_TO_BYTE (XFIXNUM (end)); /* We never let tree-sitter run on buffers too large, so these commit c3a25bfb75c1cd41a1d2c613ec944a490cf8aff7 Author: Yuan Fu Date: Tue Mar 21 16:03:08 2023 -0700 Refactor Ftreesit_query_capture Refactor some part of Ftreesit_query_capture out into separate functions, to pave the way for other query-based functions. * src/treesit.c (treesit_resolve_node): New function. (treesit_initialize_query): New function. (Ftreesit_query_capture): Refactor some part into new functions. diff --git a/src/treesit.c b/src/treesit.c index 5a4fe3e8803..e728d697c9d 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -2631,8 +2631,8 @@ DEFUN ("treesit-query-compile", Lisp_Object signal_symbol = Qnil; Lisp_Object signal_data = Qnil; TSQuery *treesit_query = treesit_ensure_query_compiled (lisp_query, - &signal_symbol, - &signal_data); + &signal_symbol, + &signal_data); if (treesit_query == NULL) xsignal (signal_symbol, signal_data); @@ -2641,6 +2641,92 @@ DEFUN ("treesit-query-compile", } } +/* Resolve OBJ into a tree-sitter node Lisp_Object. OBJ can be a + node, a parser, or a language symbol. Note that this function can + signal. */ +static Lisp_Object treesit_resolve_node (Lisp_Object obj) +{ + if (TS_NODEP (obj)) + { + treesit_check_node (obj); /* Check if up-to-date. */ + return obj; + } + else if (TS_PARSERP (obj)) + { + treesit_check_parser (obj); /* Check if deleted. */ + return Ftreesit_parser_root_node (obj); + } + else if (SYMBOLP (obj)) + { + Lisp_Object parser + = Ftreesit_parser_create (obj, Fcurrent_buffer (), Qnil); + return Ftreesit_parser_root_node (parser); + } + else + xsignal2 (Qwrong_type_argument, + list4 (Qor, Qtreesit_node_p, Qtreesit_parser_p, Qsymbolp), + obj); +} + +/* Create and initialize QUERY. When success, initialize TS_QUERY, + CURSOR, and NEED_FREE, and return true; if failed, initialize + SIGNAL_SYMBOL and SIGNAL_DATA, and return false. If NEED_FREE is + initialized to true, the TS_QUERY and CURSOR needs to be freed + after use; otherwise they shouldn't be freed by hand. + + Basically this function looks at QUERY and check its type, if QUERY + is a compiled query, this function takes out its query and cursor; + if QUERY is a string or a cons, this function creates a new query + and cursor (so they need to be manually freed). + + This function assumes QUERY is either a compiled query, a string or + a cons, the caller should make sure QUERY is valid. + + LANG is the language to use if we need to create the query and + cursor. */ +static bool +treesit_initialize_query (Lisp_Object query, const TSLanguage *lang, + TSQuery **ts_query, TSQueryCursor **cursor, + bool *need_free, Lisp_Object *signal_symbol, + Lisp_Object *signal_data) +{ + if (TS_COMPILED_QUERY_P (query)) + { + *ts_query = treesit_ensure_query_compiled (query, signal_symbol, + signal_data); + *cursor = XTS_COMPILED_QUERY (query)->cursor; + /* We don't need to free ts_query and cursor because they + are stored in a lisp object, which is tracked by gc. */ + *need_free = false; + return (*ts_query != NULL); + } + else + { + /* Since query is not TS_COMPILED_QUERY, it can only be a string + or a cons. */ + if (CONSP (query)) + query = Ftreesit_query_expand (query); + char *query_string = SSDATA (query); + uint32_t error_offset; + TSQueryError error_type; + *ts_query = ts_query_new (lang, query_string, strlen (query_string), + &error_offset, &error_type); + if (*ts_query == NULL) + { + *signal_symbol = Qtreesit_query_error; + *signal_data = treesit_compose_query_signal_data (error_offset, + error_type, query); + return false; + } + else + { + *cursor = ts_query_cursor_new (); + *need_free = true; + return true; + } + } +} + DEFUN ("treesit-query-capture", Ftreesit_query_capture, Streesit_query_capture, 2, 5, 0, @@ -2681,27 +2767,7 @@ DEFUN ("treesit-query-capture", treesit_initialize (); /* Resolve NODE into an actual node. */ - Lisp_Object lisp_node; - if (TS_NODEP (node)) - { - treesit_check_node (node); /* Check if up-to-date. */ - lisp_node = node; - } - else if (TS_PARSERP (node)) - { - treesit_check_parser (node); /* Check if deleted. */ - lisp_node = Ftreesit_parser_root_node (node); - } - else if (SYMBOLP (node)) - { - Lisp_Object parser - = Ftreesit_parser_create (node, Fcurrent_buffer (), Qnil); - lisp_node = Ftreesit_parser_root_node (parser); - } - else - xsignal2 (Qwrong_type_argument, - list4 (Qor, Qtreesit_node_p, Qtreesit_parser_p, Qsymbolp), - node); + Lisp_Object lisp_node = treesit_resolve_node (node); /* Extract C values from Lisp objects. */ TSNode treesit_node @@ -2725,40 +2791,15 @@ DEFUN ("treesit-query-capture", TSQuery *treesit_query; TSQueryCursor *cursor; bool needs_to_free_query_and_cursor; - if (TS_COMPILED_QUERY_P (query)) - { - Lisp_Object signal_symbol = Qnil; - Lisp_Object signal_data = Qnil; - treesit_query = treesit_ensure_query_compiled (query, &signal_symbol, - &signal_data); - cursor = XTS_COMPILED_QUERY (query)->cursor; - /* We don't need to free ts_query and cursor because they - are stored in a lisp object, which is tracked by gc. */ - needs_to_free_query_and_cursor = false; - if (treesit_query == NULL) - xsignal (signal_symbol, signal_data); - } - else - { - /* Since query is not TS_COMPILED_QUERY, it can only be a string - or a cons. */ - if (CONSP (query)) - query = Ftreesit_query_expand (query); - char *query_string = SSDATA (query); - uint32_t error_offset; - TSQueryError error_type; - treesit_query = ts_query_new (lang, query_string, strlen (query_string), - &error_offset, &error_type); - if (treesit_query == NULL) - xsignal (Qtreesit_query_error, - treesit_compose_query_signal_data (error_offset, - error_type, query)); - cursor = ts_query_cursor_new (); - needs_to_free_query_and_cursor = true; - } + Lisp_Object signal_symbol; + Lisp_Object signal_data; + if (!treesit_initialize_query (query, lang, &treesit_query, &cursor, + &needs_to_free_query_and_cursor, + &signal_symbol, &signal_data)) + xsignal (signal_symbol, signal_data); - /* WARN: After this point, free treesit_query and cursor before every - signal and return. */ + /* WARN: After this point, free TREESIT_QUERY and CURSOR before every + signal and return if NEEDS_TO_FREE_QUERY_AND_CURSOR is true. */ /* Set query range. */ if (!NILP (beg) && !NILP (end)) commit 4a2dccd6a68625914ae6fa4b0bbb002f5ea079db Author: Eli Zaretskii Date: Fri Mar 24 21:37:11 2023 +0300 Fix invocation of elixir LSP server on MS-Windows * lisp/progmodes/eglot.el (eglot-server-programs): Invoke the elixir-ls LSP via a batch file if using an MS-Windows shell. (Bug#62407) diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 806e498c38c..cc9c8115b08 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -222,7 +222,10 @@ eglot-server-programs (dart-mode . ("dart" "language-server" "--client-id" "emacs.eglot-dart")) ((elixir-mode elixir-ts-mode heex-ts-mode) - . ("language_server.sh")) + . ,(if (and (fboundp 'w32-shell-dos-semantics) + (w32-shell-dos-semantics)) + '("language_server.bat") + '("language_server.sh"))) (ada-mode . ("ada_language_server")) (scala-mode . ,(eglot-alternatives '("metals" "metals-emacs"))) commit e79b4ccd7948108a29a8a3d84489e47c376c95db Author: João Távora Date: Fri Mar 24 17:37:01 2023 +0000 Allow users to customize eldoc buffer separator (bug#62029) * lisp/emacs-lisp/eldoc.el (eldoc-doc-buffer-separator): New variable. (eldoc--format-doc-buffer): Use it. diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index ccc466cfd44..43e768054a7 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -499,6 +499,10 @@ eldoc-doc-buffer (display-buffer (current-buffer))) (t (current-buffer))))) +(defvar eldoc-doc-buffer-separator + "String used to separate items in Eldoc documentation buffer." + (concat "\n" (propertize "\n" 'face '(:inherit separator-line :extend t)) "\n")) + (defun eldoc--format-doc-buffer (docs) "Ensure DOCS are displayed in an *eldoc* buffer." (with-current-buffer (if (buffer-live-p eldoc--doc-buffer) @@ -522,7 +526,8 @@ eldoc--format-doc-buffer ": " this-doc)) do (insert this-doc) - when rest do (insert "\n") + when rest do + (insert eldoc-doc-buffer-separator) finally (goto-char (point-min))) ;; Rename the buffer, taking into account whether it was ;; hidden or not commit a384401eab59fbc3e551211c4ebf342c73957409 Author: João Távora Date: Fri Mar 24 17:16:00 2023 +0000 Eldoc: slightly enhance meaning of :echo option Can now be a string to echo instead of a larger docstring. * lisp/emacs-lisp/eldoc.el (eldoc-documentation-functions): Describe :echo. (eldoc--echo-area-render): Allow :echo to be string. diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 05033f39cba..ccc466cfd44 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -454,9 +454,10 @@ eldoc-documentation-functions documentation buffer accordingly. * `:echo', controlling how `eldoc-display-in-echo-area' should - present this documentation item, to save space. If VALUE is - `skip' don't echo DOCSTRING. If a number, only echo DOCSTRING - up to that character position. + 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. Finally, major modes should modify this hook locally, for example: @@ -544,7 +545,10 @@ eldoc--echo-area-render for echo = (plist-get plist :echo) for thing = (plist-get plist :thing) unless (eq echo 'skip) do - (when echo (setq this-doc (substring this-doc 0 echo))) + (setq this-doc + (cond ((integerp echo) (substring this-doc 0 echo)) + ((stringp echo) echo) + (t this-doc))) (when thing (setq this-doc (concat (propertize (format "%s" thing) commit 3e32865d2c56b9f5048f6f2e5a8236a2bb4e26d8 Author: Mattias Engdegård Date: Fri Mar 24 16:03:41 2023 +0100 * etc/NEWS: Mention that \x without hex digits is an error. diff --git a/etc/NEWS b/etc/NEWS index ffce2baf2d0..2a87bf08406 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -278,6 +278,12 @@ their customization options. * Incompatible Lisp Changes in Emacs 30.1 +--- +** The escape sequence \x not followed by hex digits is now an error. +Previously, \x without at least one hex digit denoted character code +zero (NUL) but as this was neither intended nor documented or even +known by anyone, it is now treated as an error by the Lisp reader. + ** User option 'tramp-completion-reread-directory-timeout' has been removed. This user option has been obsoleted in Emacs 27, use 'remote-file-name-inhibit-cache' instead. commit 541eec259be19464f855f852f99df4f995a380cc Author: João Távora Date: Fri Mar 24 14:34:05 2023 +0000 Eldoc: fix bug recently introduced in "old" protocol In the "old" protocol, eldoc-documentation-strategy is actually used as the deprecated eldoc-documentation-function and it is itself the "origin" of the doc snippet to be displayed. * lisp/emacs-lisp/eldoc.el (eldoc--invoke-strategy): Fix. diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 2108e189fbd..05033f39cba 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -911,8 +911,11 @@ eldoc--invoke-strategy (let* ((eldoc--make-callback #'make-callback) (res (funcall eldoc-documentation-strategy))) ;; Observe the old and the new protocol: - (cond (;; Old protocol: got string, output immediately; - (stringp res) (register-doc 0 res nil) (display-doc)) + (cond (;; Old protocol: got string, e-d-strategy is iself the + ;; origin function, and we output immediately; + (stringp res) + (register-doc 0 res nil eldoc-documentation-strategy) + (display-doc)) (;; Old protocol: got nil, clear the echo area; (null res) (eldoc--message nil)) (;; New protocol: trust callback will be called; commit 4566a0c6b825a18e6c065da0543b8b942b7db8df Author: JD Smith Date: Thu Mar 23 17:43:52 2023 +0000 Eglot: let user opt-in to plaintext LSP docs (bug#61373) * lisp/progmodes/eglot.el (eglot-prefer-plaintext): New variable. (eglot-client-capabilities): Use it. Copyright-paperwork-exempt: yes diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index fae1c8db8b7..806e498c38c 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -391,6 +391,10 @@ eglot-extend-to-xref "If non-nil, activate Eglot in cross-referenced non-project files." :type 'boolean) +(defcustom eglot-prefer-plaintext nil + "If non-nil, always request plaintext responses to hover requests." + :type 'boolean) + (defcustom eglot-menu-string "eglot" "String displayed in mode line when Eglot is active." :type 'string) @@ -776,7 +780,8 @@ eglot-client-capabilities :contextSupport t) :hover (list :dynamicRegistration :json-false :contentFormat - (if (fboundp 'gfm-view-mode) + (if (and (not eglot-prefer-plaintext) + (fboundp 'gfm-view-mode)) ["markdown" "plaintext"] ["plaintext"])) :signatureHelp (list :dynamicRegistration :json-false commit fe8af4eb30b81d34bea3f3d982a9167acc8e19e1 Author: João Távora Date: Thu Mar 23 17:34:46 2023 +0000 Eglot: don't send invisible text to Eldoc Eglot's rendering of Markdown is imperfect and based on gfm-view-mode. To make it slightly better, don't send invisible text in eglot-hover-eldoc-function. * lisp/progmodes/eglot.el (eglot--format-markup): Hide invisible text. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 919f44a2d9a..fae1c8db8b7 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -1653,10 +1653,15 @@ eglot--format-markup (setq-local markdown-fontify-code-blocks-natively t) (insert string) (let ((inhibit-message t) - (message-log-max nil)) - (ignore-errors (delay-mode-hooks (funcall mode)))) - (font-lock-ensure) - (string-trim (buffer-string))))) + (message-log-max nil) + match) + (ignore-errors (delay-mode-hooks (funcall mode))) + (font-lock-ensure) + (goto-char (point-min)) + (while (setq match (text-property-search-forward 'invisible)) + (delete-region (prop-match-beginning match) + (prop-match-end match))) + (string-trim (buffer-string)))))) (define-obsolete-variable-alias 'eglot-ignored-server-capabilites 'eglot-ignored-server-capabilities "1.8") commit 67c1e6e89cea426e77e7a844376a38a4e7b2ec46 Author: João Távora Date: Thu Mar 23 09:04:27 2023 +0000 Eglot: inform ElDoc about overly long 'hover' docs * lisp/progmodes/eglot.el (eglot-hover-eldoc-function): Include :echo info in return value. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index d88647ccbdf..919f44a2d9a 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -3169,7 +3169,8 @@ eglot-hover-eldoc-function (eglot--when-buffer-window buf (let ((info (unless (seq-empty-p contents) (eglot--hover-info contents range)))) - (funcall cb info :buffer t)))) + (funcall cb info + :echo (and info (string-match "\n" info)))))) :deferred :textDocument/hover)) (eglot--highlight-piggyback cb) t)) commit e19994fe8c000b0ed2dbc667cdec26cf54356907 Author: João Távora Date: Thu Mar 23 09:02:18 2023 +0000 ElDoc: rework rendering of echo area (bug#62029) Previously, the display function 'eldoc-display-in-echo-area' reused the same buffer as 'eldoc-display-in-doc-buffer', but that made it harder to render documentation items differently depending on the specific constraints of each display functions. Allow documentation-generating backends to pass an :echo-area property for tweaking the echo area display of certain documentation items. * lisp/emacs-lisp/eldoc.el (eldoc-documentation-functions): Update docstring. (eldoc--doc-buffer-docs): Remove. (eldoc--format-doc-buffer): Simplify. (eldoc--echo-area-render): New helper. (eldoc-display-in-echo-area): Use 'eldoc--echo-area-render'. diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 8fc109dcfef..2108e189fbd 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -437,7 +437,7 @@ eldoc-documentation-functions documentation-displaying frontends. For example, KEY can be: * `:thing', VALUE being a short string or symbol designating what - is being reported on. It can, for example be the name of the + DOCSTRING reports on. It can, for example be the name of the function whose signature is being documented, or the name of the variable whose docstring is being documented. `eldoc-display-in-echo-area', a member of @@ -453,6 +453,11 @@ eldoc-documentation-functions 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, to save space. If VALUE is + `skip' don't echo DOCSTRING. If a number, only echo DOCSTRING + up to that character position. + Finally, major modes should modify this hook locally, for example: (add-hook \\='eldoc-documentation-functions #\\='foo-mode-eldoc nil t) @@ -476,8 +481,6 @@ eldoc-display-functions (defvar eldoc--doc-buffer nil "Buffer displaying latest ElDoc-produced docs.") -(defvar eldoc--doc-buffer-docs nil "Documentation items in `eldoc--doc-buffer'.") - (defun eldoc-doc-buffer (&optional interactive) "Get or display ElDoc documentation buffer. @@ -501,40 +504,56 @@ eldoc--format-doc-buffer eldoc--doc-buffer (setq eldoc--doc-buffer (get-buffer-create " *eldoc*"))) - (unless (eq docs eldoc--doc-buffer-docs) - (setq-local eldoc--doc-buffer-docs docs) - (let ((inhibit-read-only t) - (things-reported-on)) - (special-mode) - (erase-buffer) - (setq-local nobreak-char-display nil) - (cl-loop for (docs . rest) on docs - for (this-doc . plist) = docs - for thing = (plist-get plist :thing) - when thing do - (cl-pushnew thing things-reported-on) - (setq this-doc - (concat - (propertize (format "%s" thing) - 'face (plist-get plist :face)) - ": " - this-doc)) - do (insert this-doc) - when rest do (insert "\n") - finally (goto-char (point-min))) - ;; Rename the buffer, taking into account whether it was - ;; hidden or not - (rename-buffer (format "%s*eldoc%s*" - (if (string-match "^ " (buffer-name)) " " "") - (if things-reported-on - (format " for %s" - (mapconcat - (lambda (s) (format "%s" s)) - things-reported-on - ", ")) - "")))))) + (let ((inhibit-read-only t) + (things-reported-on)) + (special-mode) + (erase-buffer) + (setq-local nobreak-char-display nil) + (cl-loop for (docs . rest) on docs + for (this-doc . plist) = docs + for thing = (plist-get plist :thing) + when thing do + (cl-pushnew thing things-reported-on) + (setq this-doc + (concat + (propertize (format "%s" thing) + 'face (plist-get plist :face)) + ": " + this-doc)) + do (insert this-doc) + when rest do (insert "\n") + finally (goto-char (point-min))) + ;; Rename the buffer, taking into account whether it was + ;; hidden or not + (rename-buffer (format "%s*eldoc%s*" + (if (string-match "^ " (buffer-name)) " " "") + (if things-reported-on + (format " for %s" + (mapconcat + (lambda (s) (format "%s" s)) + things-reported-on + ", ")) + ""))))) eldoc--doc-buffer) +(defun eldoc--echo-area-render (docs) + "Similar to `eldoc--format-doc-buffer', but for echo area. +Helper for `eldoc-display-in-echo-area'." + (cl-loop for (item . rest) on docs + for (this-doc . plist) = item + for echo = (plist-get plist :echo) + for thing = (plist-get plist :thing) + unless (eq echo 'skip) do + (when echo (setq this-doc (substring this-doc 0 echo))) + (when thing (setq this-doc + (concat + (propertize (format "%s" thing) + 'face (plist-get plist :face)) + ": " + this-doc))) + (insert this-doc) + (when rest (insert "\n")))) + (defun eldoc--echo-area-substring (available) "Given AVAILABLE lines, get buffer substring to display in echo area. Helper for `eldoc-display-in-echo-area'." @@ -620,15 +639,15 @@ eldoc-display-in-echo-area single-doc) ((and (numberp available) (cl-plusp available)) - ;; Else, given a positive number of logical lines, we - ;; format the *eldoc* buffer, using as most of its - ;; contents as we know will fit. - (with-current-buffer (eldoc--format-doc-buffer docs) - (save-excursion - (eldoc--echo-area-substring available)))) + ;; Else, given a positive number of logical lines, grab + ;; as many as we can. + (with-temp-buffer + (eldoc--echo-area-render docs) + (eldoc--echo-area-substring available))) (t ;; this is the "truncate brutally" situation (let ((string - (with-current-buffer (eldoc--format-doc-buffer docs) + (with-temp-buffer + (eldoc--echo-area-render docs) (buffer-substring (goto-char (point-min)) (progn (end-of-visible-line) (point)))))) commit 9b18407c7fd91313544acfb3457be5447987e20a Author: João Távora Date: Wed Mar 22 13:35:43 2023 +0000 ElDoc: remember origin backend in doc snippets (bug#62029) This lays groundwork for discriminating between different documentation providers in ElDoc display outlets, i.e. members of eldoc-display-functions * lisp/emacs-lisp/eldoc.el (eldoc--make-callback): Take extra origin arg. (eldoc-documentation-compose-1) (eldoc-documentation-compose-eagerly) (eldoc-documentation-default): Pass extra arg to eglot--make-callback. (eldoc--invoke-strategy): Rework. (eldoc-documentation-functions): Work docstring. diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 74bef264bf1..8fc109dcfef 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -448,6 +448,11 @@ eldoc-documentation-functions `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. + Finally, major modes should modify this hook locally, for example: (add-hook \\='eldoc-documentation-functions #\\='foo-mode-eldoc nil t) @@ -644,8 +649,9 @@ eldoc-display-in-buffer (defun eldoc-documentation-default () "Show the first non-nil documentation string for item at point. This is the default value for `eldoc-documentation-strategy'." - (run-hook-with-args-until-success 'eldoc-documentation-functions - (eldoc--make-callback :patient))) + (run-hook-wrapped 'eldoc-documentation-functions + (lambda (f) + (funcall f (eldoc--make-callback :eager f))))) (defun eldoc--documentation-compose-1 (eagerlyp) "Helper function for composing multiple doc strings. @@ -654,7 +660,8 @@ eldoc--documentation-compose-1 (run-hook-wrapped 'eldoc-documentation-functions (lambda (f) (let* ((callback (eldoc--make-callback - (if eagerlyp :eager :patient))) + (if eagerlyp :eager :patient) + f)) (str (funcall f callback))) (if (or (null str) (stringp str)) (funcall callback str)) nil))) @@ -675,7 +682,7 @@ eldoc-documentation-enthusiast This is meant to be used as a value for `eldoc-documentation-strategy'." (run-hook-wrapped 'eldoc-documentation-functions (lambda (f) - (let* ((callback (eldoc--make-callback :enthusiast)) + (let* ((callback (eldoc--make-callback :enthusiast f)) (str (funcall f callback))) (if (stringp str) (funcall callback str)) nil))) @@ -780,7 +787,7 @@ eldoc--make-callback ;; `eldoc--invoke-strategy' could be moved to ;; `eldoc-documentation-strategy' or thereabouts if/when we decide to ;; extend or publish the `make-callback' protocol. -(defun eldoc--make-callback (method) +(defun eldoc--make-callback (method origin) "Make callback suitable for `eldoc-documentation-functions'. The return value is a function FN whose lambda list is (STRING &rest PLIST) and can be called by those functions. Its @@ -800,8 +807,11 @@ eldoc--make-callback `eldoc-documentation-functions' have been collected; - `:eager' says to display STRING along with all other competing - strings so far, as soon as possible." - (funcall eldoc--make-callback method)) + strings so far, as soon as possible. + +ORIGIN is the member of `eldoc-documentation-functions' which +will be responsible for eventually calling the FN." + (funcall eldoc--make-callback method origin)) (defun eldoc--invoke-strategy (interactive) "Invoke `eldoc-documentation-strategy' function. @@ -838,9 +848,10 @@ eldoc--invoke-strategy (docs-registered '())) (cl-labels ((register-doc - (pos string plist) + (pos string plist origin) (when (and string (> (length string) 0)) - (push (cons pos (cons string plist)) docs-registered))) + (push (cons pos (cons string `(:origin ,origin ,@plist))) + docs-registered))) (display-doc () (run-hook-with-args @@ -850,7 +861,7 @@ eldoc--invoke-strategy (lambda (a b) (< (car a) (car b)))))) interactive)) (make-callback - (method) + (method origin) (let ((pos (prog1 howmany (cl-incf howmany)))) (cl-ecase method (:enthusiast @@ -858,7 +869,7 @@ eldoc--invoke-strategy (when (and string (cl-loop for (p) in docs-registered never (< p pos))) (setq docs-registered '()) - (register-doc pos string plist)) + (register-doc pos string plist origin)) (when (and (timerp eldoc--enthusiasm-curbing-timer) (memq eldoc--enthusiasm-curbing-timer timer-list)) @@ -870,12 +881,12 @@ eldoc--invoke-strategy (:patient (cl-incf want) (lambda (string &rest plist) - (register-doc pos string plist) + (register-doc pos string plist origin) (when (zerop (cl-decf want)) (display-doc)) t)) (:eager (lambda (string &rest plist) - (register-doc pos string plist) + (register-doc pos string plist origin) (display-doc) t)))))) (let* ((eldoc--make-callback #'make-callback) commit 0effdb29d6db4d5d13b09d82db3a681b8501b9d5 Author: João Távora Date: Thu Mar 23 13:13:16 2023 +0000 Eglot: rework progress indicators Show progress indicator in Eglot's mode line by default. * lisp/progmodes/eglot.el (eglot-report-progress): Work docstring. (eglot--mode-line-format): Rework. (eglot-handle-notification $/progress): Rework. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 9fd12d174da..d88647ccbdf 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -396,7 +396,9 @@ eglot-menu-string :type 'string) (defcustom eglot-report-progress t - "If non-nil, show progress of long running LSP server work" + "If non-nil, show progress of long running LSP server work. +If set to `messages', use *Messages* buffer, else use Eglot's +mode line indicator." :type 'boolean :version "29.1") @@ -2040,7 +2042,7 @@ eglot--mode-line-props mouse-face mode-line-highlight)))) (defun eglot--mode-line-format () - "Compose the Eglot's mode-line." + "Compose Eglot's mode-line." (let* ((server (eglot-current-server)) (nick (and server (eglot-project-nickname server))) (pending (and server (hash-table-count @@ -2077,7 +2079,15 @@ eglot--mode-line-format '((mouse-3 eglot-forget-pending-continuations "Forget pending continuations")) "Number of outgoing, \ -still unanswered LSP requests to the server\n")))))))) +still unanswered LSP requests to the server\n"))) + ,@(cl-loop for pr hash-values of (eglot--progress-reporters server) + when (eq (car pr) 'eglot--mode-line-reporter) + append `("/" ,(eglot--mode-line-props + (format "%s%%%%" (or (nth 4 pr) "?")) + 'eglot-mode-line + nil + (format "(%s) %s %s" (nth 1 pr) + (nth 2 pr) (nth 3 pr)))))))))) (add-to-list 'mode-line-misc-info `(eglot--managed-mode (" [" eglot--mode-line-format "] "))) @@ -2167,22 +2177,31 @@ eglot-handle-notification (server (_method (eql $/progress)) &key token value) "Handle $/progress notification identified by TOKEN from SERVER." (when eglot-report-progress - (cl-flet ((fmt (&rest args) (mapconcat #'identity args " "))) + (cl-flet ((fmt (&rest args) (mapconcat #'identity args " ")) + (mkpr (title) + (if (eq eglot-report-progress 'messages) + (make-progress-reporter + (format "[eglot] %s %s: %s" + (eglot-project-nickname server) token title)) + (list 'eglot--mode-line-reporter token title))) + (upd (pcnt msg &optional + (pr (gethash token (eglot--progress-reporters server)))) + (cond + ((eq (car pr) 'eglot--mode-line-reporter) + (setcdr (cddr pr) (list msg pcnt)) + (force-mode-line-update t)) + (pr (progress-reporter-update pr pcnt msg))))) (eglot--dbind ((WorkDoneProgress) kind title percentage message) value (pcase kind ("begin" - (let* ((prefix (format (concat "[eglot] %s %s:" (when percentage " ")) - (eglot-project-nickname server) token)) - (pr (puthash token - (if percentage - (make-progress-reporter prefix 0 100 percentage 1 0) - (make-progress-reporter prefix nil nil nil 1 0)) - (eglot--progress-reporters server)))) - (eglot--reporter-update pr percentage (fmt title message)))) - ("report" - (when-let ((pr (gethash token (eglot--progress-reporters server)))) - (eglot--reporter-update pr percentage (fmt title message)))) - ("end" (remhash token (eglot--progress-reporters server)))))))) + (upd percentage (fmt title message) + (puthash token (mkpr title) + (eglot--progress-reporters server)))) + ("report" (upd percentage message)) + ("end" (upd (or percentage 100) message) + (run-at-time 2 nil + (lambda () + (remhash token (eglot--progress-reporters server)))))))))) (cl-defmethod eglot-handle-notification (_server (_method (eql textDocument/publishDiagnostics)) &key uri diagnostics commit f20fe3e11a96defe69e7cfed801a047be1778b51 Author: João Távora Date: Wed Mar 22 10:16:17 2023 +0000 Eglot: more leniently handle 'window/showMessageRequest' Some servers mess up this message's arguments. * lisp/progmodes/eglot.el (eglot-handle-request): Fix. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 058bcec1489..9fd12d174da 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -2136,13 +2136,14 @@ eglot-handle-notification type message)) (cl-defmethod eglot-handle-request - (_server (_method (eql window/showMessageRequest)) &key type message actions) + (_server (_method (eql window/showMessageRequest)) + &key type message actions &allow-other-keys) "Handle server request window/showMessageRequest." (let* ((actions (append actions nil)) ;; gh#627 (label (completing-read (concat (format (propertize "[eglot] Server reports (type=%s): %s" - 'face (if (<= type 1) 'error)) + 'face (if (or (not type) (<= type 1)) 'error)) type message) "\nChoose an option: ") (or (mapcar (lambda (obj) (plist-get obj :title)) actions) commit b19d040a4fe709b032143d8e569690073befabdf Author: Michael Albinus Date: Thu Mar 23 17:09:52 2023 +0100 New user option remote-file-name-inhibit-auto-save * doc/misc/tramp.texi (Auto-save File Lock and Backup): Mention remote-file-name-inhibit-auto-save. (Frequently Asked Questions): Describe, how to suppress auto-save. * etc/NEWS: Add remote-file-name-inhibit-auto-save. * lisp/simple.el (remote-file-name-inhibit-auto-save): New defcustom. (auto-save-mode): Handle it. (Bug#62260) * lisp/net/tramp-integration.el (tramp-set-connection-local-variables-for-buffer): Declare. (find-file-hook): Add `tramp-set-connection-local-variables-for-buffer'. diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi index 6f14fc875f4..5d6def75f2f 100644 --- a/doc/misc/tramp.texi +++ b/doc/misc/tramp.texi @@ -3190,6 +3190,11 @@ Auto-save File Lock and Backup Alternatively, set the user option @code{tramp-auto-save-directory} to direct all auto saves to that location. +@c Since Emacs 30. +@vindex remote-file-name-inhibit-auto-save +If you want to suppress auto-saving of remote files at all, set user +option @code{remote-file-name-inhibit-auto-save} to non-@code{nil}. + @c Since Emacs 29. @vindex remote-file-name-inhibit-auto-save-visited An alternative to @code{auto-save-mode} is @@ -4906,6 +4911,36 @@ Frequently Asked Questions @code{t} if you know that different Emacs sessions are not modifying the same remote file. +@item +@vindex remote-file-name-inhibit-auto-save +Keep auto-save files local. This is already the default configuration +in Emacs, don't change it. If you want to disable auto-saving for +remote files at all, set @code{remote-file-name-inhibit-auto-save} to +@code{t}, but think about the consequences! + +If you want to disable auto-saving just for selected connections, for +example due to security considerations, use connection-local variables +in order to set @code{buffer-auto-save-file-name}. If you, for +example, want to disable auto-saving for all @option{sudo} +connections, apply the following code. +@ifinfo +@xref{Connection Variables, , , emacs}. +@end ifinfo + +@lisp +@group +(connection-local-set-profile-variables + 'my-auto-save-profile + '((buffer-auto-save-file-name . nil))) +@end group + +@group +(connection-local-set-profiles + '(:application tramp :protocol "sudo") + 'my-auto-save-profile) +@end group +@end lisp + @item Disable excessive traces. Set @code{tramp-verbose} to 3 or lower, default being 3. Increase trace levels temporarily when hunting for @@ -5220,6 +5255,7 @@ Frequently Asked Questions @item Where are remote files trashed to? +@vindex remote-file-name-inhibit-delete-by-moving-to-trash Emacs can trash file instead of deleting @ifinfo them, @ref{Misc File Ops, Trashing , , emacs}. diff --git a/etc/NEWS b/etc/NEWS index 80413c00965..ffce2baf2d0 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -50,6 +50,11 @@ as it has in batch mode since Emacs 24. When non-nil, this option suppresses moving remote files to the local trash when deleting. Default is nil. +--- +** New user option 'remote-file-name-inhibit-auto-save'. +If this user option is non-nil, 'auto-save-mode' will not auto-save +remote buffers. The default is nil. + +++ ** New user option 'yes-or-no-prompt'. This allows the user to customize the prompt that is appended by diff --git a/lisp/net/tramp-integration.el b/lisp/net/tramp-integration.el index 5b3259eab03..d7fcd8afefa 100644 --- a/lisp/net/tramp-integration.el +++ b/lisp/net/tramp-integration.el @@ -42,9 +42,10 @@ (declare-function shortdoc-add-function "shortdoc") (declare-function tramp-dissect-file-name "tramp") (declare-function tramp-file-name-equal-p "tramp") -(declare-function tramp-tramp-file-p "tramp") (declare-function tramp-rename-files "tramp-cmds") (declare-function tramp-rename-these-files "tramp-cmds") +(declare-function tramp-set-connection-local-variables-for-buffer "tramp") +(declare-function tramp-tramp-file-p "tramp") (defvar eshell-path-env) (defvar ido-read-file-name-non-ido) (defvar info-lookup-alist) @@ -549,6 +550,14 @@ tramp-connection-local-darwin-ps-variables '(:application tramp :machine "localhost") local-profile)) +;; Set connection-local variables for buffers visiting a file. + +(add-hook 'find-file-hook #'tramp-set-connection-local-variables-for-buffer -50) +(add-hook 'tramp-unload-hook + (lambda () + (remove-hook + 'find-file-hook #'tramp-set-connection-local-variables-for-buffer))) + (add-hook 'tramp-unload-hook (lambda () (unload-feature 'tramp-integration 'force))) diff --git a/lisp/simple.el b/lisp/simple.el index 80c75d4d7c3..1447c7e53ff 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -9108,6 +9108,13 @@ size-indication-mode "Toggle buffer size display in the mode line (Size Indication mode)." :global t :group 'mode-line) +(defcustom remote-file-name-inhibit-auto-save nil + "When nil, `auto-save-mode' will auto-save remote files. +Any other value means that it will not." + :group 'auto-save + :type 'boolean + :version "30.1") + (define-minor-mode auto-save-mode "Toggle auto-saving in the current buffer (Auto Save mode). @@ -9130,6 +9137,9 @@ auto-save-mode (setq buffer-auto-save-file-name (cond ((null val) nil) + ((and buffer-file-name remote-file-name-inhibit-auto-save + (file-remote-p buffer-file-name)) + nil) ((and buffer-file-name auto-save-visited-file-name (not buffer-read-only)) buffer-file-name) commit 117a29fd1889591d01e1bb5ede4eec9526a482ad Author: Michael Albinus Date: Thu Mar 23 10:21:11 2023 +0100 * test/infra/Dockerfile.emba (emacs-eglot): Install wget. diff --git a/test/infra/Dockerfile.emba b/test/infra/Dockerfile.emba index adca95eb209..5b14384ceb3 100644 --- a/test/infra/Dockerfile.emba +++ b/test/infra/Dockerfile.emba @@ -62,6 +62,11 @@ RUN make bootstrap FROM emacs-base as emacs-eglot +RUN apt-get update && \ + apt-get install -y --no-install-recommends -o=Dpkg::Use-Pty=0 \ + wget \ + && rm -rf /var/lib/apt/lists/* + # We install a recent clangd for Eglot tests. RUN bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" commit 90c0472ed6f9a21ff6e16a846bbec15bc3e077f3 Author: Wilhelm H Kirschbaum Date: Tue Mar 21 12:13:58 2023 +0200 ; Add test for embedded HEEx to elixir-ts-mode-tests * test/lisp/progmodes/elixir-ts-mode-resources/indent.erts: Add test. (Bug#62327) diff --git a/test/lisp/progmodes/elixir-ts-mode-resources/indent.erts b/test/lisp/progmodes/elixir-ts-mode-resources/indent.erts index 748455cc3f2..ea5d9e62240 100644 --- a/test/lisp/progmodes/elixir-ts-mode-resources/indent.erts +++ b/test/lisp/progmodes/elixir-ts-mode-resources/indent.erts @@ -306,3 +306,27 @@ Name: Long tuple {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} =-=-= + +Name: Embedded HEEx + +=-= + defmodule Foo do + def foo(assigns) do +~H""" + +text + +""" + end + end +=-= +defmodule Foo do + def foo(assigns) do + ~H""" + + text + + """ + end +end +=-=-= commit 6731c8827b5aa6e693123951f49b3f735a23ebf2 Author: Eli Zaretskii Date: Thu Mar 23 10:48:31 2023 +0200 ; * lisp/progmodes/eglot.el (eglot-server-programs): Fix last change. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 80d8471f046..058bcec1489 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -221,7 +221,8 @@ eglot-server-programs ((java-mode java-ts-mode) . ("jdtls")) (dart-mode . ("dart" "language-server" "--client-id" "emacs.eglot-dart")) - ((elixir-mode elixir-ts-mode heex-ts-mode) . ("language_server.sh")) + ((elixir-mode elixir-ts-mode heex-ts-mode) + . ("language_server.sh")) (ada-mode . ("ada_language_server")) (scala-mode . ,(eglot-alternatives '("metals" "metals-emacs"))) commit 50a2b34ca709f0c062a65cd4633ab345ff624f0a Author: Wilhelm H Kirschbaum Date: Tue Mar 21 11:48:30 2023 +0200 Add heex-ts-mode to eglot-server-programs (bug#62327) The same language server is used for elixir and heex modes. * lisp/progmodes/eglot.el (eglot-server-programs): Add heex-ts-mode. diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 29883aa9602..80d8471f046 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -221,7 +221,7 @@ eglot-server-programs ((java-mode java-ts-mode) . ("jdtls")) (dart-mode . ("dart" "language-server" "--client-id" "emacs.eglot-dart")) - ((elixir-ts-mode elixir-mode) . ("language_server.sh")) + ((elixir-mode elixir-ts-mode heex-ts-mode) . ("language_server.sh")) (ada-mode . ("ada_language_server")) (scala-mode . ,(eglot-alternatives '("metals" "metals-emacs")))