commit 75a314dc8a9d82449630631f9c41b26d512bdb87 (HEAD, refs/remotes/origin/master) Author: Eli Zaretskii Date: Fri Feb 28 09:35:05 2025 +0200 ; Recommend not to use -O3 in production builds * nt/INSTALL: * INSTALL: Recommend not to use -O3 and -Os in ordinary production builds. (Bug#76559) diff --git a/INSTALL b/INSTALL index 14dea1d4a46..d38fff7b2e6 100644 --- a/INSTALL +++ b/INSTALL @@ -10,7 +10,7 @@ instructions in this file should be sufficient. For other configurations, we have additional specialized files: . INSTALL.REPO if you build from a Git checkout - . nt/INSTALL if you build for MS-Windows + . nt/INSTALL if you build a native (non-Cygwin) Emacs for MS-Windows . nextstep/INSTALL if you build for GNUstep/macOS . java/INSTALL if you build for Android . msdos/INSTALL if you build for MS-DOS @@ -294,12 +294,20 @@ Lisp code even if your system satisfies the build requirements, use the DETAILED BUILDING AND INSTALLATION: -(This is for a Unix or Unix-like system. For GNUstep and macOS, -see nextstep/INSTALL. For non-ancient versions of MS Windows, see -the file nt/INSTALL. For MS-DOS and MS Windows 3.X, see msdos/INSTALL.) +(This is for a Unix or Unix-like system (including Cygwin on +MS-Windows). For GNUstep and macOS, see nextstep/INSTALL. For building +a native Windows Emacs for non-ancient versions of MS Windows, see the +file nt/INSTALL. For MS-DOS and MS Windows 3.X, see msdos/INSTALL.) 1) See BASIC INSTALLATION above for getting and configuring Emacs. +1a) For ordinary production builds, if you want to specify non-default +compiler options via CFLAGS variable, we recommend against using +optimization options -O3 or -Os, and also recommend not to use the +compiler option -fsanitize=undefined. These are known to sometimes cause +problems with the generated code, and we recommend using them only in +debugging builds or for testing specific problems in Emacs. + 2) In the unlikely event that 'configure' does not detect your system type correctly, consult './etc/MACHINES' to see what --host, --build options you should pass to 'configure'. That file also offers hints @@ -534,6 +542,9 @@ to look in '/bar/mylib' for libraries, pass the -Og optimization switch to the compiler, and link against libfoo and libbar libraries in addition to the standard ones. +For ordinary production builds, we recommend against using -O3 and -Os +in CFLAGS, and also against using -fsanitize=undefined compiler option. + For some libraries, like Gtk+, fontconfig and ALSA, 'configure' uses pkg-config to find where those libraries are installed. If you want pkg-config to look in special directories, you have to set diff --git a/nt/INSTALL b/nt/INSTALL index 39936ce53a1..a99e60839e9 100644 --- a/nt/INSTALL +++ b/nt/INSTALL @@ -55,6 +55,10 @@ build should run on Windows 9X and newer systems). ./configure --prefix=/d/usr/emacs --enable-checking='yes,glyphs' \ CFLAGS='-O0 -g3' + (For ordinary production builds, we recommend not to use -O3 or -Os + in CFLAGS, as those could sometimes produce bad or suboptimal code, + especially in major new releases of GCC.) + 3. After the configure script finishes, it should display the resulting configuration. After that, type commit 6ed119d3052ffebd20450ec0c7fb3abf863b3a49 Author: Eli Zaretskii Date: Fri Feb 28 09:12:05 2025 +0200 ; Fix documentation of recent treesit changes * src/treesit.c (Ftreesit_query_capture) (Ftreesit_parser_embed_level, Ftreesit_parser_set_embed_level) (Ftreesit_parser_set_parent_node): * lisp/treesit.el (treesit-query-range) (treesit-query-range-by-language, treesit-range-settings) (treesit-range-rules, treesit--parser-at-level) (treesit--update-ranges-non-local, treesit--update-ranges-local) (treesit--update-range-1): Fix wording and typos in doc strings. * doc/lispref/parsing.texi (Pattern Matching): Fix wording. diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi index 2b224036de0..ade7b819756 100644 --- a/doc/lispref/parsing.texi +++ b/doc/lispref/parsing.texi @@ -1247,9 +1247,8 @@ matching node whose span overlaps with the region between @var{beg} and @var{end} is captured; it doesn't have to be completely contained in the region. -If @var{grouped} is non-@code{nil}, instead of returning a list of -@w{@code{(@var{capture_name} . @var{node})}}, this function returns a -list of list of it. The grouping is determined by @var{query}. +If @var{grouped} is non-@code{nil}, this function returns a grouped list +of lists of captured nodes. The grouping is determined by @var{query}. Captures in the same match of a pattern in @var{query} are grouped together. diff --git a/lisp/treesit.el b/lisp/treesit.el index e05e3526c15..e958b2cad64 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -153,7 +153,7 @@ of max unsigned 32-bit value for byte offsets into buffer text." ;;; Parser API supplement -;; The primary parser will be access frequently (after each re-parse, +;; The primary parser will be accessed frequently (after each re-parse, ;; before redisplay, etc, see ;; `treesit--font-lock-mark-ranges-to-fontify'), so we don't want to ;; allow it to be a callback function which returns the primary parser @@ -559,8 +559,8 @@ respective offset values are added to each (START . END) range being returned. Capture names generally don't matter, but names that starts with an underscore are ignored. -RANGE-FN, if non-nil, is a function that takes a node and OFFSET, and -returns the ranges to use for that node." +RANGE-FN, if non-nil, is a function that takes a NODE and OFFSET, and +returns the ranges to use for that NODE." (let ((offset-left (or (car offset) 0)) (offset-right (or (cdr offset) 0))) (cl-loop for capture @@ -585,8 +585,8 @@ Query NODE with QUERY, the captured nodes generates ranges. Nodes captured by the `@language' capture name are converted to language symbols with LANGUAGE-FN. -RANGE-FN, if non-nil, is a function that takes a node and OFFSET, and -returns the ranges to use for that node. +RANGE-FN, if non-nil, is a function that takes a NODE and OFFSET, and +returns the ranges to use for that NODE. BEG, END, OFFSET are the same as in `treesit-query-range'." (let ((offset-left (or (car offset) 0)) @@ -644,13 +644,13 @@ using a single parser for all the ranges. If OFFSET is non-nil, it should be a cons of numbers (START-OFFSET . END-OFFSET), where the start and end offset are added to each queried range to get the result ranges. -If RANGE-FN is non-nil, it should be a function, Emacs uses this +If RANGE-FN is non-nil, it should be a function; Emacs uses this function to compute the ranges to use for the embedded parser. The function is passed the captured node and OFFSET, and should return a list of ranges, where each range is a cons of the start and end position. -Capture names generally don't matter, but names that starts with +Capture names generally don't matter, but names that start with an underscore are ignored. QUERY can also be a function, in which case it is called with 2 @@ -672,8 +672,8 @@ like this: Each QUERY is a tree-sitter query in either the string, s-expression or compiled form. -Capture names generally don't matter, but names that starts with an -underscore are ignored. And `@language' is reserved. +Capture names generally don't matter, but names that start with an +underscore are ignored. The `@language' capture name is reserved. For each QUERY, :KEYWORD and VALUE pairs add meta information to it. For example, @@ -690,8 +690,8 @@ this way: Emacs queries QUERY in the host language's parser, computes the ranges spanned by the captured nodes, and applies these ranges to parsers for the embedded language. -If the embed language is dynamic, then a function in place of the embed -language symbol. This function will by passed a node and should return +If the embed language is dynamic, then `:embed' can specify a +function. This function will by passed a node and should return the language symbol for the embedded code block. The node is the one captured from QUERY with capture name `@language'. Also make sure the code block and language capture are in the same match group. @@ -896,10 +896,10 @@ it." (delete-overlay ov))))) (defsubst treesit--parser-at-level (parsers level &optional include-null) - "Filter for parsers in PARSERS that has embed level equal to LEVEL. + "Filter for parsers in PARSERS that have embed level equal to LEVEL. -If INCLUDE-NULL is non-nil, also include parsers that has a nil embed -level." +If INCLUDE-NULL is non-nil, also include parsers whose embed level +is nil." (seq-filter (lambda (parser) (or (eq (treesit-parser-embed-level parser) level) (and include-null @@ -918,7 +918,7 @@ Use QUERY to get the ranges, and set ranges for embedded parsers to those ranges. HOST-PARSER and QUERY must match. EMBED-LANG is either a language symbol or a function that takes a node -and return a language symbol. +and returns a language symbol. EMBED-LEVEL is the embed level for the local parsers being created or updated. When looking for existing local parsers, only look for parsers @@ -928,7 +928,7 @@ level. RANGE-FN, if non-nil, is a function that takes a node and OFFSET, and returns the ranges to use for that node. -Return updated parsers in a list." +Return updated parsers as a list." (let ((ranges-by-lang (if (functionp embed-lang) (treesit-query-range-by-language @@ -998,7 +998,7 @@ for the local parser. RANGE-FN, if non-nil, is a function that takes a node and OFFSET, and returns the ranges to use for that node. -Return the created local parsers in a list." +Return the created local parsers as a list." ;; Update range. (let ((ranges-by-lang (if (functionp embedded-lang) @@ -1050,8 +1050,8 @@ Return the created local parsers in a list." "Given a HOST-PARSER, update ranges between BEG and END. Go over each settings in SETTINGS, try to create or update the embedded -language in that setting. Return the created or updated embedded -language parsers in a list. +language in that setting. Return the list of the created or updated +embedded language parsers. EMBED-LEVEL is the embed level for the embedded parser being created or updated. When looking for existing embedded parsers, only look for diff --git a/src/treesit.c b/src/treesit.c index c8af17a5b8b..20c3626732a 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -1827,8 +1827,8 @@ DEFUN ("treesit-parser-embed-level", The embed level can be either nil or a non-negative integer. A value of nil means the parser isn't part of the embedded parser tree. The -primary parser has embed level 0, from it, each layer of embedded parser -has +1 embed level. */) +primary parser has embed level 0, and each additional layer of parser +embedding increments the embed level by 1. */) (Lisp_Object parser) { treesit_check_parser (parser); @@ -1839,7 +1839,9 @@ has +1 embed level. */) DEFUN ("treesit-parser-set-embed-level", Ftreesit_parser_set_embed_level, Streesit_parser_set_embed_level, 2, 2, 0, - doc: /* Set the embed level for PARSER to LEVEL. */) + doc: /* Set the embed level for PARSER to LEVEL. +LEVEL can be nil, for a parser that is not part of an embedded parser +tree; otherwise it must be a non-negative integer. */) (Lisp_Object parser, Lisp_Object level) { treesit_check_parser (parser); @@ -1859,7 +1861,7 @@ DEFUN ("treesit-parser-parent-node", 1, 1, 0, doc: /* Return PARSER's parent node, if one exists. -Only embeded local parser can have parent node. When Emacs uses a node +Only embeded local parsers can have parent node. When Emacs uses a node in the host parser to create this local parser, that node is considered the parent node of the local parser. */) (Lisp_Object parser) @@ -1871,7 +1873,7 @@ the parent node of the local parser. */) DEFUN ("treesit-parser-set-parent-node", Ftreesit_parser_set_parent_node, Streesit_parser_set_parent_node, 2, 2, 0, - doc: /* Return PARSER's parent node to NODE. */) + doc: /* Make NODE be the parent node of PARSER. */) (Lisp_Object parser, Lisp_Object node) { treesit_check_parser (parser); @@ -3289,8 +3291,9 @@ in which the query is executed. Any matching node whose span overlaps with the region between BEG and END are captured, it doesn't have to be completely in the region. -If GROUPED is non-nil, group captures into matches and return a list of -MATCH, where each MATH is a list of (CAPTURE_NAME . NODE). +If GROUPED is non-nil, ther function groups the returned list of +captures into matches and return a list of MATCH, where each MATCH is +a list of the form (CAPTURE_NAME . NODE). If NODE-ONLY is non-nil, return nodes only, and don't include CAPTURE_NAME. diff --git a/src/treesit.h b/src/treesit.h index d19a0e76216..d4cfc47ee5d 100644 --- a/src/treesit.h +++ b/src/treesit.h @@ -64,12 +64,13 @@ struct Lisp_TS_Parser = 0, end_byte = UINT32_MAX). */ Lisp_Object last_set_ranges; /* Parsers for embedded code blocks will have a non-zero embed level. - The primary parser has level 0, and each layer of embedded parser - gets +1 level. The embed level can be either a non-negative - integer or nil. Every parser created by treesit-parser-create - starts with a nil level. If the value is nil, that means the range - functions (treesit-update-ranges and friends) haven't touched this - parser yet, and this parser isn't part of the embed parser tree. */ + The primary parser has level 0, and each additional layer of parser + embedding increments the leve by 1. The embed level can be either + a non-negative integer or nil. Every parser created by + 'treesit-parser-create' starts with a nil level. If the value is + nil, that means the range functions (treesit-update-ranges and + friends) haven't touched this parser yet, and this parser isn't + part of the embed parser tree. */ Lisp_Object embed_level; /* Some comments: Technically you could calculate embed_level by following parent_node, but parent_node might be outdated so it's a @@ -80,7 +81,7 @@ struct Lisp_TS_Parser for a parser will be useful beyond this. And we can always convert these to properties later, but not vice versa. */ /* When an embedded parser is created, it's usually based on a node in - the host parser. This field saves that node so it possible to + the host parser. This field saves that node so it's possible to climb up and out of the embedded parser into the host parser. Note that the range of the embedded parser doesn't have to match that of the parent node. */ commit becdfb11a5b13bc773729236763e99e513806451 Author: Stefan Kangas Date: Fri Feb 28 03:02:15 2025 +0100 Remove Emacs 23 compat code from ox-publish.el * lisp/org/ox-publish.el (org-publish-timestamp-filename): Remove Emacs 23 compat code. diff --git a/lisp/org/ox-publish.el b/lisp/org/ox-publish.el index bb9e0bd2e30..f324f0d881f 100644 --- a/lisp/org/ox-publish.el +++ b/lisp/org/ox-publish.el @@ -365,7 +365,7 @@ The timestamp file name is constructed using FILENAME, publishing directory PUB-DIR, and PUB-FUNC publishing function." (setq filename (concat filename "::" (or pub-dir "") "::" (format "%s" (or pub-func "")))) - (concat "X" (if (fboundp 'sha1) (sha1 filename) (md5 filename)))) + (concat "X" (sha1 filename))) (defun org-publish-needed-p (filename &optional pub-dir pub-func _true-pub-dir base-dir) commit 64edd5a5eb832c2ca9f4495c17e880a86179f826 Author: Stefan Kangas Date: Fri Feb 28 02:20:40 2025 +0100 Don't use incf/decf in eldoc.el * lisp/emacs-lisp/eldoc.el (eldoc--invoke-strategy): Don't use incf or decf. This is a :core package and supports Emacs 26.3. (Bug#76604) diff --git a/lisp/emacs-lisp/eldoc.el b/lisp/emacs-lisp/eldoc.el index 3e701076ef3..81890268dd7 100644 --- a/lisp/emacs-lisp/eldoc.el +++ b/lisp/emacs-lisp/eldoc.el @@ -903,7 +903,7 @@ the docstrings eventually produced, using interactive)) (make-callback (method origin) - (let ((pos (prog1 howmany (incf howmany)))) + (let ((pos (prog1 howmany (cl-incf howmany)))) (cl-ecase method (:enthusiast (lambda (string &rest plist) @@ -920,10 +920,10 @@ the docstrings eventually produced, using nil #'display-doc)) t)) (:patient - (incf want) + (cl-incf want) (lambda (string &rest plist) (register-doc pos string plist origin) - (when (zerop (decf want)) (display-doc)) + (when (zerop (cl-decf want)) (display-doc)) t)) (:eager (lambda (string &rest plist) commit 4ea2197f726835951ea55e04b88d212778676da1 Author: Stefan Kangas Date: Fri Feb 28 00:33:00 2025 +0100 Add test for apply-partially * test/lisp/subr-tests.el (subr-test-apply-partially): New test. diff --git a/test/lisp/subr-tests.el b/test/lisp/subr-tests.el index 702502627f2..b21f90226c6 100644 --- a/test/lisp/subr-tests.el +++ b/test/lisp/subr-tests.el @@ -30,6 +30,19 @@ (require 'ert-x) (eval-when-compile (require 'cl-lib)) +(ert-deftest subr-test-apply-partially () + (should (functionp (apply-partially #'identity))) + (should (functionp (apply-partially #'list 1 2 3))) + (should (equal (mapcar (apply-partially #'identity) '(9 cups of sugar)) + '(9 cups of sugar))) + (should (equal (mapcar (apply-partially #'eq 3) '(3 spoons of butter)) + '(t nil nil nil))) + (should (equal (funcall (apply-partially #'list 1 2 3) 4) + '(1 2 3 4))) + (let* ((a 1) (b 2) (c 3) + (fun (apply-partially #'list a b c))) + (should (equal (funcall fun 4) '(1 2 3 4))))) + (ert-deftest subr-test-zerop () (should (zerop 0)) (should (zerop 0.0)) commit 8a3e19f4b39be68c22e056d56adb86397e25a673 Author: Yuan Fu Date: Thu Feb 27 17:18:28 2025 -0800 Support alternative range function for tree-sitter range settings Some embedded parser needs to exclude child nodes from the range, like markdown-inline. So I added this keyword that allows users to customize the range for the embedded parser. This can also be potentially useful for markdown comments in rust, for example, because we want to exclude the comment starters (//) from the embedded markdown parser. * lisp/treesit.el (treesit-query-range): (treesit-query-range-by-language): Add new parameter RANGE-FN. (treesit-range-settings): Add new field RANGE-FN. (treesit-range-rules): Add new keyword RANGE-FN. (treesit-range-fn-exclude-children): New function. (treesit--update-ranges-non-local): (treesit--update-ranges-local): (treesit--update-range-1): Support the RANGE-FN field. diff --git a/lisp/treesit.el b/lisp/treesit.el index 8374ce936de..e05e3526c15 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -548,7 +548,7 @@ See `treesit-query-capture' for QUERY." (treesit-parser-root-node parser) query)))) -(defun treesit-query-range (node query &optional beg end offset) +(defun treesit-query-range (node query &optional beg end offset range-fn) "Query the current buffer and return ranges of captured nodes. QUERY, NODE, BEG, END are the same as in `treesit-query-capture'. @@ -557,7 +557,10 @@ END specifics the range of each captured node. OFFSET is an optional pair of numbers (START-OFFSET . END-OFFSET). The respective offset values are added to each (START . END) range being returned. Capture names generally don't matter, but names -that starts with an underscore are ignored." +that starts with an underscore are ignored. + +RANGE-FN, if non-nil, is a function that takes a node and OFFSET, and +returns the ranges to use for that node." (let ((offset-left (or (car offset) 0)) (offset-right (or (cdr offset) 0))) (cl-loop for capture @@ -565,11 +568,14 @@ that starts with an underscore are ignored." for name = (car capture) for node = (cdr capture) if (not (string-prefix-p "_" (symbol-name name))) - collect (cons (+ (treesit-node-start node) offset-left) - (+ (treesit-node-end node) offset-right))))) + append + (if range-fn + (funcall range-fn node offset) + (list (cons (+ (treesit-node-start node) offset-left) + (+ (treesit-node-end node) offset-right))))))) (defun treesit-query-range-by-language - (node query language-fn &optional beg end offset) + (node query language-fn &optional beg end offset range-fn) "Like `treesit-query-range', but return multiple ranges by language. Return an alist of the form ((LANGUAGE . RANGES) ...), containing @@ -579,6 +585,9 @@ Query NODE with QUERY, the captured nodes generates ranges. Nodes captured by the `@language' capture name are converted to language symbols with LANGUAGE-FN. +RANGE-FN, if non-nil, is a function that takes a node and OFFSET, and +returns the ranges to use for that node. + BEG, END, OFFSET are the same as in `treesit-query-range'." (let ((offset-left (or (car offset) 0)) (offset-right (or (cdr offset) 0)) @@ -591,12 +600,14 @@ BEG, END, OFFSET are the same as in `treesit-query-range'." (node (cdr capture))) (when (and (not (equal (symbol-name name) "language")) (not (string-prefix-p "_" (symbol-name name)))) - (push (cons (+ (treesit-node-start node) offset-left) - (+ (treesit-node-end node) offset-right)) + (push (if range-fn + (funcall range-fn node offset) + (list (cons (+ (treesit-node-start node) offset-left) + (+ (treesit-node-end node) offset-right)))) (alist-get lang ranges-by-language))))))) (mapcar (lambda (entry) (cons (car entry) - (nreverse (cdr entry)))) + (apply #'append (nreverse (cdr entry))))) ranges-by-language))) (defun treesit-query-valid-p (language query) @@ -621,15 +632,23 @@ If none are valid, return nil." (defvar-local treesit-range-settings nil "A list of range settings. -Each element of the list is of the form (QUERY LANGUAGE LOCAL-P -OFFSET). When updating the range of each parser in the buffer, -`treesit-update-ranges' queries each QUERY, and sets LANGUAGE's -range to the range spanned by captured nodes. QUERY must be a -compiled query. If LOCAL-P is t, give each range a separate -local parser rather than using a single parser for all the -ranges. If OFFSET is non-nil, it should be a cons of -numbers (START-OFFSET . END-OFFSET), where the start and end -offset are added to each queried range to get the result ranges. +Each element of the list is of the form + + (QUERY LANGUAGE LOCAL-P OFFSET RANGE-FN) + +When updating the range of each parser in the buffer, +`treesit-update-ranges' queries each QUERY, and sets LANGUAGE's range to +the range spanned by captured nodes. QUERY must be a compiled query. +If LOCAL-P is t, give each range a separate local parser rather than +using a single parser for all the ranges. If OFFSET is non-nil, it +should be a cons of numbers (START-OFFSET . END-OFFSET), where the start +and end offset are added to each queried range to get the result ranges. + +If RANGE-FN is non-nil, it should be a function, Emacs uses this +function to compute the ranges to use for the embedded parser. The +function is passed the captured node and OFFSET, and should return a +list of ranges, where each range is a cons of the start and end +position. Capture names generally don't matter, but names that starts with an underscore are ignored. @@ -688,13 +707,19 @@ be (3 . 7). This can be used to exclude things like surrounding delimiters from being included in the range covered by an embedded parser. +If there's a `:range-fn' keyword with a function, Emacs uses that +function to compute the ranges to use for the embedded parser. The +function is passed the captured node and the offset given by the +`:offset' keyword, and should return a list of ranges, where each range +is a cons of the start and end position. + QUERY can also be a function that takes two arguments, START and END. If QUERY is a function, it doesn't need the :KEYWORD VALUE pair preceding it. This function should set the ranges for parsers in the current buffer in the region between START and END. It is OK for this function to set ranges in a larger region that encompasses the region between START and END." - (let (host embed offset result local) + (let (host embed offset result local range-fn) (while query-specs (pcase (pop query-specs) (:local (when (eq t (pop query-specs)) @@ -713,6 +738,10 @@ that encompasses the region between START and END." (numberp (cdr range-offset))) (signal 'treesit-error (list "Value of :offset option should be a pair of numbers" range-offset))) (setq offset range-offset))) + (:range-fn (let ((range-fn (pop query-specs))) + (unless (functionp range-fn) + (signal 'treesit-error (list "Value of :range-fn option should be a function" range-fn))) + (setq range-fn range-fn))) (query (if (functionp query) (push (list query nil nil) result) (when (null embed) @@ -720,11 +749,29 @@ that encompasses the region between START and END." (when (null host) (signal 'treesit-error (list "Value of :host option cannot be omitted"))) (push (list (treesit-query-compile host query) - embed local offset) + embed local offset range-fn) result)) - (setq host nil embed nil offset nil local nil)))) + (setq host nil embed nil offset nil local nil range-fn nil)))) (nreverse result))) +(defun treesit-range-fn-exclude-children (node offset) + "Return ranges spanned by NODE but excluding its children. + +OFFSET is added to the start and end of the overall range. + +This can be used as a `:range-fn' in `treesit-range-rules'." + (let* ((start (+ (treesit-node-start node) (or (car offset) 0))) + (end (+ (treesit-node-end node) (or (cdr offset) 0))) + (prev-end start) + (ranges nil)) + (dolist (child (treesit-node-children node)) + (let ((child-start (treesit-node-start child)) + (child-end (treesit-node-end child))) + (push (cons prev-end child-start) ranges) + (setq prev-end child-end))) + (push (cons prev-end end) ranges) + (nreverse ranges))) + (defun treesit--merge-ranges (old-ranges new-ranges start end) "Merge OLD-RANGES and NEW-RANGES, discarding ranges between START and END. OLD-RANGES and NEW-RANGES are lists of cons of the form (BEG . END). @@ -861,7 +908,7 @@ level." (defun treesit--update-ranges-non-local ( host-parser query embed-lang embed-level - &optional beg end offset) + &optional beg end offset range-fn) "Update range for non-local parsers between BEG and END under HOST-PARSER. OFFSET is a cons (OFFSET-START . OFFSET-END), the start and end will be @@ -878,22 +925,31 @@ updated. When looking for existing local parsers, only look for parsers of this level; when creating new local parsers, set their level to this level. -Return updated parsers." +RANGE-FN, if non-nil, is a function that takes a node and OFFSET, and +returns the ranges to use for that node. + +Return updated parsers in a list." (let ((ranges-by-lang (if (functionp embed-lang) (treesit-query-range-by-language - host-parser query embed-lang beg end offset) + host-parser query embed-lang beg end offset range-fn) (list (cons embed-lang (treesit-query-range - host-parser query beg end offset))))) + host-parser query beg end offset range-fn))))) (touched-parsers nil)) (dolist (lang-and-ranges ranges-by-lang) (let* ((resolved-embed-lang (car lang-and-ranges)) (new-ranges (cdr lang-and-ranges)) (embed-parser - (car (treesit--parser-at-level - (treesit-parser-list nil resolved-embed-lang) - embed-level 'include-null)))) + ;; Prefer embed parser with the right level, but if none + ;; exists, ones that doesn't have a embed level are ok + ;; too. + (or (car (treesit--parser-at-level + (treesit-parser-list nil resolved-embed-lang) + embed-level)) + (car (treesit--parser-at-level + (treesit-parser-list nil resolved-embed-lang) + embed-level 'include-null))))) (when embed-parser (let* ((old-ranges (treesit-parser-included-ranges embed-parser)) @@ -917,7 +973,7 @@ Return updated parsers." (defun treesit--update-ranges-local ( host-parser query embedded-lang modified-tick embed-level - &optional beg end) + &optional beg end range-fn) "Update range for local parsers between BEG and END under HOST-PARSER. Use QUERY to get the ranges, and make sure each range has a local parser for EMBEDDED-LANG. HOST-PARSER and QUERY must match. @@ -936,14 +992,21 @@ updated. When looking for existing local parsers, only look for parsers of this level; when creating new local parsers, set their level to this level. +OFFSET is a cons of start and end offsets that are applied to the range +for the local parser. + +RANGE-FN, if non-nil, is a function that takes a node and OFFSET, and +returns the ranges to use for that node. + Return the created local parsers in a list." ;; Update range. (let ((ranges-by-lang (if (functionp embedded-lang) (treesit-query-range-by-language - host-parser query embedded-lang beg end) + host-parser query embedded-lang beg end range-fn) (list (cons embedded-lang - (treesit-query-range host-parser query beg end))))) + (treesit-query-range + host-parser query beg end range-fn))))) (touched-parsers nil)) (dolist (lang-and-range ranges-by-lang) (let ((embedded-lang (car lang-and-range)) @@ -1001,7 +1064,8 @@ this level." (query-lang (treesit-query-language query)) (embed-lang (nth 1 setting)) (local (nth 2 setting)) - (offset (nth 3 setting))) + (offset (nth 3 setting)) + (range-fn (nth 4 setting))) (when (eq query-lang (treesit-parser-language host-parser)) (cond ((functionp query) (funcall query beg end)) @@ -1010,7 +1074,7 @@ this level." (append touched-parsers (treesit--update-ranges-local host-parser query embed-lang modified-tick - embed-level beg end)))) + embed-level beg end range-fn)))) ;; When updating ranges, we want to avoid querying the whole ;; buffer which could be slow in very large buffers. ;; Instead, we only query for nodes that intersect with the @@ -1021,7 +1085,7 @@ this level." (append touched-parsers (treesit--update-ranges-non-local host-parser query embed-lang embed-level - beg end offset)))))))) + beg end offset range-fn)))))))) touched-parsers)) (defun treesit-update-ranges (&optional beg end) commit 8a45c2da226e188420956fd6269f72db3f437e38 Author: Yuan Fu Date: Thu Feb 27 04:06:36 2025 -0800 Make treesit-node-at take advantage of the embed-level property * lisp/treesit.el (treesit-node-at): Select the local parser with the highest embed-level. diff --git a/lisp/treesit.el b/lisp/treesit.el index 5a067575e65..8374ce936de 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -258,10 +258,20 @@ language and doesn't match the language of the local parser." ;; finding parser, try local parser first, then global ;; parser. (t - ;; LANG can be nil. - (let* ((lang (treesit-language-at pos)) - (local-parser (car (treesit-local-parsers-at - pos lang))) + ;; LANG can be nil. We don't want to use the fallback + ;; in `treesit-language-at', so here we call + ;; `treesit-language-at-point-function' directly. + (let* ((lang (and treesit-language-at-point-function + (funcall treesit-language-at-point-function + pos))) + (local-parser + ;; Find the local parser with highest + ;; embed-level at point. + (car (seq-sort-by #'treesit-parser-embed-level + (lambda (a b) + (> (or a 0) (or b 0))) + (treesit-local-parsers-at + pos lang)))) (global-parser (car (treesit-parser-list nil lang))) (parser (or local-parser global-parser))) commit 1314272bf398e068385572ca16d4dfcd55a48828 Author: Yuan Fu Date: Thu Feb 27 03:10:47 2025 -0800 Rework range facility in treesit.el to support arbitrary nesting Make use of the newly added embed-level parser property, we now iterativeLy create/update embedded parsers at each embed level, allowing arbitrary levels of nesting. At the beginning, we start with the primary parser. We query it with each range settings, gets some ranges and their corresponding language. Then we create the first level of embedded parsers using the ranges and language we got. This is one iteration. For the next iteration/level, we start with the embedded parsers we create/updated in the previous iteration/level, and query each of them with each of the range settings, creating/updating the next level of embedded parsers. And we keep doing this until we don't get more matches from the queries. We now also support the :embed keyword in 'treesit-range-rules' to be a function that returns a language. This allows major modes like markdown and org mode to support code blocks of which the language isn't known ahead of time. * lisp/treesit.el (treesit-primary-parser): Move to front. (treesit-query-range-by-language): New function. (treesit-range-rules): Allow :embed to be a function, update docstring. (treesit-local-parsers-on): Update docstring. (treesit--parser-at-level): (treesit--update-ranges-non-local): New functions. (treesit--update-ranges-local): Use the new logic, support :embed being a function. (treesit--update-range-1): New function, has the meat of original treesit-update-ranges. (treesit-update-ranges): Implements the iteration logic using treesit--update-range-1 to do the actual work. diff --git a/lisp/treesit.el b/lisp/treesit.el index e96aa4cb8f5..5a067575e65 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -153,6 +153,18 @@ of max unsigned 32-bit value for byte offsets into buffer text." ;;; Parser API supplement +;; The primary parser will be access frequently (after each re-parse, +;; before redisplay, etc, see +;; `treesit--font-lock-mark-ranges-to-fontify'), so we don't want to +;; allow it to be a callback function which returns the primary parser +;; (it might be slow). It's not something that needs to be dynamic +;; anyway. +(defvar-local treesit-primary-parser nil + "The primary parser for this buffer. + +The primary parser should be a parser that parses the entire buffer, as +opposed to embedded parsers which parses only part of the buffer.") + (defvar-local treesit-language-at-point-function nil "A function that returns the language at point. This is used by `treesit-language-at', which is used by various @@ -546,6 +558,37 @@ that starts with an underscore are ignored." collect (cons (+ (treesit-node-start node) offset-left) (+ (treesit-node-end node) offset-right))))) +(defun treesit-query-range-by-language + (node query language-fn &optional beg end offset) + "Like `treesit-query-range', but return multiple ranges by language. + +Return an alist of the form ((LANGUAGE . RANGES) ...), containing +separate ranges for each language detected in the query. + +Query NODE with QUERY, the captured nodes generates ranges. Nodes +captured by the `@language' capture name are converted to language +symbols with LANGUAGE-FN. + +BEG, END, OFFSET are the same as in `treesit-query-range'." + (let ((offset-left (or (car offset) 0)) + (offset-right (or (cdr offset) 0)) + (ranges-by-language nil)) + (dolist (match-group (treesit-query-capture node query beg end nil t)) + (let* ((lang-node (alist-get 'language match-group)) + (lang (funcall language-fn lang-node))) + (dolist (capture match-group) + (let ((name (car capture)) + (node (cdr capture))) + (when (and (not (equal (symbol-name name) "language")) + (not (string-prefix-p "_" (symbol-name name)))) + (push (cons (+ (treesit-node-start node) offset-left) + (+ (treesit-node-end node) offset-right)) + (alist-get lang ranges-by-language))))))) + (mapcar (lambda (entry) + (cons (car entry) + (nreverse (cdr entry)))) + ranges-by-language))) + (defun treesit-query-valid-p (language query) "Return non-nil if QUERY is valid in LANGUAGE, nil otherwise." (ignore-errors @@ -600,8 +643,8 @@ like this: Each QUERY is a tree-sitter query in either the string, s-expression or compiled form. -Capture names generally don't matter, but names that starts with -an underscore are ignored. +Capture names generally don't matter, but names that starts with an +underscore are ignored. And `@language' is reserved. For each QUERY, :KEYWORD and VALUE pairs add meta information to it. For example, @@ -610,7 +653,7 @@ it. For example, :embed \\='javascript :host \\='html :offset \\='(1 . -1) - \\='((script_element (raw_text) @cap))) + \\='((script_element (raw_text) @javascript))) The `:embed' keyword specifies the embedded language, and the `:host' keyword specifies the host language. They are used in @@ -618,6 +661,12 @@ this way: Emacs queries QUERY in the host language's parser, computes the ranges spanned by the captured nodes, and applies these ranges to parsers for the embedded language. +If the embed language is dynamic, then a function in place of the embed +language symbol. This function will by passed a node and should return +the language symbol for the embedded code block. The node is the one +captured from QUERY with capture name `@language'. Also make sure the +code block and language capture are in the same match group. + If there's a `:local' keyword with value t, the range computed by this QUERY is given a dedicated local parser. Otherwise, the range shares the same parser with other ranges. @@ -645,8 +694,8 @@ that encompasses the region between START and END." (signal 'treesit-error (list "Value of :host option should be a symbol" host-lang))) (setq host host-lang))) (:embed (let ((embed-lang (pop query-specs))) - (unless (symbolp embed-lang) - (signal 'treesit-error (list "Value of :embed option should be a symbol" embed-lang))) + (unless (or (symbolp embed-lang) (functionp embed-lang)) + (signal 'treesit-error (list "Value of :embed option should be a symbol or a function" embed-lang))) (setq embed embed-lang))) (:offset (let ((range-offset (pop query-specs))) (unless (and (consp range-offset) @@ -753,12 +802,13 @@ PARSER." (nreverse res))) (defun treesit-local-parsers-on (&optional beg end language with-host) - "Return all the local parsers between BEG END. + "Return all the local parsers between BEG and END. -BEG and END default to the beginning and end of the buffer's -accessible portion. -Local parsers are those which have an `embedded' tag, and only parse -a limited region marked by an overlay with a non-nil `treesit-parser' +BEG and END default to the beginning and end of the buffer's accessible +portion. + +Local parsers are those that have an `embedded' tag, and only parse a +limited region marked by an overlay with a non-nil `treesit-parser' property. If LANGUAGE is non-nil, only return parsers for LANGUAGE. If WITH-HOST is non-nil, return a list of (PARSER . HOST-PARSER) @@ -788,52 +838,181 @@ it." (treesit-parser-delete local-parser)) (delete-overlay ov))))) +(defsubst treesit--parser-at-level (parsers level &optional include-null) + "Filter for parsers in PARSERS that has embed level equal to LEVEL. + +If INCLUDE-NULL is non-nil, also include parsers that has a nil embed +level." + (seq-filter (lambda (parser) + (or (eq (treesit-parser-embed-level parser) level) + (and include-null + (null (treesit-parser-embed-level parser))))) + parsers)) + +(defun treesit--update-ranges-non-local + ( host-parser query embed-lang embed-level + &optional beg end offset) + "Update range for non-local parsers between BEG and END under HOST-PARSER. + +OFFSET is a cons (OFFSET-START . OFFSET-END), the start and end will be +added to each captured range as range offset. + +Use QUERY to get the ranges, and set ranges for embedded parsers to +those ranges. HOST-PARSER and QUERY must match. + +EMBED-LANG is either a language symbol or a function that takes a node +and return a language symbol. + +EMBED-LEVEL is the embed level for the local parsers being created or +updated. When looking for existing local parsers, only look for parsers +of this level; when creating new local parsers, set their level to this +level. + +Return updated parsers." + (let ((ranges-by-lang + (if (functionp embed-lang) + (treesit-query-range-by-language + host-parser query embed-lang beg end offset) + (list (cons embed-lang + (treesit-query-range + host-parser query beg end offset))))) + (touched-parsers nil)) + (dolist (lang-and-ranges ranges-by-lang) + (let* ((resolved-embed-lang (car lang-and-ranges)) + (new-ranges (cdr lang-and-ranges)) + (embed-parser + (car (treesit--parser-at-level + (treesit-parser-list nil resolved-embed-lang) + embed-level 'include-null)))) + (when embed-parser + (let* ((old-ranges (treesit-parser-included-ranges + embed-parser)) + (set-ranges (treesit--clip-ranges + (treesit--merge-ranges + old-ranges new-ranges beg end) + (point-min) (point-max)))) + (treesit-parser-set-embed-level + embed-parser embed-level) + (treesit-parser-set-included-ranges + embed-parser (or set-ranges + ;; When there's no range for the + ;; embedded language, set it's + ;; range to a dummy (1 . 1), + ;; otherwise it would be set to + ;; the whole buffer, which is + ;; not what we want. + `((,(point-min) . ,(point-min))))) + (push embed-parser touched-parsers))))) + touched-parsers)) + (defun treesit--update-ranges-local - (query embedded-lang modified-tick &optional beg end) - "Update range for local parsers between BEG and END. + ( host-parser query embedded-lang modified-tick embed-level + &optional beg end) + "Update range for local parsers between BEG and END under HOST-PARSER. Use QUERY to get the ranges, and make sure each range has a local -parser for EMBEDDED-LANG. +parser for EMBEDDED-LANG. HOST-PARSER and QUERY must match. The local parser is stored in an overlay, in the `treesit-parser' property, the host parser is stored in the `treesit-host-parser' property. When this function touches an overlay, it sets the -`treesit-parser-ov-timestamp' property of the overlay to -MODIFIED-TICK. This will help Emacs garbage-collect overlays that -aren't in use anymore." +`treesit-parser-ov-timestamp' property of the overlay to MODIFIED-TICK. +This will help Emacs garbage-collect overlays that aren't in use +anymore. + +EMBED-LEVEL is the embed level for the local parsers being created or +updated. When looking for existing local parsers, only look for parsers +of this level; when creating new local parsers, set their level to this +level. + +Return the created local parsers in a list." ;; Update range. - (let* ((host-lang (treesit-query-language query)) - (host-parser (car (treesit-parser-list nil host-lang))) - (ranges (treesit-query-range host-parser query beg end))) - (pcase-dolist (`(,beg . ,end) ranges) - (let ((has-parser nil)) - (setq - has-parser - (catch 'done - (dolist (ov (overlays-in beg end) nil) - ;; Update range of local parser. - (when-let* ((embedded-parser (overlay-get ov 'treesit-parser)) - (parser-lang (treesit-parser-language - embedded-parser))) - (when (eq parser-lang embedded-lang) - (treesit-parser-set-included-ranges - embedded-parser `((,beg . ,end))) - (move-overlay ov beg end) - (overlay-put ov 'treesit-parser-ov-timestamp - modified-tick) - (throw 'done t)))))) - ;; Create overlay and local parser. - (when (not has-parser) - (let ((embedded-parser (treesit-parser-create - embedded-lang nil t 'embedded)) - (ov (make-overlay beg end nil nil t))) - (overlay-put ov 'treesit-parser embedded-parser) - (overlay-put ov 'treesit-host-parser host-parser) - (overlay-put ov 'treesit-parser-ov-timestamp - modified-tick) - (treesit-parser-set-included-ranges - embedded-parser `((,beg . ,end))))))))) + (let ((ranges-by-lang + (if (functionp embedded-lang) + (treesit-query-range-by-language + host-parser query embedded-lang beg end) + (list (cons embedded-lang + (treesit-query-range host-parser query beg end))))) + (touched-parsers nil)) + (dolist (lang-and-range ranges-by-lang) + (let ((embedded-lang (car lang-and-range)) + (ranges (cdr lang-and-range))) + (pcase-dolist (`(,beg . ,end) ranges) + (let ((existing-local-parser + (catch 'done + (dolist (ov (overlays-in beg end) nil) + ;; Update range of local parser. + (when-let* ((embedded-parser + (overlay-get ov 'treesit-parser)) + (parser-lang (treesit-parser-language + embedded-parser)) + (parser-level (treesit-parser-embed-level + embedded-parser))) + (when (and (eq parser-lang embedded-lang) + (eq embed-level parser-level)) + (treesit-parser-set-included-ranges + embedded-parser `((,beg . ,end))) + (move-overlay ov beg end) + (overlay-put ov 'treesit-parser-ov-timestamp + modified-tick) + (throw 'done embedded-parser))))))) + (if existing-local-parser + (push existing-local-parser touched-parsers) + ;; Create overlay and local parser. + (let ((embedded-parser (treesit-parser-create + embedded-lang nil t 'embedded)) + (ov (make-overlay beg end nil nil t))) + (treesit-parser-set-embed-level embedded-parser embed-level) + (overlay-put ov 'treesit-parser embedded-parser) + (overlay-put ov 'treesit-host-parser host-parser) + (overlay-put ov 'treesit-parser-ov-timestamp + modified-tick) + (treesit-parser-set-included-ranges + embedded-parser `((,beg . ,end))) + (push embedded-parser touched-parsers))))))) + touched-parsers)) + +(defun treesit--update-range-1 (beg end host-parser settings embed-level) + "Given a HOST-PARSER, update ranges between BEG and END. + +Go over each settings in SETTINGS, try to create or update the embedded +language in that setting. Return the created or updated embedded +language parsers in a list. + +EMBED-LEVEL is the embed level for the embedded parser being created or +updated. When looking for existing embedded parsers, only look for +parsers of this level; when creating new parsers, set their level to +this level." + (let ((touched-parsers nil) + (modified-tick (buffer-chars-modified-tick))) + (dolist (setting settings) + (let* ((query (nth 0 setting)) + (query-lang (treesit-query-language query)) + (embed-lang (nth 1 setting)) + (local (nth 2 setting)) + (offset (nth 3 setting))) + (when (eq query-lang (treesit-parser-language host-parser)) + (cond + ((functionp query) (funcall query beg end)) + (local + (setq touched-parsers + (append touched-parsers + (treesit--update-ranges-local + host-parser query embed-lang modified-tick + embed-level beg end)))) + ;; When updating ranges, we want to avoid querying the whole + ;; buffer which could be slow in very large buffers. + ;; Instead, we only query for nodes that intersect with the + ;; region between BEG and END. Also, we only update the + ;; ranges intersecting BEG and END; outside of that region we + ;; inherit old ranges. + (t (setq touched-parsers + (append touched-parsers + (treesit--update-ranges-non-local + host-parser query embed-lang embed-level + beg end offset)))))))) + touched-parsers)) (defun treesit-update-ranges (&optional beg end) "Update the ranges for each language in the current buffer. @@ -841,41 +1020,19 @@ If BEG and END are non-nil, only update parser ranges in that region." (let ((modified-tick (buffer-chars-modified-tick)) (beg (or beg (point-min))) - (end (or end (point-max)))) - ;; When updating ranges, we want to avoid querying the whole buffer - ;; which could be slow in very large buffers. Instead, we only - ;; query for nodes that intersect with the region between BEG and - ;; END. Also, we only update the ranges intersecting BEG and END; - ;; outside of that region we inherit old ranges. - (dolist (setting treesit-range-settings) - (let ((query (nth 0 setting)) - (language (nth 1 setting)) - (local (nth 2 setting)) - (offset (nth 3 setting))) - (cond - ((functionp query) (funcall query beg end)) - (local - (treesit--update-ranges-local - query language modified-tick beg end)) - (t - (let* ((host-lang (treesit-query-language query)) - (parser (car (treesit-parser-list nil language))) - (old-ranges (treesit-parser-included-ranges parser)) - (new-ranges (treesit-query-range - host-lang query beg end offset)) - (set-ranges (treesit--clip-ranges - (treesit--merge-ranges - old-ranges new-ranges beg end) - (point-min) (point-max)))) - (dolist (parser (treesit-parser-list nil language)) - (treesit-parser-set-included-ranges - parser (or set-ranges - ;; When there's no range for the embedded - ;; language, set it's range to a dummy (1 - ;; . 1), otherwise it would be set to the - ;; whole buffer, which is not what we want. - `((,(point-min) . ,(point-min))))))))))) - + (end (or end (point-max))) + (host-parsers (list treesit-primary-parser)) + (embed-level 0)) + (while (and host-parsers (< embed-level 4)) + (cl-incf embed-level) + (let ((next-round-of-host-parsers nil)) + (dolist (host-parser host-parsers) + (setq next-round-of-host-parsers + (append next-round-of-host-parsers + (treesit--update-range-1 + beg end host-parser + treesit-range-settings embed-level)))) + (setq host-parsers next-round-of-host-parsers))) (treesit--cleanup-local-range-overlays modified-tick beg end))) (defun treesit-parser-range-on (parser beg &optional end) @@ -930,18 +1087,6 @@ LANGUAGE's name and return the resulting string." "Generic tree-sitter font-lock error" 'treesit-error) -;; The primary parser will be access frequently (after each re-parse, -;; before redisplay, etc, see -;; `treesit--font-lock-mark-ranges-to-fontify'), so we don't want to -;; allow it to be a callback function which returns the primary parser -;; (it might be slow). It's not something that needs to be dynamic -;; anyway. -(defvar-local treesit-primary-parser nil - "The primary parser for this buffer. - -The primary parser should be a parser that parses the entire buffer, as -opposed to embedded parsers which parses only part of the buffer.") - (defvar-local treesit-font-lock-settings nil "A list of SETTINGs for treesit-based fontification. @@ -4323,6 +4468,9 @@ Return the start of the syntax tree text corresponding to NODE." do (setq start (overlay-start ov)) if (treesit-node-eq end-node (overlay-get ov 'treesit-node)) do (setq end (overlay-end ov))) + ;; FIXME: Fix highlighting for a node that spans across multiple + ;; ranges: the buffer region in-between ranges shouldn't be + ;; highlighted. (when (and start end) (setq-local treesit--explorer-highlight-overlay (make-overlay start end)) commit 625b2b02a3c9bad6d7abf57ea7f95ece29855906 Author: Yuan Fu Date: Thu Feb 27 03:07:34 2025 -0800 Enable treesit-query-capture to return grouped captures This is needed for creating embedded parsers for embedded code blocks of which language cannot be known ahead of time. E.g., markdown and org mode's code block. * src/treesit.c (Ftreesit_query_capture): Add parameter GROUPED. diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi index f12104ea267..2b224036de0 100644 --- a/doc/lispref/parsing.texi +++ b/doc/lispref/parsing.texi @@ -1225,7 +1225,7 @@ example, the capture name @code{biexp}: @cindex query functions, tree-sitter Now we can introduce the @dfn{query functions}. -@defun treesit-query-capture node query &optional beg end node-only +@defun treesit-query-capture node query &optional beg end node-only grouped This function matches patterns in @var{query} within @var{node}. The argument @var{query} can be either an s-expression, a string, or a compiled query object. For now, we focus on the s-expression syntax; @@ -1247,6 +1247,12 @@ matching node whose span overlaps with the region between @var{beg} and @var{end} is captured; it doesn't have to be completely contained in the region. +If @var{grouped} is non-@code{nil}, instead of returning a list of +@w{@code{(@var{capture_name} . @var{node})}}, this function returns a +list of list of it. The grouping is determined by @var{query}. +Captures in the same match of a pattern in @var{query} are grouped +together. + @vindex treesit-query-error @findex treesit-query-validate This function raises the @code{treesit-query-error} error if @@ -1284,7 +1290,7 @@ For example, it could have two top-level patterns: @group (setq query '((binary_expression) @@biexp - (number_literal) @@number @@biexp) + (number_literal) @@number) @end group @end example diff --git a/src/treesit.c b/src/treesit.c index 16308193bf5..c8af17a5b8b 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -3272,7 +3272,7 @@ treesit_initialize_query (Lisp_Object query, const TSLanguage *lang, DEFUN ("treesit-query-capture", Ftreesit_query_capture, - Streesit_query_capture, 2, 5, 0, + Streesit_query_capture, 2, 6, 0, doc: /* Query NODE with patterns in QUERY. Return a list of (CAPTURE_NAME . NODE). CAPTURE_NAME is the name @@ -3289,7 +3289,11 @@ in which the query is executed. Any matching node whose span overlaps with the region between BEG and END are captured, it doesn't have to be completely in the region. -If NODE-ONLY is non-nil, return a list of nodes. +If GROUPED is non-nil, group captures into matches and return a list of +MATCH, where each MATH is a list of (CAPTURE_NAME . NODE). + +If NODE-ONLY is non-nil, return nodes only, and don't include +CAPTURE_NAME. Besides a node, NODE can be a parser, in which case the root node of that parser is used. NODE can also be a language symbol, in which case @@ -3300,7 +3304,8 @@ Signal `treesit-query-error' if QUERY is malformed or something else goes wrong. You can use `treesit-query-validate' to validate and debug the query. */) (Lisp_Object node, Lisp_Object query, - Lisp_Object beg, Lisp_Object end, Lisp_Object node_only) + Lisp_Object beg, Lisp_Object end, Lisp_Object node_only, + Lisp_Object grouped) { if (!(TS_COMPILED_QUERY_P (query) || CONSP (query) || STRINGP (query))) @@ -3385,8 +3390,22 @@ the query. */) while (ts_query_cursor_next_match (cursor, &match)) { - /* Record the checkpoint that we may roll back to. */ + /* Depends on the value of GROUPED, we have two modes of + operation. + + If GROUPED is nil (mode 1), we return a list of captures; in + this case, we append the captures first, and revert back if the + captures don't match. + + If GROUPED is non-nil (mode 2), we return a list of match + groups; in this case, we collect captures into a list first, + and append to the results after verifying that the group + matches. */ + + /* Mode 1: Record the checkpoint that we may roll back to. */ prev_result = result; + /* Mode 2: Create a list storing captures of this match group. */ + Lisp_Object match_group = Qnil; /* 1. Get captured nodes. */ const TSQueryCapture *captures = match.captures; for (int idx = 0; idx < match.capture_count; idx++) @@ -3408,7 +3427,10 @@ the query. */) else cap = captured_node; - result = Fcons (cap, result); + if (NILP (grouped)) + result = Fcons (cap, result); /* Mode 1. */ + else + match_group = Fcons (cap, match_group); /* Mode 2. */ } /* 2. Get predicates and check whether this match can be included in the result list. */ @@ -3421,15 +3443,27 @@ the query. */) } /* captures_lisp = Fnreverse (captures_lisp); */ + /* Mode 1. */ struct capture_range captures_range = { result, prev_result }; - bool match = treesit_eval_predicates (captures_range, predicates, - &predicate_signal_data); + /* Mode 2. */ + if (!NILP (grouped)) + { + captures_range.start = match_group; + captures_range.end = Qnil; + } + 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) + /* Mode 1: Predicates didn't pass, roll back. */ + if (!match && NILP (grouped)) result = prev_result; + /* Mode 2: Predicates pass, add this match group. */ + if (match && !NILP (grouped)) + result = Fcons (Fnreverse (match_group), result); } /* Final clean up. */ commit 30e1508ef2d40e221736cea2c50c64941d7d2f0d Author: Yuan Fu Date: Thu Feb 27 03:05:26 2025 -0800 Add tree-sitter-parser-embed-level and parent-node Add parser properties embed-level and parent-node. They'll be help us implement arbitrarily nested embeded parser, and navigation across embedded and host parsers, respectively. * src/treesit.c: (Ftreesit_parser_embed_level): (Ftreesit_parser_set_embed_level): (Ftreesit_parser_parent_node): (Ftreesit_parser_set_parent_node): New functions. diff --git a/src/treesit.c b/src/treesit.c index 62606d99749..16308193bf5 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -1367,6 +1367,8 @@ make_treesit_parser (Lisp_Object buffer, TSParser *parser, lisp_parser->after_change_functions = Qnil; lisp_parser->tag = tag; lisp_parser->last_set_ranges = Qnil; + lisp_parser->embed_level = Qnil; + lisp_parser->parent_node = Qnil; lisp_parser->buffer = buffer; lisp_parser->parser = parser; lisp_parser->tree = tree; @@ -1818,6 +1820,69 @@ DEFUN ("treesit-parser-tag", return XTS_PARSER (parser)->tag; } +DEFUN ("treesit-parser-embed-level", + Ftreesit_parser_embed_level, Streesit_parser_embed_level, + 1, 1, 0, + doc: /* Return PARSER's embed level. + +The embed level can be either nil or a non-negative integer. A value of +nil means the parser isn't part of the embedded parser tree. The +primary parser has embed level 0, from it, each layer of embedded parser +has +1 embed level. */) + (Lisp_Object parser) +{ + treesit_check_parser (parser); + return XTS_PARSER (parser)->embed_level; +} + +/* TODO: Mention in manual, once the API stabilizes. */ +DEFUN ("treesit-parser-set-embed-level", + Ftreesit_parser_set_embed_level, Streesit_parser_set_embed_level, + 2, 2, 0, + doc: /* Set the embed level for PARSER to LEVEL. */) + (Lisp_Object parser, Lisp_Object level) +{ + treesit_check_parser (parser); + if (!NILP (level)) + { + CHECK_NUMBER (level); + if (XFIXNUM (level) < 0) + xsignal (Qargs_out_of_range, list1 (level)); + } + + XTS_PARSER (parser)->embed_level = level; + return level; +} + +DEFUN ("treesit-parser-parent-node", + Ftreesit_parser_parent_node, Streesit_parser_parent_node, + 1, 1, 0, + doc: /* Return PARSER's parent node, if one exists. + +Only embeded local parser can have parent node. When Emacs uses a node +in the host parser to create this local parser, that node is considered +the parent node of the local parser. */) + (Lisp_Object parser) +{ + treesit_check_parser (parser); + return XTS_PARSER (parser)->parent_node; +} + +DEFUN ("treesit-parser-set-parent-node", + Ftreesit_parser_set_parent_node, Streesit_parser_set_parent_node, + 2, 2, 0, + doc: /* Return PARSER's parent node to NODE. */) + (Lisp_Object parser, Lisp_Object node) +{ + treesit_check_parser (parser); + if (!NILP (node)) + CHECK_TS_NODE (node); + + XTS_PARSER (parser)->parent_node = node; + return node; +} + + /* Return true if PARSER is not deleted and its buffer is live. */ static bool treesit_parser_live_p (Lisp_Object parser) @@ -4538,6 +4603,10 @@ applies to LANGUAGE-A will be redirected to LANGUAGE-B instead. */); defsubr (&Streesit_parser_buffer); defsubr (&Streesit_parser_language); defsubr (&Streesit_parser_tag); + defsubr (&Streesit_parser_embed_level); + defsubr (&Streesit_parser_set_embed_level); + defsubr (&Streesit_parser_parent_node); + defsubr (&Streesit_parser_set_parent_node); defsubr (&Streesit_parser_root_node); defsubr (&Streesit_parse_string); diff --git a/src/treesit.h b/src/treesit.h index 19dc28af246..d19a0e76216 100644 --- a/src/treesit.h +++ b/src/treesit.h @@ -63,6 +63,28 @@ struct Lisp_TS_Parser but rather return DEFAULT_RANGE. (A single range where start_byte = 0, end_byte = UINT32_MAX). */ Lisp_Object last_set_ranges; + /* Parsers for embedded code blocks will have a non-zero embed level. + The primary parser has level 0, and each layer of embedded parser + gets +1 level. The embed level can be either a non-negative + integer or nil. Every parser created by treesit-parser-create + starts with a nil level. If the value is nil, that means the range + functions (treesit-update-ranges and friends) haven't touched this + parser yet, and this parser isn't part of the embed parser tree. */ + Lisp_Object embed_level; + /* Some comments: Technically you could calculate embed_level by + following parent_node, but parent_node might be outdated so it's a + good idea to record embed_level separately. Embed_level and + parent_node could have been implemented as "parser properties" with + an obarray, but ultimately I think two explicit fields helps + documentation better and it's not clear to me that a property list + for a parser will be useful beyond this. And we can always convert + these to properties later, but not vice versa. */ + /* When an embedded parser is created, it's usually based on a node in + the host parser. This field saves that node so it possible to + climb up and out of the embedded parser into the host parser. Note + that the range of the embedded parser doesn't have to match that of + the parent node. */ + Lisp_Object parent_node; /* The buffer associated with this parser. */ Lisp_Object buffer; /* The pointer to the tree-sitter parser. Never NULL. */ commit 32da093e524d5e28945557701f7c50d7c4a898cd Author: Philipp Stephani Date: Thu Jan 30 16:12:49 2025 +0100 Don't overwrite non-local exit symbol and data (Bug#65796). The previous approach would incorrectly invalidate the returned module values if another non-local exit occurred while dealing with a non-local exit. See Bug#65796. Instead, allocate the values from the usual environment storage, and return statically-allocated objects if that fails. * src/emacs-module.c (struct emacs_env_private): Turn non-local exit symbol and data into normal Lisp objects. (initialize_environment): Initialize them. (mark_module_environment): Prevent them from being garbage-collected. (module_signal_or_throw, module_non_local_exit_signal_1) (module_non_local_exit_throw_1): Adapt uses. (value_to_lisp): No longer scan for them with module assertions enabled. (module_out_of_memory_signal, module_out_of_memory_data): New statically-allocated module values to return in case of allocation failure. (syms_of_module): Initialize them. (module_non_local_exit_get): Allocate module values normally. If that fails, return statically-allocated values. * doc/lispref/internals.texi (Module Nonlocal): Document new behavior. diff --git a/doc/lispref/internals.texi b/doc/lispref/internals.texi index ee1fcbbbd68..a8b9ea88f7f 100644 --- a/doc/lispref/internals.texi +++ b/doc/lispref/internals.texi @@ -2076,7 +2076,11 @@ error symbol in @code{*@var{symbol}} and the error data in tag symbol in @code{*@var{symbol}} and the @code{throw} value in @code{*@var{data}}. The function doesn't store anything in memory pointed by these arguments when the return value is -@code{emacs_funcall_exit_return}. +@code{emacs_funcall_exit_return}. If the function fails to allocate +storage for @var{symbol} or @var{data}, it stores a value representing +the symbol @code{module-out-of-memory} in @code{*@var{symbol}}, stores a +value representing @code{nil} in @code{*@var{data}}, and returns +@code{emacs_funcall_exit_signal}. @end deftypefn You should check nonlocal exit conditions where it matters: before you diff --git a/src/emacs-module.c b/src/emacs-module.c index 0a67433ec70..ab6b900df8d 100644 --- a/src/emacs-module.c +++ b/src/emacs-module.c @@ -167,7 +167,7 @@ struct emacs_env_private /* Dedicated storage for non-local exit symbol and data so that storage is always available for them, even in an out-of-memory situation. */ - struct emacs_value_tag non_local_exit_symbol, non_local_exit_data; + Lisp_Object non_local_exit_symbol, non_local_exit_data; struct emacs_value_storage storage; }; @@ -500,6 +500,9 @@ module_non_local_exit_clear (emacs_env *env) env->private_members->pending_non_local_exit = emacs_funcall_exit_return; } +static struct emacs_value_tag module_out_of_memory_symbol; +static struct emacs_value_tag module_out_of_memory_data; + static enum emacs_funcall_exit module_non_local_exit_get (emacs_env *env, emacs_value *symbol, emacs_value *data) @@ -507,12 +510,23 @@ module_non_local_exit_get (emacs_env *env, module_assert_thread (); module_assert_env (env); struct emacs_env_private *p = env->private_members; - if (p->pending_non_local_exit != emacs_funcall_exit_return) + enum emacs_funcall_exit ret = p->pending_non_local_exit; + if (ret != emacs_funcall_exit_return) { - *symbol = &p->non_local_exit_symbol; - *data = &p->non_local_exit_data; + emacs_value sym + = allocate_emacs_value (env, p->non_local_exit_symbol); + emacs_value dat + = allocate_emacs_value (env, p->non_local_exit_data); + if (sym == NULL || dat == NULL) + { + sym = &module_out_of_memory_symbol; + dat = &module_out_of_memory_data; + ret = emacs_funcall_exit_signal; + } + *symbol = sym; + *data = dat; } - return p->pending_non_local_exit; + return ret; } /* Like for `signal', DATA must be a list. */ @@ -1185,11 +1199,11 @@ module_signal_or_throw (struct emacs_env_private *env) case emacs_funcall_exit_return: return; case emacs_funcall_exit_signal: - xsignal (value_to_lisp (&env->non_local_exit_symbol), - value_to_lisp (&env->non_local_exit_data)); + xsignal (env->non_local_exit_symbol, + env->non_local_exit_data); case emacs_funcall_exit_throw: - Fthrow (value_to_lisp (&env->non_local_exit_symbol), - value_to_lisp (&env->non_local_exit_data)); + Fthrow (env->non_local_exit_symbol, + env->non_local_exit_data); default: eassume (false); } @@ -1389,8 +1403,8 @@ module_non_local_exit_signal_1 (emacs_env *env, Lisp_Object sym, if (p->pending_non_local_exit == emacs_funcall_exit_return) { p->pending_non_local_exit = emacs_funcall_exit_signal; - p->non_local_exit_symbol.v = sym; - p->non_local_exit_data.v = data; + p->non_local_exit_symbol = sym; + p->non_local_exit_data = data; } } @@ -1402,8 +1416,8 @@ module_non_local_exit_throw_1 (emacs_env *env, Lisp_Object tag, if (p->pending_non_local_exit == emacs_funcall_exit_return) { p->pending_non_local_exit = emacs_funcall_exit_throw; - p->non_local_exit_symbol.v = tag; - p->non_local_exit_data.v = value; + p->non_local_exit_symbol = tag; + p->non_local_exit_data = value; } } @@ -1439,13 +1453,6 @@ value_to_lisp (emacs_value v) { const emacs_env *env = pdl->unwind_ptr.arg; struct emacs_env_private *priv = env->private_members; - /* The value might be one of the nonlocal exit values. Note - that we don't check whether a nonlocal exit is currently - pending, because the module might have cleared the flag - in the meantime. */ - if (&priv->non_local_exit_symbol == v - || &priv->non_local_exit_data == v) - goto ok; if (value_storage_contains_p (&priv->storage, v, &num_values)) goto ok; ++num_environments; @@ -1536,6 +1543,8 @@ mark_module_environment (void *ptr) { emacs_env *env = ptr; struct emacs_env_private *priv = env->private_members; + mark_object (priv->non_local_exit_symbol); + mark_object (priv->non_local_exit_data); for (struct emacs_value_frame *frame = &priv->storage.initial; frame != NULL; frame = frame->next) for (int i = 0; i < frame->offset; ++i) @@ -1561,6 +1570,8 @@ initialize_environment (emacs_env *env, struct emacs_env_private *priv) } priv->pending_non_local_exit = emacs_funcall_exit_return; + priv->non_local_exit_symbol = Qnil; + priv->non_local_exit_data = Qnil; initialize_storage (&priv->storage); env->size = sizeof *env; env->private_members = priv; @@ -1711,6 +1722,18 @@ syms_of_module (void) Vmodule_refs_hash = make_hash_table (&hashtest_eq, DEFAULT_HASH_SIZE, Weak_None); + DEFSYM (Qmodule_out_of_memory, "module-out-of-memory"); + Fput (Qmodule_out_of_memory, Qerror_conditions, + list2 (Qmodule_out_of_memory, Qerror)); + Fput (Qmodule_out_of_memory, Qerror_message, + build_unibyte_string ("Module out of memory")); + + staticpro (&module_out_of_memory_symbol.v); + module_out_of_memory_symbol.v = Qmodule_out_of_memory; + + staticpro (&module_out_of_memory_data.v); + module_out_of_memory_data.v = Qnil; + DEFSYM (Qmodule_load_failed, "module-load-failed"); Fput (Qmodule_load_failed, Qerror_conditions, list (Qmodule_load_failed, Qerror)); commit ea715b0183f6a19d491cad36eb18c2c9cf0f0dd3 Author: Visuwesh Date: Thu Feb 20 20:19:32 2025 +0530 Recommend 'tab-bar-history-mode' over 'winner-mode' * lisp/winner.el (winner-mode): * lisp/tab-bar.el (tab-bar-mode): Update the docstring to suggest 'tab-bar-history-mode' instead of 'winner-mode' when using 'tab-bar-mode' instead. (Bug#76439) diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el index 2a6137f527f..9d804c4ed28 100644 --- a/lisp/tab-bar.el +++ b/lisp/tab-bar.el @@ -291,7 +291,12 @@ to switch the frame between different window configurations. See `current-window-configuration' for more about window configurations. To add a button (which can then record one more window configuration), click on the \"+\" button. Clicking on the \"x\" icon of a button -deletes the button." +deletes the button. + +If you intend to use `tab-bar-mode' with `winner-mode', we recommend +using `tab-bar-history-mode' instead, since it provides tab-specific +window configuration history, and is better behaved when `tab-bar-mode' +is turned on." :global t ;; It's defined in C/cus-start, this stops the d-m-m macro defining it again. :variable tab-bar-mode diff --git a/lisp/winner.el b/lisp/winner.el index a129417b08c..b2f462365cf 100644 --- a/lisp/winner.el +++ b/lisp/winner.el @@ -345,7 +345,13 @@ the window configuration (i.e. how the frames are partitioned into windows) so that the changes can be \"undone\" using the command `winner-undo'. By default this one is bound to the key sequence \\`C-c '. If you change your mind (while undoing), -you can press \\`C-c ' (calling `winner-redo')." +you can press \\`C-c ' (calling `winner-redo'). + +If you use `tab-bar-mode', consider using `tab-bar-history-mode', as +`winner-mode' is unaware of tab switching, and might turn the window +configuration of the current tab to another's (old) window +configuration. `tab-bar-history-mode' provides tab-specific window +configuration history avoiding this problem." :global t (if winner-mode (progn commit 5f1a019d26f475f35fe8efc583749321affb6060 Author: Vincenzo Pupillo Date: Wed Feb 19 21:47:23 2025 +0100 Fix CSS indentation, added support for CSS 'color_value'. * lisp/textmodes/mhtml-ts-mode.el (mhtml-ts-mode--colorize-css-value): Added 'color_value' node, refactoring. (mhtml-ts-mode--treesit-font-lock-settings): Added 'color_value' node. (mhtml-ts-mode--treesit-indent-rules): Removed the old comment and fixed the CSS indentation rule (bug#76597). diff --git a/lisp/textmodes/mhtml-ts-mode.el b/lisp/textmodes/mhtml-ts-mode.el index 33c5f3c3019..09fbb4c1cad 100644 --- a/lisp/textmodes/mhtml-ts-mode.el +++ b/lisp/textmodes/mhtml-ts-mode.el @@ -232,21 +232,23 @@ Optional ARGUMENTS to to be passed to it." (defun mhtml-ts-mode--colorize-css-value (node override start end &rest _) "Colorize CSS property value like `css--fontify-region'. For NODE, OVERRIDE, START, and END, see `treesit-font-lock-rules'." - (if (and mhtml-ts-mode-css-fontify-colors - (string-equal "plain_value" (treesit-node-type node))) - (let ((color (css--compute-color start (treesit-node-text node t)))) - (when color - (with-silent-modifications - (add-text-properties - (treesit-node-start node) (treesit-node-end node) - (list 'face (list :background color - :foreground (readable-foreground-color - color) - :box '(:line-width -1))))))) + (let ((node-start (treesit-node-start node)) + (node-end (treesit-node-end node))) (treesit-fontify-with-override - (treesit-node-start node) (treesit-node-end node) + node-start node-end 'font-lock-variable-name-face - override start end))) + override start end) + ;; apply color if required + (when-let* ((ok (and mhtml-ts-mode-css-fontify-colors + (member (treesit-node-type node) '("plain_value" "color_value")))) + (color (css--compute-color start (treesit-node-text node t)))) + (with-silent-modifications + (add-text-properties + node-start node-end + (list 'face (list :background color + :foreground (readable-foreground-color + color) + :box '(:line-width -1)))))))) ;; Embedded languages should be indented according to the language ;; that embeds them. @@ -293,8 +295,8 @@ NODE and PARENT are ignored." :language 'css :override t :feature 'variable - '((plain_value) @font-lock-variable-name-face - (plain_value) @mhtml-ts-mode--colorize-css-value)) + '((plain_value) @mhtml-ts-mode--colorize-css-value + (color_value) @mhtml-ts-mode--colorize-css-value)) css--treesit-settings)) "Settings for `treesit-font-lock-settings'.") @@ -338,9 +340,8 @@ NODE and PARENT are ignored." `((css ((parent-is "stylesheet") mhtml-ts-mode--js-css-tag-bol mhtml-ts-mode--js-css-indent-offset))) - css--treesit-indent-rules 'prepend) - :replace)) - "Settings for `treesit-simple-indent-rules'.") + css--treesit-indent-rules + :prepend)))) (defvar mhtml-ts-mode--treesit-aggregated-simple-imenu-settings `((html ,@html-ts-mode--treesit-simple-imenu-settings) commit 3e1d7e0334d884a17887bd1b9ee6a9b9e3026a30 Author: Paul Nelson Date: Thu Feb 27 10:30:13 2025 +0100 Document :continue-only in use-package Info manual * doc/misc/use-package.texi (Binding to repeat-maps): Document :continue-only (bug#74140). diff --git a/doc/misc/use-package.texi b/doc/misc/use-package.texi index bcb068e6654..e9fdbff5a45 100644 --- a/doc/misc/use-package.texi +++ b/doc/misc/use-package.texi @@ -1172,6 +1172,35 @@ Specifying @code{:continue} @emph{forces} setting the @end group @end lisp +@findex :continue-only@r{, inside} :repeat-map@r{ and} :bind +@cindex binding commands that only continue repeat series +Specifying @code{:continue-only} inside the scope of @code{:repeat-map} +will make commands continue an active repeating sequence, but never +initiate it. This is done by setting the @code{repeat-continue} +property of each command with the keymap, but not the @code{repeat-map} +property. This is useful for commands that should be available while +repeating a sequence, but not initiate the repeat map themselves. +Example: + +@lisp +@group +(use-package emacs + :bind + (:repeat-map + paragraph-repeat-map + ("@}" . forward-paragraph) + ("@{" . backward-paragraph) + ("]" . forward-paragraph) + ("[" . backward-paragraph) + :continue-only + ("h" . mark-paragraph) + ("w" . kill-region) + ("M-w" . kill-ring-save) + ("k" . kill-paragraph) + ("y" . yank))) +@end group +@end lisp + @node Displaying keybindings @subsection Displaying personal keybindings @cindex display your keybindings commit dc1637fef7c658b6d865f3e71f3b95686e56542d Author: Juri Linkov Date: Thu Feb 27 19:21:45 2025 +0200 Improve 'treesit-outline-search' * lisp/treesit.el (treesit-outline-search): Add optional arg 'recursive' to avoid infinite recursion when it gets stuck. * lisp/textmodes/markdown-ts-mode.el (markdown-ts-mode): Set buffer-local 'treesit-outline-predicate' to "section". diff --git a/lisp/textmodes/markdown-ts-mode.el b/lisp/textmodes/markdown-ts-mode.el index 62fa23c7105..18669bf7483 100644 --- a/lisp/textmodes/markdown-ts-mode.el +++ b/lisp/textmodes/markdown-ts-mode.el @@ -98,6 +98,7 @@ (setq-local treesit-simple-imenu-settings `(("Headings" markdown-ts-imenu-node-p nil markdown-ts-imenu-name-function))) + (setq-local treesit-outline-predicate "section") (when (treesit-ready-p 'markdown) (treesit-parser-create 'markdown-inline) diff --git a/lisp/treesit.el b/lisp/treesit.el index 97764e3ef80..e96aa4cb8f5 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -3664,7 +3664,7 @@ this variable takes priority.") (if backward (seq-max bounds) (seq-min bounds))))) closest)) -(defun treesit-outline-search (&optional bound move backward looking-at) +(defun treesit-outline-search (&optional bound move backward looking-at recursive) "Search for the next outline heading in the syntax tree. For BOUND, MOVE, BACKWARD, LOOKING-AT, see the descriptions in `outline-search-function'." @@ -3684,7 +3684,7 @@ For BOUND, MOVE, BACKWARD, LOOKING-AT, see the descriptions in (if (bobp) (point) (1- (point))) (pos-eol)))) (pred (if treesit-aggregated-outline-predicate - (alist-get (treesit-language-at pos) + (alist-get (treesit-language-at (or bob-pos pos)) treesit-aggregated-outline-predicate) treesit-outline-predicate)) (found (or bob-pos @@ -3692,7 +3692,7 @@ For BOUND, MOVE, BACKWARD, LOOKING-AT, see the descriptions in (closest (treesit-closest-parser-boundary pos backward))) ;; Handle multi-language modes - (if (and closest + (if (and closest (not recursive) (or ;; Possibly was inside the local parser, and when can't find ;; more matches inside it then need to go over the closest @@ -3707,7 +3707,7 @@ For BOUND, MOVE, BACKWARD, LOOKING-AT, see the descriptions in (goto-char (if backward (max (point-min) (1- closest)) (min (point-max) (1+ closest)))) - (treesit-outline-search bound move backward)) + (treesit-outline-search bound move backward nil 'recursive)) (if found (if (or (not bound) (if backward (>= found bound) (<= found bound))) commit 4cc5e4ec0bc6c11a19ca0244f60813619acd718d Author: Stephen Berman Date: Thu Feb 27 12:13:26 2025 +0100 Prevent a todo-mode test from running with 'make check' * test/lisp/calendar/todo-mode-tests.el (todo-test-add-and-delete-file): Tag this test as unstable, since it has been reported to fail unreproducibly (bug#58473) or reproducibly only on the ZFS filesystem (bug#76381) and the causes are still unknown. diff --git a/test/lisp/calendar/todo-mode-tests.el b/test/lisp/calendar/todo-mode-tests.el index 0d10cc60547..157a4c3168c 100644 --- a/test/lisp/calendar/todo-mode-tests.el +++ b/test/lisp/calendar/todo-mode-tests.el @@ -825,6 +825,10 @@ automatic testing." Calling todo-show should display the last current todo file, not necessarily the new file. After deleting the new file, todo-show should display the previously current (or default) todo file." + ;; This test has been reported to fail unreproducibly (bug#58473) or + ;; reproducibly only on the ZFS filesystem (bug#76381). Since the + ;; causes are still unknown, don't run it with 'make check'. + :tags '(:unstable) (with-todo-test (todo-show) (should (equal todo-current-todo-file todo-test-file-1)) commit 13423cd0197fa69bd6246f0720471b265c328596 Author: Stephen Berman Date: Thu Feb 27 12:03:24 2025 +0100 Prevent a todo-mode test from running with 'make check' * test/lisp/calendar/todo-mode-tests.el (todo-test-add-and-delete-file): Tag this test as unstable, since it has been reported to fail unreproducibly (bug#58473) or reproducibly only on the ZFS filesystem (bug#76381) and the causes are still unknown. diff --git a/test/lisp/calendar/todo-mode-tests.el b/test/lisp/calendar/todo-mode-tests.el index 157a4c3168c..0d10cc60547 100644 --- a/test/lisp/calendar/todo-mode-tests.el +++ b/test/lisp/calendar/todo-mode-tests.el @@ -825,10 +825,6 @@ automatic testing." Calling todo-show should display the last current todo file, not necessarily the new file. After deleting the new file, todo-show should display the previously current (or default) todo file." - ;; This test has been reported to fail unreproducibly (bug#58473) or - ;; reproducibly only on the ZFS filesystem (bug#76381). Since the - ;; causes are still unknown, don't run it with 'make check'. - :tags '(:unstable) (with-todo-test (todo-show) (should (equal todo-current-todo-file todo-test-file-1)) commit f5f0ba38e0e22c9017d1c8482fbe4432a87ed8af Author: Stephen Berman Date: Thu Feb 27 11:51:22 2025 +0100 Prevent a todo-mode test from running with 'make check' * test/lisp/calendar/todo-mode-tests.el (todo-test-add-and-delete-file): Tag this test as unstable, since it has been reported to fail unreproducibly (bug#58473) or reproducibly only on the ZFS filesystem (bug#76381) and the causes are still unknown. diff --git a/test/lisp/calendar/todo-mode-tests.el b/test/lisp/calendar/todo-mode-tests.el index 0d10cc60547..157a4c3168c 100644 --- a/test/lisp/calendar/todo-mode-tests.el +++ b/test/lisp/calendar/todo-mode-tests.el @@ -825,6 +825,10 @@ automatic testing." Calling todo-show should display the last current todo file, not necessarily the new file. After deleting the new file, todo-show should display the previously current (or default) todo file." + ;; This test has been reported to fail unreproducibly (bug#58473) or + ;; reproducibly only on the ZFS filesystem (bug#76381). Since the + ;; causes are still unknown, don't run it with 'make check'. + :tags '(:unstable) (with-todo-test (todo-show) (should (equal todo-current-todo-file todo-test-file-1)) commit a4a458ffa4aaeb988ff4b6e6087c44dfdaf40a0b Author: Po Lu Date: Thu Feb 27 09:53:41 2025 +0800 Fix drag-and-drop treatment of reused tooltip frames * src/androidfns.c (Fx_show_tip): Set `tip_window' to that of any reused tooltip frame. * src/haikufns.c (unwind_create_frame): Return whether the frame was destroyed, as on X. (unwind_create_tip_frame, haiku_create_frame, Fx_show_tip): Synchronize with X. (do_unwind_create_frame): New function. (tip_window): Remove unused variable. * src/nsfns.m (tip_window, unwind_create_tip_frame): Remove unused variable `tip_window'. * src/pgtkfns.c (pgtk_create_tip_frame): Rename to pgtk_create_tip_frame. (Fx_show_tip): Adjust accordingly. Set `tip_window' to that of any reused tooltip frame. * src/w32fns.c (Fx_show_tip): * src/xfns.c (Fx_show_tip): Set `tip_window' to that of any reused tooltip frame. diff --git a/src/androidfns.c b/src/androidfns.c index f8e3d397008..ec8651aafcb 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -2449,6 +2449,8 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, /* Creating the tip frame failed. */ return unbind_to (count, Qnil); } + else + tip_window = FRAME_ANDROID_WINDOW (XFRAME (tip_frame)); tip_f = XFRAME (tip_frame); window = FRAME_ROOT_WINDOW (tip_f); diff --git a/src/haikufns.c b/src/haikufns.c index 6490a22bd01..298a27ccc02 100644 --- a/src/haikufns.c +++ b/src/haikufns.c @@ -53,10 +53,6 @@ Lisp_Object tip_frame; /* The X and Y deltas of the last call to `x-show-tip'. */ Lisp_Object tip_dx, tip_dy; -/* The window-system window corresponding to the frame of the - currently visible tooltip. */ -static Window tip_window; - /* A timer that hides or deletes the currently visible tooltip when it fires. */ static Lisp_Object tip_timer; @@ -610,7 +606,7 @@ initial_setup_back_buffer (struct frame *f) unblock_input (); } -static void +static Lisp_Object unwind_create_frame (Lisp_Object frame) { struct frame *f = XFRAME (frame); @@ -619,22 +615,27 @@ unwind_create_frame (Lisp_Object frame) display is disconnected after the frame has become official, but before x_create_frame removes the unwind protect. */ if (!FRAME_LIVE_P (f)) - return; + return Qnil; /* If frame is ``official'', nothing to do. */ if (NILP (Fmemq (frame, Vframe_list))) { haiku_free_frame_resources (f); free_glyphs (f); + return Qt; } + + return Qnil; } static void unwind_create_tip_frame (Lisp_Object frame) { - unwind_create_frame (frame); - tip_window = NULL; - tip_frame = Qnil; + Lisp_Object deleted; + + deleted = unwind_create_frame (frame); + if (deleted) + tip_frame = Qnil; } static unsigned long @@ -673,6 +674,12 @@ haiku_set_foreground_color (struct frame *f, Lisp_Object arg, Lisp_Object oldval } } +static void +do_unwind_create_frame (Lisp_Object frame) +{ + unwind_create_frame (frame); +} + static Lisp_Object haiku_create_frame (Lisp_Object parms) { @@ -759,7 +766,7 @@ haiku_create_frame (Lisp_Object parms) FRAME_DISPLAY_INFO (f) = dpyinfo; /* With FRAME_DISPLAY_INFO set up, this unwind-protect is safe. */ - record_unwind_protect (unwind_create_frame, frame); + record_unwind_protect (do_unwind_create_frame, frame); /* Set the name; the functions to which we pass f expect the name to be set. */ @@ -2504,12 +2511,12 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, break; } else - tip_last_parms = - calln (Qassq_delete_all, parm, tip_last_parms); + tip_last_parms + = calln (Qassq_delete_all, parm, tip_last_parms); } else - tip_last_parms = - calln (Qassq_delete_all, parm, tip_last_parms); + tip_last_parms + = calln (Qassq_delete_all, parm, tip_last_parms); } /* Now check if every parameter in what is left of diff --git a/src/nsfns.m b/src/nsfns.m index a2c50468cd1..9f52777879c 100644 --- a/src/nsfns.m +++ b/src/nsfns.m @@ -66,10 +66,6 @@ Updated by Christian Limpach (chris@nice.ch) /* The X and Y deltas of the last call to `x-show-tip'. */ static Lisp_Object tip_dx, tip_dy; -/* The window-system window corresponding to the frame of the - currently visible tooltip. */ -static NSWindow *tip_window; - /* A timer that hides or deletes the currently visible tooltip when it fires. */ static Lisp_Object tip_timer; @@ -2959,10 +2955,7 @@ Frames are listed from topmost (first) to bottommost (last). */) deleted = unwind_create_frame (frame); if (EQ (deleted, Qt)) - { - tip_window = NULL; - tip_frame = Qnil; - } + tip_frame = Qnil; } /* Create a frame for a tooltip on the display described by DPYINFO. diff --git a/src/pgtkfns.c b/src/pgtkfns.c index 73cd6e5695d..6231ef1cb48 100644 --- a/src/pgtkfns.c +++ b/src/pgtkfns.c @@ -2646,7 +2646,7 @@ unwind_create_tip_frame (Lisp_Object frame) when this happens. */ static Lisp_Object -x_create_tip_frame (struct pgtk_display_info *dpyinfo, Lisp_Object parms, struct frame *p) +pgtk_create_tip_frame (struct pgtk_display_info *dpyinfo, Lisp_Object parms, struct frame *p) { struct frame *f; Lisp_Object frame; @@ -3264,10 +3264,13 @@ Text larger than the specified size is clipped. */) /* Create a frame for the tooltip, and record it in the global variable tip_frame. */ - if (NILP (tip_frame = x_create_tip_frame (FRAME_DISPLAY_INFO (f), parms, f))) + if (NILP ((tip_frame = pgtk_create_tip_frame (FRAME_DISPLAY_INFO (f), + parms, f)))) /* Creating the tip frame failed. */ return unbind_to (count, Qnil); } + else + tip_window = FRAME_X_WINDOW (XFRAME (tip_frame)); tip_f = XFRAME (tip_frame); window = FRAME_ROOT_WINDOW (tip_f); diff --git a/src/w32fns.c b/src/w32fns.c index 89a4d46240a..17221b73470 100644 --- a/src/w32fns.c +++ b/src/w32fns.c @@ -7931,6 +7931,11 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0, return unbind_to (count, Qnil); } } + else + /* Required by X11 drag and drop, and left here in the interests of + consistency and in the event drag and drop should be implemented + on W32. */ + tip_window = FRAME_W32_WINDOW (XFRAME (tip_frame)); tip_f = XFRAME (tip_frame); window = FRAME_ROOT_WINDOW (tip_f); diff --git a/src/xfns.c b/src/xfns.c index ec7d54180e4..d8a3ce0b3b1 100644 --- a/src/xfns.c +++ b/src/xfns.c @@ -9109,12 +9109,12 @@ Text larger than the specified size is clipped. */) break; } else - tip_last_parms = - calln (Qassq_delete_all, parm, tip_last_parms); + tip_last_parms + = calln (Qassq_delete_all, parm, tip_last_parms); } else - tip_last_parms = - calln (Qassq_delete_all, parm, tip_last_parms); + tip_last_parms + = calln (Qassq_delete_all, parm, tip_last_parms); } /* Now check if every parameter in what is left of @@ -9166,6 +9166,9 @@ Text larger than the specified size is clipped. */) /* Creating the tip frame failed. */ return unbind_to (count, Qnil); } + else + /* Required by X11 drag and drop. */ + tip_window = FRAME_X_WINDOW (XFRAME (tip_frame)); tip_f = XFRAME (tip_frame); window = FRAME_ROOT_WINDOW (tip_f);