Skip to content

Commit

Permalink
support range formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
MahdiBM committed Nov 8, 2024
1 parent e73e8b7 commit 8f3253d
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 5 deletions.
4 changes: 4 additions & 0 deletions Sources/SourceKitLSP/Clang/ClangLanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,10 @@ extension ClangLanguageService {
return try await forwardRequestToClangd(req)
}

func documentRangeFormatting(_ req: DocumentRangeFormattingRequest) async throws -> [TextEdit]? {
return try await forwardRequestToClangd(req)
}

func codeAction(_ req: CodeActionRequest) async throws -> CodeActionRequestResponse? {
return try await forwardRequestToClangd(req)
}
Expand Down
1 change: 1 addition & 0 deletions Sources/SourceKitLSP/LanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ package protocol LanguageService: AnyObject, Sendable {
func codeLens(_ req: CodeLensRequest) async throws -> [CodeLens]
func documentDiagnostic(_ req: DocumentDiagnosticsRequest) async throws -> DocumentDiagnosticReport
func documentFormatting(_ req: DocumentFormattingRequest) async throws -> [TextEdit]?
func documentRangeFormatting(_ req: DocumentRangeFormattingRequest) async throws -> [TextEdit]?

// MARK: - Rename

Expand Down
11 changes: 11 additions & 0 deletions Sources/SourceKitLSP/SourceKitLSPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,8 @@ extension SourceKitLSPServer: QueueBasedMessageHandler {
await self.handleRequest(for: request, requestHandler: self.documentDiagnostic)
case let request as RequestAndReply<DocumentFormattingRequest>:
await self.handleRequest(for: request, requestHandler: self.documentFormatting)
case let request as RequestAndReply<DocumentRangeFormattingRequest>:
await self.handleRequest(for: request, requestHandler: self.documentRangeFormatting)
case let request as RequestAndReply<DocumentHighlightRequest>:
await self.handleRequest(for: request, requestHandler: self.documentSymbolHighlight)
case let request as RequestAndReply<DocumentSemanticTokensDeltaRequest>:
Expand Down Expand Up @@ -1038,6 +1040,7 @@ extension SourceKitLSPServer {
),
codeLensProvider: CodeLensOptions(),
documentFormattingProvider: .value(DocumentFormattingOptions(workDoneProgress: false)),
documentRangeFormattingProvider: .value(DocumentRangeFormattingOptions(workDoneProgress: false)),
renameProvider: .value(RenameOptions(prepareProvider: true)),
colorProvider: .bool(true),
foldingRangeProvider: foldingRangeOptions,
Expand Down Expand Up @@ -1531,6 +1534,14 @@ extension SourceKitLSPServer {
return try await languageService.documentFormatting(req)
}

func documentRangeFormatting(
_ req: DocumentRangeFormattingRequest,
workspace: Workspace,
languageService: LanguageService
) async throws -> [TextEdit]? {
return try await languageService.documentRangeFormatting(req)
}

func colorPresentation(
_ req: ColorPresentationRequest,
workspace: Workspace,
Expand Down
36 changes: 31 additions & 5 deletions Sources/SourceKitLSP/Swift/DocumentFormatting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,20 +136,46 @@ private func edits(from original: DocumentSnapshot, to edited: String) -> [TextE

extension SwiftLanguageService {
package func documentFormatting(_ req: DocumentFormattingRequest) async throws -> [TextEdit]? {
let snapshot = try documentManager.latestSnapshot(req.textDocument.uri)
return try await format(
textDocument: req.textDocument,
options: req.options
)
}

package func documentRangeFormatting(_ req: DocumentRangeFormattingRequest) async throws -> [TextEdit]? {
return try await format(
textDocument: req.textDocument,
options: req.options,
range: req.range
)
}

private func format(
textDocument: TextDocumentIdentifier,
options: FormattingOptions,
range: Range<Position>? = nil
) async throws -> [TextEdit]? {
let snapshot = try documentManager.latestSnapshot(textDocument.uri)

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

let process = TSCBasic.Process(
args: swiftFormat.pathString,
var args = try [
swiftFormat.pathString,
"format",
"--configuration",
try swiftFormatConfiguration(for: req.textDocument.uri, options: req.options)
)
swiftFormatConfiguration(for: textDocument.uri, options: options),
]
if let range {
args += [
"--offsets",
"\(snapshot.utf8Offset(of: range.lowerBound)):\(snapshot.utf8Offset(of: range.upperBound))",
]
}
let process = TSCBasic.Process(arguments: args)
let writeStream = try process.launch()

// Send the file to format to swift-format's stdin. That way we don't have to write it to a file.
Expand Down
120 changes: 120 additions & 0 deletions Tests/SourceKitLSPTests/RangeFormattingTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import LanguageServerProtocol
import SKLogging
import SKTestSupport
import SourceKitLSP
import XCTest

final class RangeFormattingTests: XCTestCase {
func testOnlyFormatsSpecifiedLines() async throws {
try await SkipUnless.toolchainContainsSwiftFormat()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)

let positions = testClient.openDocument(
"""
func foo() {
if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() {
1️⃣// do stuff2️⃣
}
}
""",
uri: uri
)

let response = try await testClient.send(
DocumentRangeFormattingRequest(
textDocument: TextDocumentIdentifier(uri),
range: positions["1️⃣"]..<positions["2️⃣"],
options: FormattingOptions(tabSize: 2, insertSpaces: true)
)
)

let edits = try XCTUnwrap(response)
XCTAssertEqual(
edits,
[
TextEdit(range: Range(positions["1️⃣"]), newText: " ")
]
)
}

func testOnlyFormatsSpecifiedColumns() async throws {
try await SkipUnless.toolchainContainsSwiftFormat()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)

let positions = testClient.openDocument(
"""
func foo() {
if let SomeReallyLongVar 1️⃣= 2️⃣ 3️⃣Some.More.Stuff(), let a = myfunc() {
// do stuff
}
}
""",
uri: uri
)

let response = try await testClient.send(
DocumentRangeFormattingRequest(
textDocument: TextDocumentIdentifier(uri),
range: positions["1️⃣"]..<positions["3️⃣"],
options: FormattingOptions(tabSize: 2, insertSpaces: true)
)
)

let edits = try XCTUnwrap(response)
XCTAssertEqual(
edits,
[
TextEdit(range: positions["2️⃣"]..<positions["3️⃣"], newText: "")
]
)
}

func testFormatsMultipleLines() async throws {
try await SkipUnless.toolchainContainsSwiftFormat()
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)

let positions = testClient.openDocument(
"""
1️⃣func foo() {
2️⃣if let SomeReallyLongVar = Some.More.Stuff(), let a = myfunc() {
3️⃣// do stuff
4️⃣}
}5️⃣
""",
uri: uri
)

let response = try await testClient.send(
DocumentRangeFormattingRequest(
textDocument: TextDocumentIdentifier(uri),
range: positions["1️⃣"]..<positions["5️⃣"],
options: FormattingOptions(tabSize: 4, insertSpaces: true)
)
)

let edits = try XCTUnwrap(response)
XCTAssertEqual(
edits,
[
TextEdit(range: Range(positions["2️⃣"]), newText: " "),
TextEdit(range: Range(positions["3️⃣"]), newText: " "),
TextEdit(range: Range(positions["4️⃣"]), newText: " "),
]
)
}
}

0 comments on commit 8f3253d

Please sign in to comment.