Skip to content

Commit 89686ce

Browse files
[language-html] Allow for folding of attribute lists…
…such as in multi-line self-closing tags and multi-line opening tags. Also fix an issue in fold handling that made it impossible to use `@_IGNORE_` captures in `folds.scm`.
1 parent cc20d70 commit 89686ce

File tree

2 files changed

+73
-2
lines changed

2 files changed

+73
-2
lines changed

packages/language-html/grammars/tree-sitter-html/folds.scm

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,74 @@
1+
; When dealing with a self-closing element that spans multiple lines, this lets
2+
; us fold the attribute list.
3+
;
4+
; This query captures elements that happen to be self-closing but don't end
5+
; with an XHTML-style ` />`. Because `tree-sitter-html` doesn't distinguish
6+
; these from elements that can have content, we have to check the tag name to
7+
; know how to treat these.
8+
9+
((element
10+
(start_tag
11+
(tag_name) @_IGNORE_) @fold)
12+
(#match? @_IGNORE_ "^(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$")
13+
)
14+
15+
; This one captures the XHTML-style nodes.
16+
(self_closing_tag) @fold
17+
18+
19+
; TODO: Right now, the fold cache doesn't work properly when a given range
20+
; satisfies more than one fold. We should employ `ScopeResolver` to fix this.
21+
22+
; Fold up all of
23+
;
24+
; <div
25+
; foo="bar"
26+
; baz="thud">
27+
;
28+
; </div>
29+
;
30+
; with the fold indicator appearing on whichever line has the `>` that closes
31+
; the opening tag.
32+
;
33+
; Usually this'll be the same line on which the tag opened; but when it isn't,
34+
; this allows for the attribute list of the opening element to be folded
35+
; separately from the element's contents.
36+
;
37+
38+
(element
39+
(start_tag
40+
(tag_name) @_IGNORE_
41+
">" @fold)
42+
(#not-match? @_IGNORE_ "^(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$")
43+
(#set! fold.endAt parent.parent.lastNamedChild.startPosition)
44+
(#set! fold.adjustToEndOfPreviousRow true)
45+
)
46+
47+
48+
; When we have…
49+
;
50+
; <div
51+
; foo="bar"
52+
; baz="thud"
53+
; >
54+
;
55+
; </div>
56+
;
57+
; …we can put a fold indicator on the line with `<div` and use it to fold up
58+
; all of a start tag's attributes.
59+
;
60+
; We keep the end of the fold on a separate line because otherwise we lose the
61+
; ability to independently toggle the folding of the element's contents.
62+
;
63+
(element
64+
(start_tag
65+
(tag_name) @_IGNORE_) @fold
66+
(#not-match? @_IGNORE_ "^(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$")
67+
(#set! fold.endAt lastChild.startPosition)
68+
(#set! fold.adjustToEndOfPreviousRow true))
69+
170

271
[
3-
(element)
472
(script_element)
573
(style_element)
674
] @fold

src/wasm-tree-sitter-language-mode.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2149,14 +2149,17 @@ class FoldResolver {
21492149
let boundaries = createTree(compareBoundaries);
21502150
let captures = this.layer.foldsQuery.captures(rootNode, start, end);
21512151

2152+
// TODO: When a node is captured more than once, we handle all captures. We
2153+
// should instead use `ScopeResolver` so that a single folds query can use
2154+
// `capture.final` and `capture.shy` to rule out other possible matches.
21522155
for (let capture of captures) {
21532156
if (capture.node.startPosition.row < start.row) { continue; }
21542157
if (capture.name === 'fold') {
21552158
boundaries = boundaries.insert({
21562159
position: capture.node.startPosition,
21572160
boundary: 'start'
21582161
}, capture);
2159-
} else {
2162+
} else if (capture.name.startsWith('fold.')) {
21602163
let key = this.keyForDividedFold(capture);
21612164
boundaries = boundaries.insert(key, capture);
21622165
}

0 commit comments

Comments
 (0)