emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master 3b90e5052ce: Tree-sitter support for outline-minor-mode (bug#6882


From: Juri Linkov
Subject: master 3b90e5052ce: Tree-sitter support for outline-minor-mode (bug#68824)
Date: Mon, 12 Feb 2024 13:19:05 -0500 (EST)

branch: master
commit 3b90e5052ce1eea47430c85c0c35741e25269ce2
Author: Juri Linkov <juri@linkov.net>
Commit: Juri Linkov <juri@linkov.net>

    Tree-sitter support for outline-minor-mode (bug#68824)
    
    * doc/emacs/text.texi (Outline Format): Add 'outline-search-function'.
    
    * doc/lispref/elisp.texi (Top): Add new menu item "Outline Minor Mode"
    after "Imenu".
    
    * doc/lispref/modes.texi (Modes): Add new menu item "Outline Minor Mode"
    after "Imenu".
    (Major Mode Conventions): Mention "Outline Minor Mode" with @pxref.
    (Outline Minor Mode): New node.
    
    * doc/lispref/parsing.texi (Tree-sitter Major Modes): Mention
    'treesit-outline-predicate' with @pxref.
    
    * lisp/treesit.el (treesit-outline-predicate): New buffer-local variable.
    (treesit-outline-predicate--from-imenu): New internal function.
    (treesit-outline-search, treesit-outline-level): New functions.
    (treesit-major-mode-setup): Set up treesit-outline-predicate,
    outline-search-function and outline-level.
    
    * lisp/progmodes/c-ts-mode.el (c-ts-mode--outline-predicate):
    New internal function.
    (c-ts-base-mode): Set 'treesit-outline-predicate' to
    'c-ts-mode--outline-predicate'.
    
    * lisp/progmodes/heex-ts-mode.el (heex-ts-mode): Kill inherited
    local variables 'outline-heading-end-regexp', 'outline-regexp',
    'outline-level'.
    
    * lisp/progmodes/lua-ts-mode.el (lua-ts-mode): Remove 'outline-regexp'.
    Suggested by john muhl <jm@pub.pink>.
    
    * lisp/textmodes/html-ts-mode.el (html-ts-mode): Kill inherited
    local variables 'outline-heading-end-regexp', 'outline-regexp',
    'outline-level'.
---
 doc/emacs/text.texi            |  6 ++++
 doc/lispref/elisp.texi         |  1 +
 doc/lispref/modes.texi         | 62 ++++++++++++++++++++++++++++++++++
 doc/lispref/parsing.texi       |  4 +++
 etc/NEWS                       |  7 ++++
 lisp/progmodes/c-ts-mode.el    | 15 +++++++++
 lisp/progmodes/heex-ts-mode.el | 10 ++++++
 lisp/progmodes/lua-ts-mode.el  | 12 +------
 lisp/textmodes/html-ts-mode.el | 11 ++++++
 lisp/treesit.el                | 76 ++++++++++++++++++++++++++++++++++++++++++
 10 files changed, 193 insertions(+), 11 deletions(-)

diff --git a/doc/emacs/text.texi b/doc/emacs/text.texi
index 338bf014208..cb347d59948 100644
--- a/doc/emacs/text.texi
+++ b/doc/emacs/text.texi
@@ -1097,6 +1097,12 @@ so that Outline mode will know that sections are 
contained in
 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
+(@pxref{Outline Minor Mode,,,elisp, the Emacs Lisp Reference Manual}).
+
 @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/elisp.texi b/doc/lispref/elisp.texi
index cab1622337e..ed254795d90 100644
--- a/doc/lispref/elisp.texi
+++ b/doc/lispref/elisp.texi
@@ -883,6 +883,7 @@ Major and Minor Modes
 * Minor Modes::        Defining minor modes.
 * Mode Line Format::   Customizing the text that appears in the mode line.
 * Imenu::              Providing a menu of definitions made in a buffer.
+* Outline Minor Mode:: Outline mode to use with other major modes.
 * Font Lock Mode::     How modes can highlight text according to syntax.
 * Auto-Indentation::   How to teach Emacs to indent for a major mode.
 * Desktop Save Mode::  How modes can have buffer state saved between
diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi
index 1d961249633..70d1a40f836 100644
--- a/doc/lispref/modes.texi
+++ b/doc/lispref/modes.texi
@@ -25,6 +25,7 @@ user.  For related topics such as keymaps and syntax tables, 
see
 * Minor Modes::       Defining minor modes.
 * Mode Line Format::  Customizing the text that appears in the mode line.
 * Imenu::             Providing a menu of definitions made in a buffer.
+* Outline Minor Mode::  Outline mode to use with other major modes.
 * Font Lock Mode::    How modes can highlight text according to syntax.
 * Auto-Indentation::  How to teach Emacs to indent for a major mode.
 * Desktop Save Mode:: How modes can have buffer state saved between
@@ -507,6 +508,12 @@ variable @code{imenu-generic-expression}, for the two 
variables
 @code{imenu-extract-index-name-function}, or for the variable
 @code{imenu-create-index-function} (@pxref{Imenu}).
 
+@item
+The mode should specify how Outline minor mode should find the
+heading lines, by setting up a buffer-local value for the variables
+@code{outline-regexp} or @code{outline-search-function}, and also
+for the variable @code{outline-level} (@pxref{Outline Minor Mode}).
+
 @item
 The mode can tell ElDoc mode how to retrieve different types of
 documentation for whatever is at point, by adding one or more
@@ -2994,6 +3001,61 @@ instead.
 automatically sets up Imenu if this variable is non-@code{nil}.
 @end defvar
 
+@node Outline Minor Mode
+@section Outline Minor Mode
+
+@cindex Outline minor mode
+  @dfn{Outline minor mode} is a buffer-local minor mode that hides
+parts of the buffer and leaves only heading lines visible.
+This minor mode can be used in conjunction with other major modes
+(@pxref{Outline Minor Mode,, Outline Minor Mode, emacs, the Emacs Manual}).
+
+  There are two ways to define which lines are headings: with the
+variable @code{outline-regexp} or @code{outline-search-function}.
+
+@defvar outline-regexp
+This variable is a regular expression.
+Any line whose beginning has a match for this regexp is considered a
+heading line.  Matches that start within a line (not at the left
+margin) do not count.
+@end defvar
+
+@defvar outline-search-function
+Alternatively, when it's impossible to create a regexp that
+matches heading lines, you can define a function that helps
+Outline minor mode to find heading lines.
+
+The variable @code{outline-search-function} specifies the function with
+four arguments: @var{bound}, @var{move}, @var{backward}, and
+@var{looking-at}.  The function completes two tasks: to match the
+current heading line, and to find the next or the previous heading line.
+If the argument @var{looking-at} is non-@code{nil}, it should return
+non-@code{nil} when point is at the beginning of the outline header line.
+If the argument @var{looking-at} is @code{nil}, the first three arguments
+are used.  The argument @var{bound} is a buffer position that bounds
+the search.  The match found must not end after that position.  A
+value of nil means search to the end of the accessible portion of
+the buffer.  If the argument @var{move} is non-@code{nil}, the
+failed search should move to the limit of search and return nil.
+If the argument @var{backward} is non-@code{nil}, this function
+should search for the previous heading backward.
+@end defvar
+
+@defvar outline-level
+This variable is a function that takes no arguments
+and should return the level of the current heading.
+It's required in both cases: whether you define
+@code{outline-regexp} or @code{outline-search-function}.
+@end defvar
+
+If built with tree-sitter, Emacs can automatically use
+Outline minor mode if the major mode sets the following variable.
+
+@defvar treesit-outline-predicate
+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
+
 @node Font Lock Mode
 @section Font Lock Mode
 @cindex Font Lock mode
diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi
index d685b7f32dc..3d2192ace64 100644
--- a/doc/lispref/parsing.texi
+++ b/doc/lispref/parsing.texi
@@ -1897,6 +1897,10 @@ add-log functions used by @code{add-log-current-defun}.
 @item
 If @code{treesit-simple-imenu-settings} (@pxref{Imenu}) is
 non-@code{nil}, it sets up Imenu.
+
+@item
+If @code{treesit-outline-predicate} (@pxref{Outline Minor Mode}) is
+non-@code{nil}, it sets up Outline minor mode.
 @end itemize
 
 @c TODO: Add treesit-thing-settings stuff once we finalize it.
diff --git a/etc/NEWS b/etc/NEWS
index afc2c22e68b..f89c8ce1d8d 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/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 @@ Return nil if NODE is not a defun node or doesn't have a 
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 @@ BEG and END are described in `treesit-range-rules'."
                                 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 @@ With ARG, do it many times.  Negative ARG means move 
backward."
                   ("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 @@ Calls REPORT-FN directly."
                                       "vararg_expression"))))
                    (text "comment"))))
 
-    ;; Imenu.
+    ;; Imenu/Outline.
     (setq-local treesit-simple-imenu-settings
                 `(("Requires"
                    "\\`function_call\\'"
@@ -789,16 +789,6 @@ Calls REPORT-FN directly."
     ;; 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 @@ Return nil if there is no name or if NODE is not a defun 
node."
   ;; 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)
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 @@ ENTRY.  MARKER marks the start of each tree-sitter node."
                     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 @@ before calling this function."
     (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)))



reply via email to

[Prev in Thread] Current Thread [Next in Thread]