From: Juri Linkov Date: Fri, 21 Feb 2025 07:55:54 +0000 (+0200) Subject: New variable 'treesit-aggregated-outline-predicate' (bug#76398) X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=eee4c895962f9b2911bcecc17fdb89caaebb4141;p=emacs.git New variable 'treesit-aggregated-outline-predicate' (bug#76398) * doc/lispref/modes.texi (Outline Minor Mode): Add 'treesit-aggregated-outline-predicate'. * lisp/treesit.el (treesit-aggregated-outline-predicate): New buffer-local variable. (treesit-outline--at-point): Use 'treesit-aggregated-outline-predicate'. (treesit-closest-parser-boundary): New function. (treesit-outline-search): Use 'treesit-aggregated-outline-predicate' and 'treesit-closest-parser-boundary'. (treesit-outline-level): Use 'treesit-aggregated-outline-predicate'. (treesit-major-mode-setup): Add 'treesit-aggregated-outline-predicate'. * lisp/textmodes/html-ts-mode.el (html-ts-mode--outline-predicate): Improve. * lisp/textmodes/mhtml-ts-mode.el (mhtml-ts-mode): Set 'treesit-aggregated-outline-predicate'. (cherry picked from commit 840be8a7d8e7db8ad7f186678226ac51712724ab) --- diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi index e9833c301af..8c902d6a935 100644 --- a/doc/lispref/modes.texi +++ b/doc/lispref/modes.texi @@ -3139,6 +3139,16 @@ This variable instructs Emacs how to find lines with outline headings. It should be a predicate that matches the node on the heading line. @end defvar +@defvar treesit-aggregated-outline-predicate +This variable allows major modes to configure outlines for multiple +languages. Its value is an alist mapping language symbols to outline +headings of the form described above for the value of +@code{treesit-outline-predicate}. + +If this variable is non-@code{nil}, it overrides +@code{treesit-outline-predicate} for setting up outline headings. +@end defvar + @node Font Lock Mode @section Font Lock Mode @cindex Font Lock mode diff --git a/lisp/textmodes/html-ts-mode.el b/lisp/textmodes/html-ts-mode.el index 26efe1be726..f6d20685ea3 100644 --- a/lisp/textmodes/html-ts-mode.el +++ b/lisp/textmodes/html-ts-mode.el @@ -126,14 +126,15 @@ Return nil if there is no name or if NODE is not a defun node." t))) (defun html-ts-mode--outline-predicate (node) - "Limit outlines to a few most meaningful elements." - (let ((name (html-ts-mode--defun-name node))) - (and name (string-match-p - (rx bos (or "html" "head" "script" "style" - "body" (and "h" (any "1-6")) - "ol" "ul" "table") - eos) - name)))) + "Limit outlines to multi-line elements." + (when (string-match-p "element" (treesit-node-type node)) + (< (save-excursion + (goto-char (treesit-node-start node)) + (pos-bol)) + (save-excursion + (goto-char (treesit-node-end node)) + (skip-chars-backward " \t\n") + (pos-bol))))) ;;;###autoload (define-derived-mode html-ts-mode html-mode "HTML" diff --git a/lisp/textmodes/mhtml-ts-mode.el b/lisp/textmodes/mhtml-ts-mode.el index 83f8879f427..190967fcab7 100644 --- a/lisp/textmodes/mhtml-ts-mode.el +++ b/lisp/textmodes/mhtml-ts-mode.el @@ -580,7 +580,11 @@ Powered by tree-sitter." (setq-local treesit-aggregated-simple-imenu-settings mhtml-ts-mode--treesit-aggregated-simple-imenu-settings) - ;; (setq-local treesit-outline-predicate nil) + (setq-local treesit-aggregated-outline-predicate + `((html . ,#'html-ts-mode--outline-predicate) + ;; TODO: add a predicate like for html above + (javascript . "\\`function_declaration\\'") + (css . "\\`rule_set\\'"))) (treesit-major-mode-setup) diff --git a/lisp/treesit.el b/lisp/treesit.el index 02170d34f29..9ef8a1a609b 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -3594,6 +3594,16 @@ Intended to be set by a major mode. When nil, the predicate is constructed from the value of `treesit-simple-imenu-settings' when a major mode sets it.") +(defvar-local treesit-aggregated-outline-predicate nil + "Settings that configure `treesit-outline-search' for multi-language modes. + +The value should be an alist of (LANG . SETTINGS), where LANG is a +language symbol, and SETTINGS has the same form as +`treesit-outline-predicate'. + +When both this variable and `treesit-outline-predicate' are non-nil, +this variable takes priority.") + (defun treesit-outline-predicate--from-imenu (node) ;; Return an outline searching predicate created from Imenu. ;; Return the value suitable to set `treesit-outline-predicate'. @@ -3611,7 +3621,10 @@ when a major mode sets it.") (defun treesit-outline--at-point () "Return the outline heading node at the current line." - (let* ((pred treesit-outline-predicate) + (let* ((pred (if treesit-aggregated-outline-predicate + (alist-get (treesit-language-at (point)) + treesit-aggregated-outline-predicate) + treesit-outline-predicate)) (bol (pos-bol)) (eol (pos-eol)) (current (treesit-thing-at (point) pred)) @@ -3623,6 +3636,18 @@ when a major mode sets it.") (or (and current-valid current) (and next-valid (treesit-thing-at next pred))))) +(defun treesit-closest-parser-boundary (pos backward) + "Get the closest boundary of a local parser." + (when-let* ((ranges (mapcar #'treesit-parser-included-ranges + (treesit-parser-list))) + (ranges (delq nil (delete '((1 . 1)) ranges))) + (bounds (seq-filter + (lambda (p) (if backward (< p pos) (> p pos))) + (flatten-list ranges))) + (closest (when bounds + (if backward (seq-max bounds) (seq-min bounds))))) + closest)) + (defun treesit-outline-search (&optional bound move backward looking-at) "Search for the next outline heading in the syntax tree. For BOUND, MOVE, BACKWARD, LOOKING-AT, see the descriptions in @@ -3642,28 +3667,66 @@ For BOUND, MOVE, BACKWARD, LOOKING-AT, see the descriptions in (if (eq (point) (pos-bol)) (if (bobp) (point) (1- (point))) (pos-eol)))) + (pred (if treesit-aggregated-outline-predicate + (alist-get (treesit-language-at pos) + treesit-aggregated-outline-predicate) + treesit-outline-predicate)) (found (or bob-pos - (treesit-navigate-thing pos (if backward -1 1) 'beg - treesit-outline-predicate)))) - (if found - (if (or (not bound) (if backward (>= found bound) (<= found bound))) - (progn - (goto-char found) - (goto-char (pos-bol)) - (set-match-data (list (point) (pos-eol))) - t) - (when move (goto-char bound)) - nil) - (when move (goto-char (or bound (if backward (point-min) (point-max))))) - nil)))) + (treesit-navigate-thing pos (if backward -1 1) 'beg pred))) + (closest (treesit-closest-parser-boundary pos backward))) + + ;; Handle multi-language modes + (if (and closest + (or + ;; Possibly was inside the local parser, and when can't find + ;; more matches inside it then need to go over the closest + ;; parser boundary to the primary parser. + (not found) + ;; Possibly skipped the local parser, either while navigating + ;; inside the primary parser, or inside a local parser + ;; interspersed by ranges of other local parsers, e.g. + ;;