@@ -20,13 +20,53 @@ class AttributeFolder: FoldingBuilderEx(), DumbAware {
2020 for (item in getAttributes(Array (1 ) { root })) {
2121 var end: Int
2222 var start: Int
23+
2324 if (settings.foldingMethod == 1 ) {
2425 end = item.attribute.textRange.endOffset
2526 start = item.attribute.textRange.startOffset
2627 } else {
27- val len = item.attributeName.length + settings.attributeSeparator.length + settings.attributeWrapper.length
28- start = item.attribute.textRange.startOffset + len
29- end = item.attribute.textRange.endOffset - settings.attributeWrapper.length
28+ val text = item.attribute.text
29+ val base = item.attribute.textRange.startOffset
30+
31+ // Find '=' then the first non-space char after it
32+ val eq = text.indexOf(settings.attributeSeparator)
33+ if (eq < 0 ) continue
34+ var i = eq + 1
35+ while (i < text.length && text[i].isWhitespace()) i++
36+ if (i >= text.length) continue
37+
38+ when (text[i]) {
39+ // name="value" or name='value'
40+ ' "' , ' \' ' -> {
41+ val quote = text[i]
42+ val open = i
43+ val close = text.indexOf(quote, startIndex = open + 1 )
44+ if (close < 0 ) continue
45+
46+ start = base + open + 1
47+ end = base + close
48+ }
49+ // name={...} or name={{...}}
50+ ' {' -> {
51+ val outerOpen = i
52+ val outerClose = findMatchingBrace(text, outerOpen) ? : continue
53+
54+ val isDouble = outerOpen + 1 < text.length && text[outerOpen + 1 ] == ' {'
55+ if (isDouble) {
56+ val innerOpen = outerOpen + 1
57+ val innerClose = findMatchingBrace(text, innerOpen) ? : continue
58+
59+ // Fold inside INNER braces INCLUDING whitespace so output becomes: style={{__PLACEHOLDER__}}
60+ start = base + innerOpen + 1
61+ end = base + innerClose
62+ } else {
63+ // Fold inside single braces INCLUDING whitespace: name={__PLACEHOLDER__}
64+ start = base + outerOpen + 1
65+ end = base + outerClose
66+ }
67+ }
68+ else -> continue
69+ }
3070 }
3171
3272 if (end > start) {
@@ -57,24 +97,46 @@ class AttributeFolder: FoldingBuilderEx(), DumbAware {
5797 return settings.collapseByDefault
5898 }
5999
100+ private fun findMatchingBrace (text : String , openIndex : Int ): Int? {
101+ if (openIndex !in text.indices || text[openIndex] != ' {' ) return null
102+ var depth = 0
103+ var i = openIndex
104+ while (i < text.length) {
105+ when (text[i]) {
106+ ' {' -> depth++
107+ ' }' -> {
108+ depth--
109+ if (depth == 0 ) return i
110+ }
111+ }
112+ i++
113+ }
114+ return null
115+ }
116+
60117 private fun getAttributes (
61118 elements : Array <PsiElement >,
62119 attributes : ArrayList <String > = settings.attributes
63120 ): Sequence <Attribute > = sequence {
64121 for (child in elements) {
122+ val t = child.text
65123 for (attributeName in attributes) {
66- val attributeBeginning = attributeName + settings.attributeSeparator + settings.attributeWrapper
67- if (child.text.startsWith(attributeBeginning)) {
124+ val startsLikeAttribute =
125+ t.startsWith(" $attributeName =\" " ) ||
126+ t.startsWith(" $attributeName ='" ) ||
127+ t.startsWith(" $attributeName ={" )
128+
129+ if (startsLikeAttribute) {
68130 yield (object : Attribute {
69131 override val attribute = child
70132 override val attributeName = attributeName
71133 })
72134 }
135+ }
73136
74- val items = getAttributes(child.children, arrayListOf (attributeName)).iterator()
75- while (items.hasNext()) {
76- yield (items.next())
77- }
137+ val items = getAttributes(child.children, attributes).iterator()
138+ while (items.hasNext()) {
139+ yield (items.next())
78140 }
79141 }
80142 }
0 commit comments