all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Juri Linkov <juri@linkov.net>
To: Eli Zaretskii <eliz@gnu.org>
Cc: casouri@gmail.com, 68824@debbugs.gnu.org
Subject: bug#68824: treesitter support for outline-minor-mode
Date: Thu, 08 Feb 2024 19:20:17 +0200	[thread overview]
Message-ID: <86v86z9e4u.fsf@mail.linkov.net> (raw)
In-Reply-To: <86zfwbwe7a.fsf@gnu.org> (Eli Zaretskii's message of "Thu, 08 Feb 2024 10:30:17 +0200")

[-- Attachment #1: Type: text/plain, Size: 2376 bytes --]

>> --- a/doc/emacs/text.texi
>> +++ b/doc/emacs/text.texi
>> @@ -1097,6 +1097,11 @@ Outline Format
>>  chapters.  This works as long as no other command starts with
>>  @samp{@@chap}.
>>
>> +@vindex outline-search-function
>> +  Instead of setting the variable @code{outline-regexp}, you can set
>> +the variable @code{outline-search-function} to a function that
>> +matches the current heading and searches for the next one.
>
> This should tell which arguments will be passed to the function, and
> what it is expected to do and return.

This is the Emacs User Manual, not the Emacs Lisp Reference Manual.
Are you sure that arguments should be described here?

>> +(defvar-local treesit-outline-predicate nil
>> +  "Predicate used to find outline headings in the syntax 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.")
>
> The doc string should explain with what arguments will the predicate
> be called and when it is expected to return non-nil.

Fixed.

>> +(defun treesit-outline-predicate-from-imenu (node)
>> +  "Return an outline searching predicate created from Imenu.
>> +Return the value suitable to set `treesit-outline-predicate'.
>> +Create this predicate from the value `treesit-simple-imenu-settings'
>> +that major modes set to find Imenu entries.  The assumption here
>> +is that the positions of Imenu entries most of the time coincide
>> +with the lines of outline headings.  When this assumption fails,
>> +you can directly set a proper value to `treesit-outline-predicate'."
>
> Likewise here: please describe in the doc string the signature and
> expectations from the predicate returned by this function (or point to
> the doc of some other variable/function where that information is
> included).

Actually, this is an internal function and doesn't need a docstring at all.
So I added two slashes to its name, and demoted its docstring to comments.

>> +(defun treesit-outline-search (&optional bound move backward looking-at)
>> +  "Search for the next outline heading in the syntax tree.
>> +See the descriptions of arguments in `outline-search-function'."
>
> What about the return value (if it is important)?

The return value is described in the docstring of
`outline-search-function' where the docstring above links to.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: treesit-outline-predicate.patch --]
[-- Type: text/x-diff, Size: 10106 bytes --]

diff --git a/doc/emacs/text.texi b/doc/emacs/text.texi
index 338bf014208..48a70c171dd 100644
--- a/doc/emacs/text.texi
+++ b/doc/emacs/text.texi
@@ -1097,6 +1097,11 @@ Outline Format
 chapters.  This works as long as no other command starts with
 @samp{@@chap}.
 
+@vindex outline-search-function
+  Instead of setting the variable @code{outline-regexp}, you can set
+the variable @code{outline-search-function} to a function that
+matches the current heading and searches for the next one.
+
 @vindex outline-level
   You can explicitly specify a rule for calculating the level of a
 heading line by setting the variable @code{outline-level}.  The value
diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi
index ac11f88ae4d..7dd5784686b 100644
--- a/doc/lispref/parsing.texi
+++ b/doc/lispref/parsing.texi
@@ -1895,6 +1895,11 @@ Tree-sitter Major Modes
 @item
 If @code{treesit-simple-imenu-settings} (@pxref{Imenu}) is
 non-@code{nil}, it sets up Imenu.
+
+@vindex treesit-outline-predicate
+@item
+If @code{treesit-outline-predicate} is non-@code{nil}, it sets up
+Outline Minor Mode (@pxref{Outline Minor Mode,,, emacs, The GNU Emacs Manual}).
 @end itemize
 
 @c TODO: Add treesit-thing-settings stuff once we finalize it.
diff --git a/etc/NEWS b/etc/NEWS
index f980d612a57..93e023b8ffc 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -130,6 +130,13 @@ 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 6a485ae591a..25ac582276b 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -2860,6 +2860,71 @@ treesit-simple-imenu
                     index))))
             treesit-simple-imenu-settings)))
 
+;;; Outline minor mode
+
+(defvar-local treesit-outline-predicate nil
+  "Predicate used to find outline headings in the syntax tree.
+The predicate can be a function, a regexp matching node type,
+and more; see docstring of `treesit-thing-settings'.
+It matches the nodes located on lines with outline headings.
+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.")
+
+(defun treesit-outline-predicate--from-imenu (node)
+  ;; Return an outline searching predicate created from Imenu.
+  ;; Return the value suitable to set `treesit-outline-predicate'.
+  ;; Create this predicate from the value `treesit-simple-imenu-settings'
+  ;; that major modes set to find Imenu entries.  The assumption here
+  ;; is that the positions of Imenu entries most of the time coincide
+  ;; with the lines of outline headings.  When this assumption fails,
+  ;; you can directly set a proper value to `treesit-outline-predicate'.
+  (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))
+
+(defun treesit-outline-search (&optional bound move backward looking-at)
+  "Search for the next outline heading in the syntax tree.
+See the descriptions of arguments in `outline-search-function'."
+  (if looking-at
+      (when-let* ((node (or (treesit--thing-at (pos-eol) treesit-outline-predicate)
+                            (treesit--thing-at (pos-bol) treesit-outline-predicate)))
+                  (start (treesit-node-start node)))
+        (eq (pos-bol) (save-excursion (goto-char start) (pos-bol))))
+
+    (let* ((pos
+            ;; When function wants to find the current outline, point
+            ;; is at the beginning of the current line.  When it wants
+            ;; to find the next outline, point is at the second column.
+            (if (eq (point) (pos-bol))
+                (if (bobp) (point) (1- (point)))
+              (pos-eol)))
+           (found (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))))
+
+(defun treesit-outline-level ()
+  "Return the depth of the current outline heading."
+  (let* ((node (treesit-node-at (point)))
+         (level (if (treesit-node-match-p node treesit-outline-predicate t)
+                    1 0)))
+    (while (setq node (treesit-parent-until node treesit-outline-predicate))
+      (setq level (1+ level)))
+    (if (zerop level) 1 level)))
+
 ;;; Activating tree-sitter
 
 (defun treesit-ready-p (language &optional quiet)
@@ -2990,6 +3055,17 @@ 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))))
+    (unless treesit-outline-predicate
+      (setq treesit-outline-predicate
+            #'treesit-outline-predicate--from-imenu))
+    (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/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el
index e5835bdb62d..c4b48f03d12 100644
--- a/lisp/progmodes/c-ts-mode.el
+++ b/lisp/progmodes/c-ts-mode.el
@@ -922,6 +922,17 @@ c-ts-mode--defun-name
         name)))
    t))
 
