Skip to content

Commit 3a18960

Browse files
authored
Merge pull request #1806 from MahdiBM/mmbm-range-formatting
Handle `DocumentRangeFormattingRequest`
2 parents 41a773f + 8f3253d commit 3a18960

File tree

5 files changed

+167
-5
lines changed

5 files changed

+167
-5
lines changed

Sources/SourceKitLSP/Clang/ClangLanguageService.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,10 @@ extension ClangLanguageService {
580580
return try await forwardRequestToClangd(req)
581581
}
582582

583+
func documentRangeFormatting(_ req: DocumentRangeFormattingRequest) async throws -> [TextEdit]? {
584+
return try await forwardRequestToClangd(req)
585+
}
586+
583587
func codeAction(_ req: CodeActionRequest) async throws -> CodeActionRequestResponse? {
584588
return try await forwardRequestToClangd(req)
585589
}

Sources/SourceKitLSP/LanguageService.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ package protocol LanguageService: AnyObject, Sendable {
209209
func codeLens(_ req: CodeLensRequest) async throws -> [CodeLens]
210210
func documentDiagnostic(_ req: DocumentDiagnosticsRequest) async throws -> DocumentDiagnosticReport
211211
func documentFormatting(_ req: DocumentFormattingRequest) async throws -> [TextEdit]?
212+
func documentRangeFormatting(_ req: DocumentRangeFormattingRequest) async throws -> [TextEdit]?
212213

213214
// MARK: - Rename
214215

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,8 @@ extension SourceKitLSPServer: QueueBasedMessageHandler {
720720
await self.handleRequest(for: request, requestHandler: self.documentDiagnostic)
721721
case let request as RequestAndReply<DocumentFormattingRequest>:
722722
await self.handleRequest(for: request, requestHandler: self.documentFormatting)
723+
case let request as RequestAndReply<DocumentRangeFormattingRequest>:
724+
await self.handleRequest(for: request, requestHandler: self.documentRangeFormatting)
723725
case let request as RequestAndReply<DocumentHighlightRequest>:
724726
await self.handleRequest(for: request, requestHandler: self.documentSymbolHighlight)
725727
case let request as RequestAndReply<DocumentSemanticTokensDeltaRequest>:
@@ -1038,6 +1040,7 @@ extension SourceKitLSPServer {
10381040
),
10391041
codeLensProvider: CodeLensOptions(),
10401042
documentFormattingProvider: .value(DocumentFormattingOptions(workDoneProgress: false)),
1043+
documentRangeFormattingProvider: .value(DocumentRangeFormattingOptions(workDoneProgress: false)),
10411044
renameProvider: .value(RenameOptions(prepareProvider: true)),
10421045
colorProvider: .bool(true),
10431046
foldingRangeProvider: foldingRangeOptions,
@@ -1531,6 +1534,14 @@ extension SourceKitLSPServer {
15311534
return try await languageService.documentFormatting(req)
15321535
}
15331536

1537+
func documentRangeFormatting(
1538+
_ req: DocumentRangeFormattingRequest,
1539+
workspace: Workspace,
1540+
languageService: LanguageService
1541+
) async throws -> [TextEdit]? {
1542+
return try await languageService.documentRangeFormatting(req)
1543+
}
1544+
15341545
func colorPresentation(
15351546
_ req: ColorPresentationRequest,
15361547
workspace: Workspace,

Sources/SourceKitLSP/Swift/DocumentFormatting.swift

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,20 +138,46 @@ private func edits(from original: DocumentSnapshot, to edited: String) -> [TextE
138138

139139
extension SwiftLanguageService {
140140
package func documentFormatting(_ req: DocumentFormattingRequest) async throws -> [TextEdit]? {
141-
let snapshot = try documentManager.latestSnapshot(req.textDocument.uri)
141+
return try await format(
142+
textDocument: req.textDocument,
143+
options: req.options
144+
)
145+
}
146+
147+
package func documentRangeFormatting(_ req: DocumentRangeFormattingRequest) async throws -> [TextEdit]? {
148+
return try await format(
149+
textDocument: req.textDocument,
150+
options: req.options,
151+
range: req.range
152+
)
153+
}
154+
155+
private func format(
156+
textDocument: TextDocumentIdentifier,
157+
options: FormattingOptions,
158+
range: Range<Position>? = nil
159+
) async throws -> [TextEdit]? {
160+
let snapshot = try documentManager.latestSnapshot(textDocument.uri)
142161

143162
guard let swiftFormat else {
144163
throw ResponseError.unknown(
145164
"Formatting not supported because the toolchain is missing the swift-format executable"
146165
)
147166
}
148167

149-
let process = TSCBasic.Process(
150-
args: swiftFormat.pathString,
168+
var args = try [
169+
swiftFormat.pathString,
151170
"format",
152171
"--configuration",
153-
try swiftFormatConfiguration(for: req.textDocument.uri, options: req.options)
154-
)
172+
swiftFormatConfiguration(for: textDocument.uri, options: options),
173+
]
174+
if let range {
175+
args += [
176+
"--offsets",
177+
"\(snapshot.utf8Offset(of: range.lowerBound)):\(snapshot.utf8Offset(of: range.upperBound))",
178+
]
179+
}
180+
let process = TSCBasic.Process(arguments: args)
155181
let writeStream = try process.launch()
156182

157183
// Send the file to format to swift-format's stdin. That way we don't have to write it to a file.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import LanguageServerProtocol
14+
import SKLogging
15+
import SKTestSupport
16+
import SourceKitLSP
17+
import XCTest
18+
19+
final class RangeFormattingTests: XCTestCase {
20+
func testOnlyFormatsSpecifiedLines() async throws {
21+
try await SkipUnless.toolchainContainsSwiftFormat()
22+
let testClient = try await TestSourceKitLSPClient()
23+
let uri = DocumentURI(for: .swift)
24+
25+
let positions = testClient.openDocument(
26+
"""
27+
func foo() {
28+
if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() {
29+
1️⃣// do stuff2️⃣
30+
}
31+
}
32+
""",
33+
uri: uri
34+
)
35+
36+
let response = try await testClient.send(
37+
DocumentRangeFormattingRequest(
38+
textDocument: TextDocumentIdentifier(uri),
39+
range: positions["1️⃣"]..<positions["2️⃣"],
40+
options: FormattingOptions(tabSize: 2, insertSpaces: true)
41+
)
42+
)
43+
44+
let edits = try XCTUnwrap(response)
45+
XCTAssertEqual(
46+
edits,
47+
[
48+
TextEdit(range: Range(positions["1️⃣"]), newText: " ")
49+
]
50+
)
51+
}
52+
53+
func testOnlyFormatsSpecifiedColumns() async throws {
54+
try await SkipUnless.toolchainContainsSwiftFormat()
55+
let testClient = try await TestSourceKitLSPClient()
56+
let uri = DocumentURI(for: .swift)
57+
58+
let positions = testClient.openDocument(
59+
"""
60+
func foo() {
61+
if let SomeReallyLongVar 1️⃣= 2️⃣ 3️⃣Some.More.Stuff(), let a = myfunc() {
62+
// do stuff
63+
}
64+
}
65+
""",
66+
uri: uri
67+
)
68+
69+
let response = try await testClient.send(
70+
DocumentRangeFormattingRequest(
71+
textDocument: TextDocumentIdentifier(uri),
72+
range: positions["1️⃣"]..<positions["3️⃣"],
73+
options: FormattingOptions(tabSize: 2, insertSpaces: true)
74+
)
75+
)
76+
77+
let edits = try XCTUnwrap(response)
78+
XCTAssertEqual(
79+
edits,
80+
[
81+
TextEdit(range: positions["2️⃣"]..<positions["3️⃣"], newText: "")
82+
]
83+
)
84+
}
85+
86+
func testFormatsMultipleLines() async throws {
87+
try await SkipUnless.toolchainContainsSwiftFormat()
88+
let testClient = try await TestSourceKitLSPClient()
89+
let uri = DocumentURI(for: .swift)
90+
91+
let positions = testClient.openDocument(
92+
"""
93+
1️⃣func foo() {
94+
2️⃣if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() {
95+
3️⃣// do stuff
96+
4️⃣}
97+
}5️⃣
98+
""",
99+
uri: uri
100+
)
101+
102+
let response = try await testClient.send(
103+
DocumentRangeFormattingRequest(
104+
textDocument: TextDocumentIdentifier(uri),
105+
range: positions["1️⃣"]..<positions["5️⃣"],
106+
options: FormattingOptions(tabSize: 4, insertSpaces: true)
107+
)
108+
)
109+
110+
let edits = try XCTUnwrap(response)
111+
XCTAssertEqual(
112+
edits,
113+
[
114+
TextEdit(range: Range(positions["2️⃣"]), newText: " "),
115+
TextEdit(range: Range(positions["3️⃣"]), newText: " "),
116+
TextEdit(range: Range(positions["4️⃣"]), newText: " "),
117+
]
118+
)
119+
}
120+
}

0 commit comments

Comments
 (0)