55package golang
66
77import (
8+ "bytes"
89 "context"
910 "go/ast"
1011 "go/token"
@@ -73,94 +74,125 @@ func foldingRangeFunc(pgf *parsego.File, n ast.Node, lineFoldingOnly bool) *Fold
7374 // TODO(suzmue): include trailing empty lines before the closing
7475 // parenthesis/brace.
7576 var kind protocol.FoldingRangeKind
77+ // start and end define the range of content to fold away.
7678 var start , end token.Pos
7779 switch n := n .(type ) {
7880 case * ast.BlockStmt :
7981 // 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 )
8583 case * ast.CaseClause :
8684 // Fold from position of ":" to end.
8785 start , end = n .Colon + 1 , n .End ()
8886 case * ast.CommClause :
8987 // Fold from position of ":" to end.
9088 start , end = n .Colon + 1 , n .End ()
9189 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 )
9492 case * ast.FieldList :
9593 // 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 )
10195 case * ast.GenDecl :
10296 // If this is an import declaration, set the kind to be protocol.Imports.
10397 if n .Tok == token .IMPORT {
10498 kind = protocol .Imports
10599 }
106100 // 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 )
112102 case * ast.BasicLit :
113103 // Fold raw string literals from position of "`" to position of "`".
114104 if n .Kind == token .STRING && len (n .Value ) >= 2 && n .Value [0 ] == '`' && n .Value [len (n .Value )- 1 ] == '`' {
115105 start , end = n .Pos (), n .End ()
116106 }
117107 case * ast.CompositeLit :
118108 // 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 )
124110 }
125111
126112 // Check that folding positions are valid.
127113 if ! start .IsValid () || ! end .IsValid () {
128114 return nil
129115 }
116+ if start == end {
117+ // Nothing to fold.
118+ return nil
119+ }
130120 // in line folding mode, do not fold if the start and end lines are the same.
131121 if lineFoldingOnly && safetoken .Line (pgf .Tok , start ) == safetoken .Line (pgf .Tok , end ) {
132122 return nil
133123 }
134124 mrng , err := pgf .PosMappedRange (start , end )
135125 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
137128 }
138129 return & FoldingRangeInfo {
139130 MappedRange : mrng ,
140131 Kind : kind ,
141132 }
142133}
143134
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+ }
151151
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
159177 }
178+ return len (bytes .TrimSpace (pgf .Src [start :end ])) == 0
179+ }
160180
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
162184 }
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
164196}
165197
166198// commentsFoldingRange returns the folding ranges for all comment blocks in file.
@@ -185,7 +217,8 @@ func commentsFoldingRange(pgf *parsego.File) (comments []*FoldingRangeInfo) {
185217 }
186218 mrng , err := pgf .PosMappedRange (endLinePos , commentGrp .End ())
187219 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
189222 }
190223 comments = append (comments , & FoldingRangeInfo {
191224 // Fold from the end of the first line comment to the end of the comment block.
0 commit comments