Skip to content

Commit 5a268f4

Browse files
committed
gopls/internal/folding_range: allow more folding ranges
When the 'lineFoldingOnly' is set, the client ignores character positions in folding ranges. (Vscode does this, eglot does not support floating ranges at all.) For multiline ranges delimited by (), [], or {}, the existing code will not generate a folding range if there is anything but whitespace after the opening (, [, or { on the same line. This seems too restrictive, as it excludes ranges starting with func foo() { // non-godoc comment or var x = []string{"a", so this CL removes that test. Fixes: golang/go#73735 Change-Id: Ie7aa5e735542b20a72e582e407b5d6ae2b8b2d29 Reviewed-on: https://go-review.googlesource.com/c/tools/+/694295 Reviewed-by: Madeline Kalil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent e48606b commit 5a268f4

File tree

3 files changed

+94
-112
lines changed

3 files changed

+94
-112
lines changed

gopls/internal/golang/folding_range.go

Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package golang
66

77
import (
8-
"bytes"
98
"cmp"
109
"context"
1110
"go/ast"
@@ -65,7 +64,7 @@ func FoldingRange(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle,
6564
switch n := cur.Node().(type) {
6665
case *ast.BlockStmt:
6766
// Fold between positions of or lines between "{" and "}".
68-
start, end = getLineFoldingRange(pgf, n.Lbrace, n.Rbrace, lineFoldingOnly)
67+
start, end = bracketedFoldingRange(n.Lbrace, n.Rbrace)
6968

7069
case *ast.CaseClause:
7170
// Fold from position of ":" to end.
@@ -77,19 +76,19 @@ func FoldingRange(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle,
7776

7877
case *ast.CallExpr:
7978
// Fold between positions of or lines between "(" and ")".
80-
start, end = getLineFoldingRange(pgf, n.Lparen, n.Rparen, lineFoldingOnly)
79+
start, end = bracketedFoldingRange(n.Lparen, n.Rparen)
8180

8281
case *ast.FieldList:
8382
// Fold between positions of or lines between opening parenthesis/brace and closing parenthesis/brace.
84-
start, end = getLineFoldingRange(pgf, n.Opening, n.Closing, lineFoldingOnly)
83+
start, end = bracketedFoldingRange(n.Opening, n.Closing)
8584

8685
case *ast.GenDecl:
8786
// If this is an import declaration, set the kind to be protocol.Imports.
8887
if n.Tok == token.IMPORT {
8988
kind = protocol.Imports
9089
}
9190
// Fold between positions of or lines between "(" and ")".
92-
start, end = getLineFoldingRange(pgf, n.Lparen, n.Rparen, lineFoldingOnly)
91+
start, end = bracketedFoldingRange(n.Lparen, n.Rparen)
9392

9493
case *ast.BasicLit:
9594
// Fold raw string literals from position of "`" to position of "`".
@@ -99,7 +98,7 @@ func FoldingRange(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle,
9998

10099
case *ast.CompositeLit:
101100
// Fold between positions of or lines between "{" and "}".
102-
start, end = getLineFoldingRange(pgf, n.Lbrace, n.Rbrace, lineFoldingOnly)
101+
start, end = bracketedFoldingRange(n.Lbrace, n.Rbrace)
103102

104103
default:
105104
panic(n)
@@ -136,9 +135,9 @@ func FoldingRange(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle,
136135
return ranges, nil
137136
}
138137

139-
// getLineFoldingRange returns the folding range for nodes with parentheses/braces/brackets
138+
// bracketedFoldingRange returns the folding range for nodes with parentheses/braces/brackets
140139
// that potentially can take up multiple lines.
141-
func getLineFoldingRange(pgf *parsego.File, open, close token.Pos, lineFoldingOnly bool) (token.Pos, token.Pos) {
140+
func bracketedFoldingRange(open, close token.Pos) (token.Pos, token.Pos) {
142141
if !open.IsValid() || !close.IsValid() {
143142
return token.NoPos, token.NoPos
144143
}
@@ -147,56 +146,38 @@ func getLineFoldingRange(pgf *parsego.File, open, close token.Pos, lineFoldingOn
147146
return token.NoPos, token.NoPos
148147
}
149148

150-
if !lineFoldingOnly {
151-
// Can fold between opening and closing parenthesis/brace
152-
// even if they are on the same line.
153-
return open + 1, close
154-
}
155-
156149
// Clients with "LineFoldingOnly" set to true can fold only full lines.
157-
// So, we return a folding range only when the closing parenthesis/brace
158-
// and the end of the last argument/statement/element are on different lines.
150+
// This is checked in the caller.
159151
//
160-
// We could skip the check for the opening parenthesis/brace and start of
161-
// the first argument/statement/element. For example, the following code
152+
// Clients that support folding ranges can display them in various ways
153+
// (e.g., how are folding ranges marked? is the final line displayed?).
154+
// The most common client
155+
// is vscode, which displays the first line followed by ..., and then does not
156+
// display any other lines in the range, but other clients might also display
157+
// final line of the range. For example, the following code
162158
//
163159
// var x = []string{"a",
164160
// "b",
165161
// "c" }
166162
//
167-
// can be folded to
163+
// can be folded (in vscode) to
164+
//
165+
// var x = []string{"a", ...
166+
//
167+
// or in some other client
168168
//
169169
// var x = []string{"a", ...
170170
// "c" }
171171
//
172-
// However, this might look confusing. So, check the lines of "open" and
173-
// "start" positions as well.
174-
175-
// isOnlySpaceBetween returns true if there are only space characters between "from" and "to".
176-
isOnlySpaceBetween := func(from token.Pos, to token.Pos) bool {
177-
text, err := pgf.PosText(from, to)
178-
if err != nil {
179-
bug.Reportf("failed to get offsets: %s", err) // can't happen
180-
return false
181-
}
182-
return len(bytes.TrimSpace(text)) == 0
183-
}
184-
185-
nextLine := safetoken.Line(pgf.Tok, open) + 1
186-
if nextLine > pgf.Tok.LineCount() {
187-
return token.NoPos, token.NoPos
188-
}
189-
nextLineStart := pgf.Tok.LineStart(nextLine)
190-
if !isOnlySpaceBetween(open+1, nextLineStart) {
191-
return token.NoPos, token.NoPos
192-
}
193-
194-
prevLineEnd := pgf.Tok.LineStart(safetoken.Line(pgf.Tok, close)) - 1 // there must be a previous line
195-
if !isOnlySpaceBetween(prevLineEnd, close) {
196-
return token.NoPos, token.NoPos
197-
}
172+
// This is a change in behavior. The old code would not fold this example,
173+
// nor would it have folded
174+
//
175+
// func foo() { // a non-godoc comment
176+
// ...
177+
// }
178+
// which seems wrong.
198179

199-
return open + 1, prevLineEnd
180+
return open + 1, close
200181
}
201182

202183
// commentsFoldingRange returns the folding ranges for all comment blocks in file.

gopls/internal/settings/settings.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,7 @@ func (o *Options) ForClientCapabilities(clientInfo *protocol.ClientInfo, caps pr
10041004
// Check if the client supports only line folding.
10051005

10061006
if fr := caps.TextDocument.FoldingRange; fr != nil {
1007+
// TODO(pjw): add telemetry
10071008
o.LineFoldingOnly = fr.LineFoldingOnly
10081009
}
10091010
// Check if the client supports hierarchical document symbols.

gopls/internal/test/marker/testdata/foldingrange/a_lineonly.txt

Lines changed: 66 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ import (<0 kind="imports">
145145
"fmt"
146146
_ "log"
147147
"sort"
148-
"time"</0>
149-
)
148+
"time"
149+
</0>)
150150

151151
import _ "os"
152152

@@ -157,15 +157,15 @@ func Bar() string {<2 kind="">
157157
switch {<3 kind="">
158158
case true:<4 kind="">
159159
if true {<5 kind="">
160-
fmt.Println("true")</5>
161-
} else {<6 kind="">
162-
fmt.Println("false")</6>
163-
}</4>
160+
fmt.Println("true")
161+
</5>} else {<6 kind="">
162+
fmt.Println("false")
163+
</6>}</4>
164164
case false:<7 kind="">
165165
fmt.Println("false")</7>
166166
default:<8 kind="">
167-
fmt.Println("default")</3></8>
168-
}
167+
fmt.Println("default")</8>
168+
</3>}
169169
/* This is a multiline<9 kind="comment">
170170
block
171171
comment */</9>
@@ -177,93 +177,93 @@ func Bar() string {<2 kind="">
177177
_ = []int{<11 kind="">
178178
1,
179179
2,
180-
3,</11>
181-
}
182-
_ = [2]string{"d",
180+
3,
181+
</11>}
182+
_ = [2]string{<12 kind="">"d",
183183
"e",
184-
}
185-
_ = map[string]int{<12 kind="">
184+
</12>}
185+
_ = map[string]int{<13 kind="">
186186
"a": 1,
187187
"b": 2,
188-
"c": 3,</12>
189-
}
190-
type T struct {<13 kind="">
188+
"c": 3,
189+
</13>}
190+
type T struct {<14 kind="">
191191
f string
192192
g int
193-
h string</13>
194-
}
195-
_ = T{<14 kind="">
193+
h string
194+
</14>}
195+
_ = T{<15 kind="">
196196
f: "j",
197197
g: 4,
198-
h: "i",</14>
199-
}
198+
h: "i",
199+
</15>}
200200
x, y := make(chan bool), make(chan bool)
201-
select {<15 kind="">
202-
case val := <-x:<16 kind="">
203-
if val {<17 kind="">
204-
fmt.Println("true from x")</17>
205-
} else {<18 kind="">
206-
fmt.Println("false from x")</18>
207-
}</16>
208-
case <-y:<19 kind="">
209-
fmt.Println("y")</19>
210-
default:<20 kind="">
211-
fmt.Println("default")</15></20>
212-
}
213-
// This is a multiline comment<21 kind="comment">
214-
// that is not a doc comment.</21>
215-
return <22 kind="">`
201+
select {<16 kind="">
202+
case val := <-x:<17 kind="">
203+
if val {<18 kind="">
204+
fmt.Println("true from x")
205+
</18>} else {<19 kind="">
206+
fmt.Println("false from x")
207+
</19>}</17>
208+
case <-y:<20 kind="">
209+
fmt.Println("y")</20>
210+
default:<21 kind="">
211+
fmt.Println("default")</21>
212+
</16>}
213+
// This is a multiline comment<22 kind="comment">
214+
// that is not a doc comment.</22>
215+
return <23 kind="">`
216216
this string
217-
is not indented`</2></22>
218-
}
217+
is not indented`</23>
218+
</2>}
219219

220-
func _() {<23 kind="">
220+
func _() {<24 kind="">
221221
slice := []int{1, 2, 3}
222-
sort.Slice(slice, func(i, j int) bool {<24 kind="">
222+
sort.Slice(<25 kind="">slice, func(i, j int) bool {<26 kind="">
223223
a, b := slice[i], slice[j]
224-
return a > b</24>
225-
})
224+
return a > b
225+
</26>}</25>)
226226

227227
sort.Slice(slice, func(i, j int) bool { return slice[i] > slice[j] })
228228

229-
sort.Slice(<25 kind="">
229+
sort.Slice(<27 kind="">
230230
slice,
231-
func(i, j int) bool {<26 kind="">
232-
return slice[i] > slice[j]</26>
233-
},</25>
234-
)
231+
func(i, j int) bool {<28 kind="">
232+
return slice[i] > slice[j]
233+
</28>},
234+
</27>)
235235

236-
fmt.Println(<27 kind="">
236+
fmt.Println(<29 kind="">
237237
1, 2, 3,
238-
4,</27>
239-
)
238+
4,
239+
</29>)
240240

241-
fmt.Println(1, 2, 3,
241+
fmt.Println(<30 kind="">1, 2, 3,
242242
4, 5, 6,
243243
7, 8, 9,
244-
10)
244+
10</30>)
245245

246246
// Call with ellipsis.
247-
_ = fmt.Errorf(<28 kind="">
247+
_ = fmt.Errorf(<31 kind="">
248248
"test %d %d",
249-
[]any{1, 2, 3}...,</28>
250-
)
249+
[]any{1, 2, 3}...,
250+
</31>)
251251

252252
// Check multiline string.
253-
fmt.Println(<29 kind="">
254-
<30 kind="">`multi
253+
fmt.Println(<32 kind="">
254+
<33 kind="">`multi
255255
line
256256
string
257-
`</30>,
258-
1, 2, 3,</29>
259-
)
257+
`</33>,
258+
1, 2, 3,
259+
</32>)
260260

261261
// Call without arguments.
262-
_ = time.Now()</23>
263-
}
262+
_ = time.Now()
263+
</24>}
264264

265-
func _(<31 kind="">
265+
func _(<34 kind="">
266266
a int, b int,
267-
c int,</31>
268-
) {
269-
}
267+
c int,
268+
</34>) {<35 kind="">
269+
</35>}

0 commit comments

Comments
 (0)