From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Juri Linkov Newsgroups: gmane.emacs.bugs Subject: bug#68824: treesitter support for outline-minor-mode Date: Tue, 30 Jan 2024 19:37:20 +0200 Organization: LINKOV.NET Message-ID: <86le8667u7.fsf@mail.linkov.net> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="32798"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/30.0.50 (x86_64-pc-linux-gnu) Cc: Yuan Fu To: 68824@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Tue Jan 30 18:42:12 2024 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1rUs7f-0008Ne-Jq for geb-bug-gnu-emacs@m.gmane-mx.org; Tue, 30 Jan 2024 18:42:11 +0100 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1rUs7Q-0004vl-Lu; Tue, 30 Jan 2024 12:41:56 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rUs7P-0004uZ-89 for bug-gnu-emacs@gnu.org; Tue, 30 Jan 2024 12:41:55 -0500 Original-Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1rUs7N-0000zL-V9 for bug-gnu-emacs@gnu.org; Tue, 30 Jan 2024 12:41:53 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1rUs7W-0000B3-Pg; Tue, 30 Jan 2024 12:42:02 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Juri Linkov Original-Sender: "Debbugs-submit" Resent-CC: casouri@gmail.com, bug-gnu-emacs@gnu.org Resent-Date: Tue, 30 Jan 2024 17:42:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 68824 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch X-Debbugs-Original-To: bug-gnu-emacs@gnu.org X-Debbugs-Original-Xcc: Yuan Fu Original-Received: via spool by submit@debbugs.gnu.org id=B.1706636467608 (code B ref -1); Tue, 30 Jan 2024 17:42:02 +0000 Original-Received: (at submit) by debbugs.gnu.org; 30 Jan 2024 17:41:07 +0000 Original-Received: from localhost ([127.0.0.1]:36646 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rUs6c-00009j-Dn for submit@debbugs.gnu.org; Tue, 30 Jan 2024 12:41:06 -0500 Original-Received: from lists.gnu.org ([2001:470:142::17]:47966) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rUs6a-00008z-H9 for submit@debbugs.gnu.org; Tue, 30 Jan 2024 12:41:05 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rUs6M-0004MR-6e for bug-gnu-emacs@gnu.org; Tue, 30 Jan 2024 12:40:50 -0500 Original-Received: from relay8-d.mail.gandi.net ([2001:4b98:dc4:8::228]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rUs6J-0000py-Kc for bug-gnu-emacs@gnu.org; Tue, 30 Jan 2024 12:40:49 -0500 Original-Received: by mail.gandi.net (Postfix) with ESMTPSA id 4C5B21BF204 for ; Tue, 30 Jan 2024 17:40:41 +0000 (UTC) X-GND-Sasl: juri@linkov.net Received-SPF: pass client-ip=2001:4b98:dc4:8::228; envelope-from=juri@linkov.net; helo=relay8-d.mail.gandi.net X-Spam_score_int: -25 X-Spam_score: -2.6 X-Spam_bar: -- X-Spam_report: (-2.6 / 5.0 requ) BAYES_00=-1.9, RCVD_IN_DNSWL_LOW=-0.7, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.bugs:279193 Archived-At: --=-=-= Content-Type: text/plain Tags: patch As discussed on https://lists.gnu.org/archive/html/emacs-devel/2024-01/msg00916.html here is the patch that adds the support for outline-minor-mode to treesit.el. It has been tested on c-ts-mode, dockerfile-ts-mode, elixir-ts-mode, heex-ts-mode, java-ts-mode, js-ts-mode, typescript-ts-mode, css-ts-mode, html-ts-mode, toml-ts-mode. --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=treesit-outline-search.patch diff --git a/etc/NEWS b/etc/NEWS index a9d6eb6789d..6ceaa6b7b6c 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -130,6 +130,12 @@ the signature) the automatically inferred function type as well. This user option controls outline visibility in the output buffer of 'describe-bindings' when 'describe-bindings-outline' is non-nil. +** Outline Mode + +*** 'outline-minor-mode' is supported in tree-sitter major modes. +It can be used in all tree-sitter major modes that set either the +variable 'treesit-simple-imenu-settings' or 'treesit-outline-predicate'. + ** X selection requests are now handled much faster and asynchronously. This means it should be less necessary to disable the likes of 'select-active-regions' when Emacs is running over a slow network diff --git a/lisp/treesit.el b/lisp/treesit.el index 96222ed81cb..13319bb0483 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -2836,6 +2836,99 @@ treesit-simple-imenu index)))) treesit-simple-imenu-settings))) +;;; Outline minor mode + +(defvar-local treesit-outline-predicate nil + "Predicate used to find outline headings in a sparse tree. +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-outline-levels nil + "Holds a cached structure that corresponds to the outline tree. +It's a list of (MARKER . LEVEL) where MARKER is a position of the +beginning of the outline heading, and LEVEL is its depth in the +outline tree.") + +(defun treesit-outline-levels (node level) + "Given a sparse tree, return a list for `treesit-outline-levels'." + (let* ((ts-node (car node)) + (children (cdr node)) + (subtrees (mapcan (lambda (node) + (treesit-outline-levels node (1+ level))) + children)) + (marker (when ts-node + (set-marker (make-marker) + (save-excursion + (goto-char (treesit-node-start ts-node)) + (search-forward (or (treesit-defun-name ts-node) "")) + (pos-bol)))))) + (cond + ((null ts-node) + subtrees) + (subtrees + (cons (cons marker level) subtrees)) + (t + (list (cons marker level)))))) + +(defun treesit-outline-prepare () + "Prepare `treesit-outline-levels' to be used by `treesit-outline-search'. +Build the internal structure based either on the value +`treesit-outline-predicate' that should be a predicate for +`treesit-induce-sparse-tree', or use the existing value of +`treesit-simple-imenu-settings' where outline headings are +on the same lines as the imenu items." + (unless treesit-outline-predicate + (setq treesit-outline-predicate + (lambda (node) + (seq-some + (lambda (setting) + (and (string-match-p (nth 1 setting) (treesit-node-type node)) + (or (null (nth 2 setting)) + (funcall (nth 2 setting) node)))) + treesit-simple-imenu-settings)))) + (setq treesit-outline-levels + (treesit-outline-levels + (treesit-induce-sparse-tree + (treesit-buffer-root-node) + treesit-outline-predicate) + 0))) + +(defun treesit-outline-search (&optional bound move backward looking-at) + "Search for the next outline heading. +See the descriptions of arguments in `outline-search-function'. +Uses the value of `treesit-outline-levels' prepared by +`treesit-outline-prepare'." + (unless treesit-outline-levels + (treesit-outline-prepare)) + + (let ((positions (mapcar #'car treesit-outline-levels))) + (if looking-at + (when (member (point-marker) positions) + (set-match-data (list (pos-bol) (pos-eol))) + t) + + (let ((found (seq-find (lambda (p) (>= p (point))) + (if backward (nreverse positions) positions)))) + (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))))) + +(defun treesit-outline-level () + "Return the depth of the current outline heading. +Uses the value of `treesit-outline-levels'." + (or (alist-get (point) treesit-outline-levels nil nil + (lambda (m k) (eq (marker-position m) k))) + 1)) + ;;; Activating tree-sitter (defun treesit-ready-p (language &optional quiet) @@ -2966,6 +3059,14 @@ treesit-major-mode-setup (setq-local imenu-create-index-function #'treesit-simple-imenu)) + ;; Outline minor mode. + (when (and (or treesit-outline-predicate treesit-simple-imenu-settings) + (not (seq-some #'local-variable-p + '(outline-search-function + outline-regexp outline-level)))) + (setq-local outline-search-function #'treesit-outline-search + outline-level #'treesit-outline-level)) + ;; Remove existing local parsers. (dolist (ov (overlays-in (point-min) (point-max))) (when-let ((parser (overlay-get ov 'treesit-parser))) diff --git a/lisp/progmodes/heex-ts-mode.el b/lisp/progmodes/heex-ts-mode.el index 7b53a44deb2..23e2afbaaca 100644 --- a/lisp/progmodes/heex-ts-mode.el +++ b/lisp/progmodes/heex-ts-mode.el @@ -166,6 +166,12 @@ heex-ts-mode ("Slot" "\\`slot\\'" nil nil) ("Tag" "\\`tag\\'" nil nil))) + ;; Outline minor mode + ;; Restore default value for `treesit-outline-search'. + (kill-local-variable 'outline-regexp) + (kill-local-variable 'outline-heading-end-regexp) + (kill-local-variable 'outline-level) + (setq-local treesit-font-lock-settings heex-ts--font-lock-settings) (setq-local treesit-simple-indent-rules heex-ts--indent-rules) diff --git a/lisp/textmodes/html-ts-mode.el b/lisp/textmodes/html-ts-mode.el index 301f3e8791c..f157d2d6949 100644 --- a/lisp/textmodes/html-ts-mode.el +++ b/lisp/textmodes/html-ts-mode.el @@ -121,6 +121,16 @@ html-ts-mode ;; Imenu. (setq-local treesit-simple-imenu-settings '(("Element" "\\`tag_name\\'" nil nil))) + + ;; Outline minor mode. + ;; Override default predicate to use "element" for outline headings + ;; instead of "tag_name" from `treesit-simple-imenu-settings'. + (setq-local treesit-outline-predicate "\\`element\\'") + ;; Restore default value for `treesit-outline-search'. + (kill-local-variable 'outline-regexp) + (kill-local-variable 'outline-heading-end-regexp) + (kill-local-variable 'outline-level) + (treesit-major-mode-setup)) (if (treesit-ready-p 'html) --=-=-=--