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 09:40:46 +0200 [thread overview]
Message-ID: <86mssbbdz5.fsf@mail.linkov.net> (raw)
In-Reply-To: <86y1c6q7oh.fsf@mail.linkov.net> (Juri Linkov's message of "Wed, 31 Jan 2024 09:32:46 +0200")
[-- Attachment #1: Type: text/plain, Size: 180 bytes --]
> Here is a new patch with a shorter implementation.
> And I will send a complete patch with documentation changes later.
So here is a complete patch with documentation changes:
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: treesit-outline-predicate.patch --]
[-- Type: text/x-diff, Size: 9854 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..5911a11a074 100644
--- a/doc/lispref/parsing.texi
+++ b/doc/lispref/parsing.texi
@@ -1895,6 +1895,10 @@ Tree-sitter Major Modes
@item
If @code{treesit-simple-imenu-settings} (@pxref{Imenu}) is
non-@code{nil}, it sets up Imenu.
+
+@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..31415617c73 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 6a485ae591a..8b20c3eb0f6 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -2860,6 +2860,68 @@ 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.
+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 +3052,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)
next prev parent reply other threads:[~2024-02-08 7:40 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 [this message]
2024-02-08 8:30 ` Eli Zaretskii
2024-02-08 17:20 ` Juri Linkov
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=86mssbbdz5.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.