mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-11-24 07:20:37 +00:00
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'.
This commit is contained in:
parent
39cce137ba
commit
3b90e5052c
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
7
etc/NEWS
7
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
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)))
|
||||
|
Loading…
Reference in New Issue
Block a user