5
5
package golang
6
6
7
7
import (
8
+ "bytes"
8
9
"context"
9
10
"go/ast"
10
11
"go/token"
@@ -73,94 +74,125 @@ func foldingRangeFunc(pgf *parsego.File, n ast.Node, lineFoldingOnly bool) *Fold
73
74
// TODO(suzmue): include trailing empty lines before the closing
74
75
// parenthesis/brace.
75
76
var kind protocol.FoldingRangeKind
77
+ // start and end define the range of content to fold away.
76
78
var start , end token.Pos
77
79
switch n := n .(type ) {
78
80
case * ast.BlockStmt :
79
81
// Fold between positions of or lines between "{" and "}".
80
- var startList , endList token.Pos
81
- if num := len (n .List ); num != 0 {
82
- startList , endList = n .List [0 ].Pos (), n .List [num - 1 ].End ()
83
- }
84
- start , end = validLineFoldingRange (pgf .Tok , n .Lbrace , n .Rbrace , startList , endList , lineFoldingOnly )
82
+ start , end = getLineFoldingRange (pgf , n .Lbrace , n .Rbrace , lineFoldingOnly )
85
83
case * ast.CaseClause :
86
84
// Fold from position of ":" to end.
87
85
start , end = n .Colon + 1 , n .End ()
88
86
case * ast.CommClause :
89
87
// Fold from position of ":" to end.
90
88
start , end = n .Colon + 1 , n .End ()
91
89
case * ast.CallExpr :
92
- // Fold from position of "(" to position of ")".
93
- start , end = n .Lparen + 1 , n .Rparen
90
+ // Fold between positions of or lines between "(" and ")".
91
+ start , end = getLineFoldingRange ( pgf , n .Lparen , n .Rparen , lineFoldingOnly )
94
92
case * ast.FieldList :
95
93
// Fold between positions of or lines between opening parenthesis/brace and closing parenthesis/brace.
96
- var startList , endList token.Pos
97
- if num := len (n .List ); num != 0 {
98
- startList , endList = n .List [0 ].Pos (), n .List [num - 1 ].End ()
99
- }
100
- start , end = validLineFoldingRange (pgf .Tok , n .Opening , n .Closing , startList , endList , lineFoldingOnly )
94
+ start , end = getLineFoldingRange (pgf , n .Opening , n .Closing , lineFoldingOnly )
101
95
case * ast.GenDecl :
102
96
// If this is an import declaration, set the kind to be protocol.Imports.
103
97
if n .Tok == token .IMPORT {
104
98
kind = protocol .Imports
105
99
}
106
100
// Fold between positions of or lines between "(" and ")".
107
- var startSpecs , endSpecs token.Pos
108
- if num := len (n .Specs ); num != 0 {
109
- startSpecs , endSpecs = n .Specs [0 ].Pos (), n .Specs [num - 1 ].End ()
110
- }
111
- start , end = validLineFoldingRange (pgf .Tok , n .Lparen , n .Rparen , startSpecs , endSpecs , lineFoldingOnly )
101
+ start , end = getLineFoldingRange (pgf , n .Lparen , n .Rparen , lineFoldingOnly )
112
102
case * ast.BasicLit :
113
103
// Fold raw string literals from position of "`" to position of "`".
114
104
if n .Kind == token .STRING && len (n .Value ) >= 2 && n .Value [0 ] == '`' && n .Value [len (n .Value )- 1 ] == '`' {
115
105
start , end = n .Pos (), n .End ()
116
106
}
117
107
case * ast.CompositeLit :
118
108
// Fold between positions of or lines between "{" and "}".
119
- var startElts , endElts token.Pos
120
- if num := len (n .Elts ); num != 0 {
121
- startElts , endElts = n .Elts [0 ].Pos (), n .Elts [num - 1 ].End ()
122
- }
123
- start , end = validLineFoldingRange (pgf .Tok , n .Lbrace , n .Rbrace , startElts , endElts , lineFoldingOnly )
109
+ start , end = getLineFoldingRange (pgf , n .Lbrace , n .Rbrace , lineFoldingOnly )
124
110
}
125
111
126
112
// Check that folding positions are valid.
127
113
if ! start .IsValid () || ! end .IsValid () {
128
114
return nil
129
115
}
116
+ if start == end {
117
+ // Nothing to fold.
118
+ return nil
119
+ }
130
120
// in line folding mode, do not fold if the start and end lines are the same.
131
121
if lineFoldingOnly && safetoken .Line (pgf .Tok , start ) == safetoken .Line (pgf .Tok , end ) {
132
122
return nil
133
123
}
134
124
mrng , err := pgf .PosMappedRange (start , end )
135
125
if err != nil {
136
- bug .Errorf ("%w" , err ) // can't happen
126
+ bug .Reportf ("failed to create mapped range: %s" , err ) // can't happen
127
+ return nil
137
128
}
138
129
return & FoldingRangeInfo {
139
130
MappedRange : mrng ,
140
131
Kind : kind ,
141
132
}
142
133
}
143
134
144
- // validLineFoldingRange returns start and end token.Pos for folding range if the range is valid.
145
- // returns token.NoPos otherwise, which fails token.IsValid check
146
- func validLineFoldingRange (tokFile * token.File , open , close , start , end token.Pos , lineFoldingOnly bool ) (token.Pos , token.Pos ) {
147
- if lineFoldingOnly {
148
- if ! open .IsValid () || ! close .IsValid () {
149
- return token .NoPos , token .NoPos
150
- }
135
+ // getLineFoldingRange returns the folding range for nodes with parentheses/braces/brackets
136
+ // that potentially can take up multiple lines.
137
+ func getLineFoldingRange (pgf * parsego.File , open , close token.Pos , lineFoldingOnly bool ) (token.Pos , token.Pos ) {
138
+ if ! open .IsValid () || ! close .IsValid () {
139
+ return token .NoPos , token .NoPos
140
+ }
141
+ if open + 1 == close {
142
+ // Nothing to fold: (), {} or [].
143
+ return token .NoPos , token .NoPos
144
+ }
145
+
146
+ if ! lineFoldingOnly {
147
+ // Can fold between opening and closing parenthesis/brace
148
+ // even if they are on the same line.
149
+ return open + 1 , close
150
+ }
151
151
152
- // Don't want to fold if the start/end is on the same line as the open/close
153
- // as an example, the example below should *not* fold:
154
- // var x = [2]string{"d",
155
- // "e" }
156
- if safetoken .Line (tokFile , open ) == safetoken .Line (tokFile , start ) ||
157
- safetoken .Line (tokFile , close ) == safetoken .Line (tokFile , end ) {
158
- return token .NoPos , token .NoPos
152
+ // Clients with "LineFoldingOnly" set to true can fold only full lines.
153
+ // So, we return a folding range only when the closing parenthesis/brace
154
+ // and the end of the last argument/statement/element are on different lines.
155
+ //
156
+ // We could skip the check for the opening parenthesis/brace and start of
157
+ // the first argument/statement/element. For example, the following code
158
+ //
159
+ // var x = []string{"a",
160
+ // "b",
161
+ // "c" }
162
+ //
163
+ // can be folded to
164
+ //
165
+ // var x = []string{"a", ...
166
+ // "c" }
167
+ //
168
+ // However, this might look confusing. So, check the lines of "open" and
169
+ // "start" positions as well.
170
+
171
+ // isOnlySpaceBetween returns true if there are only space characters between "from" and "to".
172
+ isOnlySpaceBetween := func (from token.Pos , to token.Pos ) bool {
173
+ start , end , err := safetoken .Offsets (pgf .Tok , from , to )
174
+ if err != nil {
175
+ bug .Reportf ("failed to get offsets: %s" , err ) // can't happen
176
+ return false
159
177
}
178
+ return len (bytes .TrimSpace (pgf .Src [start :end ])) == 0
179
+ }
160
180
161
- return open + 1 , end
181
+ nextLine := safetoken .Line (pgf .Tok , open ) + 1
182
+ if nextLine > pgf .Tok .LineCount () {
183
+ return token .NoPos , token .NoPos
162
184
}
163
- return open + 1 , close
185
+ nextLineStart := pgf .Tok .LineStart (nextLine )
186
+ if ! isOnlySpaceBetween (open + 1 , nextLineStart ) {
187
+ return token .NoPos , token .NoPos
188
+ }
189
+
190
+ prevLineEnd := pgf .Tok .LineStart (safetoken .Line (pgf .Tok , close )) - 1 // there must be a previous line
191
+ if ! isOnlySpaceBetween (prevLineEnd , close ) {
192
+ return token .NoPos , token .NoPos
193
+ }
194
+
195
+ return open + 1 , prevLineEnd
164
196
}
165
197
166
198
// commentsFoldingRange returns the folding ranges for all comment blocks in file.
@@ -185,7 +217,8 @@ func commentsFoldingRange(pgf *parsego.File) (comments []*FoldingRangeInfo) {
185
217
}
186
218
mrng , err := pgf .PosMappedRange (endLinePos , commentGrp .End ())
187
219
if err != nil {
188
- bug .Errorf ("%w" , err ) // can't happen
220
+ bug .Reportf ("failed to create mapped range: %s" , err ) // can't happen
221
+ continue
189
222
}
190
223
comments = append (comments , & FoldingRangeInfo {
191
224
// Fold from the end of the first line comment to the end of the comment block.
0 commit comments