Skip to content

Commit 2da648e

Browse files
committed
Improve folding ranges if editor only supports line folding
If the folding range doesn't end at the end of the last line, exclude that line from the folding range since the end line gets folded away. This means if we reported `end.line`, we would eg. fold away the `}` that matches a `{`, which looks surprising. If the folding range does end at the end of the line we are in cases that don't have a closing indicator (like comments), so we can fold the last line as well.
1 parent d439d12 commit 2da648e

File tree

2 files changed

+78
-8
lines changed

2 files changed

+78
-8
lines changed

Sources/SourceKitLSP/Swift/FoldingRange.swift

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import LSPLogging
1414
import LanguageServerProtocol
15+
import SKSupport
1516
import SwiftSyntax
1617

1718
fileprivate final class FoldingRangeFinder: SyntaxAnyVisitor {
@@ -210,9 +211,21 @@ fileprivate final class FoldingRangeFinder: SyntaxAnyVisitor {
210211
let end = snapshot.positionOf(utf8Offset: end.utf8Offset)
211212
let range: FoldingRange
212213
if lineFoldingOnly {
214+
// If the folding range doesn't end at the end of the last line, exclude that line from the folding range since
215+
// the end line gets folded away. This means if we reported `end.line`, we would eg. fold away the `}` that
216+
// matches a `{`, which looks surprising.
217+
// If the folding range does end at the end of the line we are in cases that don't have a closing indicator (like
218+
// comments), so we can fold the last line as well.
219+
let endLine: Int
220+
if snapshot.lineTable.isAtEndOfLine(end) {
221+
endLine = end.line
222+
} else {
223+
endLine = end.line - 1
224+
}
225+
213226
// Since the client cannot fold less than a single line, if the
214227
// fold would span 1 line there's no point in reporting it.
215-
guard end.line > start.line else {
228+
guard endLine > start.line else {
216229
return .visitChildren
217230
}
218231

@@ -221,7 +234,7 @@ fileprivate final class FoldingRangeFinder: SyntaxAnyVisitor {
221234
range = FoldingRange(
222235
startLine: start.line,
223236
startUTF16Index: nil,
224-
endLine: end.line,
237+
endLine: endLine,
225238
endUTF16Index: nil,
226239
kind: kind
227240
)
@@ -264,3 +277,14 @@ extension SwiftLanguageService {
264277
return ranges.sorted()
265278
}
266279
}
280+
281+
fileprivate extension LineTable {
282+
func isAtEndOfLine(_ position: Position) -> Bool {
283+
guard position.line >= 0, position.line < self.count else {
284+
return false
285+
}
286+
let line = self[position.line]
287+
let suffixAfterPositionColumn = line[line.utf16.index(line.startIndex, offsetBy: position.utf16index)...]
288+
return suffixAfterPositionColumn.allSatisfy(\.isNewline)
289+
}
290+
}

Tests/SourceKitLSPTests/FoldingRangeTests.swift

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,17 +96,63 @@ final class FoldingRangeTests: XCTestCase {
9696
try await assertFoldingRanges(
9797
markedSource: """
9898
1️⃣func foo() {
99-
100-
2️⃣}
101-
"""
102-
,
99+
2️⃣
100+
}
101+
""",
103102
expectedRanges: [
104103
FoldingRangeSpec(from: "1️⃣", to: "2️⃣")
105104
],
106105
lineFoldingOnly: true
107106
)
108107
}
109108

109+
func testLineFoldingOfFunctionWithMultiLineParameters() async throws {
110+
try await assertFoldingRanges(
111+
markedSource: """
112+
1️⃣func foo(
113+
2️⃣ param: Int
114+
3️⃣) {
115+
print(param)
116+
4️⃣
117+
}
118+
""",
119+
expectedRanges: [
120+
FoldingRangeSpec(from: "1️⃣", to: "2️⃣"),
121+
FoldingRangeSpec(from: "3️⃣", to: "4️⃣"),
122+
],
123+
lineFoldingOnly: true
124+
)
125+
}
126+
127+
func testLineFoldingOfComment() async throws {
128+
try await assertFoldingRanges(
129+
markedSource: """
130+
1️⃣// abc
131+
// def
132+
2️⃣// ghi
133+
134+
""",
135+
expectedRanges: [
136+
FoldingRangeSpec(from: "1️⃣", to: "2️⃣", kind: .comment)
137+
],
138+
lineFoldingOnly: true
139+
)
140+
}
141+
142+
func testLineFoldingOfCommentAtEndOfFile() async throws {
143+
try await assertFoldingRanges(
144+
markedSource: """
145+
1️⃣// abc
146+
// def
147+
2️⃣// ghi
148+
""",
149+
expectedRanges: [
150+
FoldingRangeSpec(from: "1️⃣", to: "2️⃣", kind: .comment)
151+
],
152+
lineFoldingOnly: true
153+
)
154+
}
155+
110156
func testLineFoldingDoesntReportSingleLine() async throws {
111157
try await assertFoldingRanges(
112158
markedSource: """
@@ -272,8 +318,8 @@ final class FoldingRangeTests: XCTestCase {
272318
try await assertFoldingRanges(
273319
markedSource: """
274320
let x = [1️⃣
275-
1: "one",
276-
2: "two",
321+
1: "one",
322+
2: "two",
277323
3: "three"
278324
2️⃣]
279325
""",

0 commit comments

Comments
 (0)