From 8f3253d773fc4ff2968546509d9e8f89dd1f0488 Mon Sep 17 00:00:00 2001 From: MahdiBM Date: Wed, 6 Nov 2024 00:05:09 +0330 Subject: [PATCH] support range formatting --- .../Clang/ClangLanguageService.swift | 4 + Sources/SourceKitLSP/LanguageService.swift | 1 + Sources/SourceKitLSP/SourceKitLSPServer.swift | 11 ++ .../Swift/DocumentFormatting.swift | 36 +++++- .../RangeFormattingTests.swift | 120 ++++++++++++++++++ 5 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 Tests/SourceKitLSPTests/RangeFormattingTests.swift diff --git a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift index dbe554bc4..542d932b5 100644 --- a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift +++ b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift @@ -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) } diff --git a/Sources/SourceKitLSP/LanguageService.swift b/Sources/SourceKitLSP/LanguageService.swift index efd90245d..bfcc09b2c 100644 --- a/Sources/SourceKitLSP/LanguageService.swift +++ b/Sources/SourceKitLSP/LanguageService.swift @@ -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 diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 671fd8d8c..504d7aad9 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -720,6 +720,8 @@ extension SourceKitLSPServer: QueueBasedMessageHandler { await self.handleRequest(for: request, requestHandler: self.documentDiagnostic) case let request as RequestAndReply: await self.handleRequest(for: request, requestHandler: self.documentFormatting) + case let request as RequestAndReply: + await self.handleRequest(for: request, requestHandler: self.documentRangeFormatting) case let request as RequestAndReply: await self.handleRequest(for: request, requestHandler: self.documentSymbolHighlight) case let request as RequestAndReply: @@ -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, @@ -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, diff --git a/Sources/SourceKitLSP/Swift/DocumentFormatting.swift b/Sources/SourceKitLSP/Swift/DocumentFormatting.swift index 9f861fbdd..5a2e0b93f 100644 --- a/Sources/SourceKitLSP/Swift/DocumentFormatting.swift +++ b/Sources/SourceKitLSP/Swift/DocumentFormatting.swift @@ -136,7 +136,26 @@ 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? = nil + ) async throws -> [TextEdit]? { + let snapshot = try documentManager.latestSnapshot(textDocument.uri) guard let swiftFormat else { throw ResponseError.unknown( @@ -144,12 +163,19 @@ extension SwiftLanguageService { ) } - 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. diff --git a/Tests/SourceKitLSPTests/RangeFormattingTests.swift b/Tests/SourceKitLSPTests/RangeFormattingTests.swift new file mode 100644 index 000000000..2dc957075 --- /dev/null +++ b/Tests/SourceKitLSPTests/RangeFormattingTests.swift @@ -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️⃣"]..