+;;; Outline minor mode
+
+(defun c-ts-mode--outline-predicate (node)
+  "Match outlines on lines with function names."
+  (and (treesit-node-match-p
+        node "\\`function_declarator\\'" t)
+       (when-let ((parent (treesit-node-parent node)))
+         (treesit-node-match-p
+          parent
+          "\\`function_definition\\'" t))))
+
 ;;; Defun navigation
 
 (defun c-ts-mode--defun-valid-p (node)
@@ -1259,6 +1270,10 @@ c-ts-base-mode
                                 eos)
                    c-ts-mode--defun-for-class-in-imenu-p nil))))
 
+  ;; Outline minor mode
+  (setq-local treesit-outline-predicate
+              #'c-ts-mode--outline-predicate)
+
   (setq-local treesit-font-lock-feature-list
               c-ts-mode--feature-list))
 
diff --git a/lisp/progmodes/heex-ts-mode.el b/lisp/progmodes/heex-ts-mode.el
index 7b53a44deb2..22e8956661d 100644
--- a/lisp/progmodes/heex-ts-mode.el
+++ b/lisp/progmodes/heex-ts-mode.el
@@ -166,6 +166,16 @@ heex-ts-mode
                   ("Slot" "\\`slot\\'" nil nil)
                   ("Tag" "\\`tag\\'" nil nil)))
 
