Skip to content

Commit 2bae1f0

Browse files
committed
Improve folding of calls that span multiple lines
1 parent 796138b commit 2bae1f0

File tree

2 files changed

+82
-24
lines changed

2 files changed

+82
-24
lines changed

Sources/SourceKitLSP/Swift/FoldingRange.swift

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -51,27 +51,27 @@ fileprivate final class FoldingRangeFinder: SyntaxAnyVisitor {
5151

5252
private func addTrivia(from node: TokenSyntax, _ trivia: Trivia) {
5353
let pieces = trivia.pieces
54-
var start = node.position.utf8Offset
54+
var start = node.position
5555
/// The index of the trivia piece we are currently inspecting.
5656
var index = 0
5757

5858
while index < pieces.count {
5959
let piece = pieces[index]
6060
defer {
61-
start += pieces[index].sourceLength.utf8Length
61+
start = start.advanced(by: pieces[index].sourceLength.utf8Length)
6262
index += 1
6363
}
6464
switch piece {
6565
case .blockComment:
6666
_ = self.addFoldingRange(
6767
start: start,
68-
end: start + piece.sourceLength.utf8Length,
68+
end: start.advanced(by: piece.sourceLength.utf8Length),
6969
kind: .comment
7070
)
7171
case .docBlockComment:
7272
_ = self.addFoldingRange(
7373
start: start,
74-
end: start + piece.sourceLength.utf8Length,
74+
end: start.advanced(by: piece.sourceLength.utf8Length),
7575
kind: .comment
7676
)
7777
case .lineComment, .docLineComment:
@@ -89,7 +89,7 @@ fileprivate final class FoldingRangeFinder: SyntaxAnyVisitor {
8989
let piece = pieces[lookaheadIndex]
9090
defer {
9191
lookaheadIndex += 1
92-
lookaheadStart += piece.sourceLength.utf8Length
92+
lookaheadStart = lookaheadStart.advanced(by: piece.sourceLength.utf8Length)
9393
}
9494
switch piece {
9595
case .newlines(let count), .carriageReturns(let count), .carriageReturnLineFeeds(let count):
@@ -115,7 +115,7 @@ fileprivate final class FoldingRangeFinder: SyntaxAnyVisitor {
115115
}
116116
_ = self.addFoldingRange(
117117
start: lineCommentBlockStart,
118-
end: start + pieces[index].sourceLength.utf8Length,
118+
end: start.advanced(by: pieces[index].sourceLength.utf8Length),
119119
kind: .comment
120120
)
121121
default:
@@ -127,61 +127,76 @@ fileprivate final class FoldingRangeFinder: SyntaxAnyVisitor {
127127
override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
128128
if let braced = node.asProtocol(BracedSyntax.self) {
129129
return self.addFoldingRange(
130-
start: braced.leftBrace.endPositionBeforeTrailingTrivia.utf8Offset,
131-
end: braced.rightBrace.positionAfterSkippingLeadingTrivia.utf8Offset
130+
start: braced.leftBrace.endPositionBeforeTrailingTrivia,
131+
end: braced.rightBrace.positionAfterSkippingLeadingTrivia
132132
)
133133
}
134134
return .visitChildren
135135
}
136136

137137
override func visit(_ node: ArrayExprSyntax) -> SyntaxVisitorContinueKind {
138138
return self.addFoldingRange(
139-
start: node.leftSquare.endPositionBeforeTrailingTrivia.utf8Offset,
140-
end: node.rightSquare.positionAfterSkippingLeadingTrivia.utf8Offset
139+
start: node.leftSquare.endPositionBeforeTrailingTrivia,
140+
end: node.rightSquare.positionAfterSkippingLeadingTrivia
141141
)
142142
}
143143

144144
override func visit(_ node: DictionaryExprSyntax) -> SyntaxVisitorContinueKind {
145145
return self.addFoldingRange(
146-
start: node.leftSquare.endPositionBeforeTrailingTrivia.utf8Offset,
147-
end: node.rightSquare.positionAfterSkippingLeadingTrivia.utf8Offset
146+
start: node.leftSquare.endPositionBeforeTrailingTrivia,
147+
end: node.rightSquare.positionAfterSkippingLeadingTrivia
148148
)
149149
}
150150

151151
override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
152-
return self.addFoldingRange(
153-
start: node.arguments.position.utf8Offset,
154-
end: node.arguments.endPosition.utf8Offset
155-
)
152+
let start = node.leftParen?.endPositionBeforeTrailingTrivia ?? node.arguments.position
153+
let end =
154+
if !node.additionalTrailingClosures.isEmpty {
155+
node.additionalTrailingClosures.endPositionBeforeTrailingTrivia
156+
} else if let trailingClosure = node.trailingClosure {
157+
trailingClosure.endPositionBeforeTrailingTrivia
158+
} else if let rightParen = node.rightParen {
159+
rightParen.positionAfterSkippingLeadingTrivia
160+
} else {
161+
// Should never happen because the call should have either a trailing closure or a closing ')'
162+
node.arguments.endPositionBeforeTrailingTrivia
163+
}
164+
return self.addFoldingRange(start: start, end: end)
156165
}
157166

158167
override func visit(_ node: SubscriptCallExprSyntax) -> SyntaxVisitorContinueKind {
159168
return self.addFoldingRange(
160-
start: node.arguments.position.utf8Offset,
161-
end: node.arguments.endPosition.utf8Offset
169+
start: node.leftSquare.endPositionBeforeTrailingTrivia,
170+
end: node.rightSquare.positionAfterSkippingLeadingTrivia
162171
)
163172
}
164173

165174
override func visit(_ node: SwitchCaseSyntax) -> SyntaxVisitorContinueKind {
166175
return self.addFoldingRange(
167-
start: node.label.endPositionBeforeTrailingTrivia.utf8Offset,
168-
end: node.statements.endPosition.utf8Offset
176+
start: node.label.endPositionBeforeTrailingTrivia,
177+
end: node.statements.endPosition
169178
)
170179
}
171180

172181
__consuming func finalize() -> Set<FoldingRange> {
173182
return self.ranges
174183
}
175184

176-
private func addFoldingRange(start: Int, end: Int, kind: FoldingRangeKind? = nil) -> SyntaxVisitorContinueKind {
185+
private func addFoldingRange(
186+
start: AbsolutePosition,
187+
end: AbsolutePosition,
188+
kind: FoldingRangeKind? = nil
189+
) -> SyntaxVisitorContinueKind {
177190
if let limit = self.rangeLimit, self.ranges.count >= limit {
178191
return .skipChildren
179192
}
180193

181-
guard let start: Position = snapshot.positionOf(utf8Offset: start),
182-
let end: Position = snapshot.positionOf(utf8Offset: end)
194+
guard let start: Position = snapshot.positionOf(utf8Offset: start.utf8Offset),
195+
let end: Position = snapshot.positionOf(utf8Offset: end.utf8Offset)
183196
else {
184-
logger.error("folding range failed to retrieve position of \(self.snapshot.uri.forLogging): \(start)-\(end)")
197+
logger.error(
198+
"folding range failed to retrieve position of \(self.snapshot.uri.forLogging): \(start.utf8Offset)-\(end.utf8Offset)"
199+
)
185200
return .visitChildren
186201
}
187202
let range: FoldingRange

Tests/SourceKitLSPTests/FoldingRangeTests.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,4 +303,47 @@ final class FoldingRangeTests: XCTestCase {
303303
]
304304
)
305305
}
306+
307+
func testFoldArgumentLabelsOnMultipleLines() async throws {
308+
try await assertFoldingRanges(
309+
markedSource: """
310+
print(1️⃣
311+
"x"
312+
2️⃣)
313+
""",
314+
expectedRanges: [FoldingRangeSpec(from: "1️⃣", to: "2️⃣")]
315+
)
316+
}
317+
318+
func testFoldCallWithTrailingClosure() async throws {
319+
try await assertFoldingRanges(
320+
markedSource: """
321+
doSomething(1️⃣normalArg: 1) {2️⃣
322+
_ = $0
323+
3️⃣}4️⃣
324+
""",
325+
expectedRanges: [
326+
FoldingRangeSpec(from: "1️⃣", to: "4️⃣"),
327+
FoldingRangeSpec(from: "2️⃣", to: "3️⃣"),
328+
]
329+
)
330+
}
331+
332+
func testFoldCallWithMultipleTrailingClosures() async throws {
333+
try await assertFoldingRanges(
334+
markedSource: """
335+
doSomething(1️⃣normalArg: 1) {2️⃣
336+
_ = $0
337+
3️⃣}
338+
additionalTrailing: {4️⃣
339+
_ = $0
340+
5️⃣}6️⃣
341+
""",
342+
expectedRanges: [
343+
FoldingRangeSpec(from: "1️⃣", to: "6️⃣"),
344+
FoldingRangeSpec(from: "2️⃣", to: "3️⃣"),
345+
FoldingRangeSpec(from: "4️⃣", to: "5️⃣"),
346+
]
347+
)
348+
}
306349
}

0 commit comments

Comments
 (0)