+    ;; Outline minor mode
+    ;; `heex-ts-mode' inherits from `html-mode' that sets
+    ;; regexp-based outline variables.  So need to restore
+    ;; the default values of outline variables to be able
+    ;; to use `treesit-outline-predicate' derived
+    ;; from `treesit-simple-imenu-settings' above.
+    (kill-local-variable 'outline-heading-end-regexp)
+    (kill-local-variable 'outline-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/progmodes/lua-ts-mode.el b/lisp/progmodes/lua-ts-mode.el
index 05a3ff6d7c6..dc2a8fcec1e 100644
--- a/lisp/progmodes/lua-ts-mode.el
+++ b/lisp/progmodes/lua-ts-mode.el
@@ -774,7 +774,7 @@ lua-ts-mode
                                       "vararg_expression"))))
                    (text "comment"))))
 
-    ;; Imenu.
+    ;; Imenu/Outline.
     (setq-local treesit-simple-imenu-settings
                 `(("Requires"
                    "\\`function_call\\'"
@@ -789,16 +789,6 @@ lua-ts-mode
     ;; Which-function.
     (setq-local which-func-functions (treesit-defun-at-point))
 
-    ;; Outline.
-    (setq-local outline-regexp
-                (rx (seq (0+ space)
-                         (or (seq "--[[" (0+ space) eol)
-                             (seq symbol-start
-                                  (or "do" "for" "if" "repeat" "while"
-                                      (seq (? (seq "local" (1+ space)))
-                                           "function"))
-                                  symbol-end)))))
-
     ;; Align.
     (setq-local align-indent-before-aligning t)
 
diff --git a/lisp/textmodes/html-ts-mode.el b/lisp/textmodes/html-ts-mode.el
index 301f3e8791c..9af2aa6748f 100644
--- a/lisp/textmodes/html-ts-mode.el
+++ b/lisp/textmodes/html-ts-mode.el
@@ -121,6 +121,17 @@ html-ts-mode
   ;; Imenu.
   (setq-local treesit-simple-imenu-settings
               '(("Element" "\\`tag_name\\'" nil nil)))
+
+  ;; Outline minor mode.
+  (setq-local treesit-outline-predicate "\\`element\\'")
+  ;; `html-ts-mode' inherits from `html-mode' that sets
+  ;; regexp-based outline variables.  So need to restore
+  ;; the default values of outline variables to be able
+  ;; to use `treesit-outline-predicate' above.
+  (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)

  reply	other threads:[~2024-02-08 17:20 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-01-30 17:37 bug#68824: treesitter support for outline-minor-mode Juri Linkov
2024-01-30 18:46 ` john muhl via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-01-31  7:20   ` Juri Linkov
2024-01-30 19:21 ` Eli Zaretskii
2024-01-31  7:32   ` Juri Linkov
2024-02-01 17:12     ` Juri Linkov
2024-02-02  0:34       ` Yuan Fu
2024-02-02  7:05         ` Eli Zaretskii
2024-02-02  7:27           ` Yuan Fu
2024-02-02  7:34             ` Eli Zaretskii
2024-02-02  7:53         ` Juri Linkov
2024-02-04 17:15         ` Juri Linkov
2024-02-05  4:34           ` Yuan Fu
2024-02-05  7:22             ` Juri Linkov
2024-02-06  7:21               ` Yuan Fu
2024-02-08  7:40     ` Juri Linkov
2024-02-08  8:30       ` Eli Zaretskii
2024-02-08 17:20         ` Juri Linkov [this message]
2024-02-08 18:56           ` Eli Zaretskii
2024-02-09  7:07             ` Juri Linkov
2024-02-09 19:25               ` Eli Zaretskii
2024-02-10 17:29                 ` Juri Linkov
2024-02-10 17:57                   ` Eli Zaretskii
2024-02-11 17:34                     ` Juri Linkov
2024-02-11 19:22                       ` Eli Zaretskii
2024-02-12  1:07                         ` Yuan Fu
2024-02-12 12:59                           ` Eli Zaretskii
2024-02-12 18:16                             ` Juri Linkov
2024-02-13 17:02                               ` Juri Linkov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=86v86z9e4u.fsf@mail.linkov.net \
    --to=juri@linkov.net \
    --cc=68824@debbugs.gnu.org \
    --cc=casouri@gmail.com \
    --cc=eliz@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.