diff --git a/Sources/LanguageServerProtocol/CodeAction.swift b/Sources/LanguageServerProtocol/CodeAction.swift index 28b222dc8..93d34209e 100644 --- a/Sources/LanguageServerProtocol/CodeAction.swift +++ b/Sources/LanguageServerProtocol/CodeAction.swift @@ -48,30 +48,6 @@ public struct CodeActionRequest: TextDocumentRequest, Hashable { self.context = context self.textDocument = textDocument } - - public func injectMetadata(atResponse response: CodeActionRequestResponse?) -> CodeActionRequestResponse? { - let metadata = SourceKitLSPCommandMetadata(textDocument: textDocument) - guard let data = try? JSONEncoder().encode(metadata), - let metadataArgument = try? JSONDecoder().decode(CommandArgumentType.self, from: data) else - { - log("failed to inject metadata in codeAction response", level: .error) - return nil - } - switch response { - case .codeActions(var codeActions)?: - for i in 0.. SourceKitLSPCommandMetadata? { + guard case .dictionary(let textDocumentDict)? = dictionary[CodingKeys.sourcekitlsp_textDocument.stringValue], + case .string(let urlString)? = textDocumentDict[TextDocumentIdentifier.CodingKeys.url.stringValue], + let url = URL(string: urlString) else + { + return nil + } + let textDocument = TextDocumentIdentifier(url) + return SourceKitLSPCommandMetadata(textDocument: textDocument) + } + + public var sourcekitlsp_textDocument: TextDocumentIdentifier + + public init(textDocument: TextDocumentIdentifier) { + self.sourcekitlsp_textDocument = textDocument + } + + func asCommandArgument() -> CommandArgumentType { + let textDocumentArgument = CommandArgumentType.dictionary( + [TextDocumentIdentifier.CodingKeys.url.stringValue: .string(sourcekitlsp_textDocument.url.absoluteString)] + ) + return .dictionary([CodingKeys.sourcekitlsp_textDocument.stringValue: textDocumentArgument]) + } +} + +extension CodeActionRequest { + public func injectMetadata(atResponse response: CodeActionRequestResponse?) -> CodeActionRequestResponse? { + let metadata = SourceKitLSPCommandMetadata(textDocument: textDocument) + let metadataArgument = metadata.asCommandArgument() + switch response { + case .codeActions(var codeActions)?: + for i in 0.., workspace: Workspace) { @@ -503,11 +505,12 @@ extension SourceKitServer { fallback: @autoclosure () -> PositionRequest.Response) where PositionRequest: TextDocumentRequest { - sendRequest(req, url: req.params.textDocument.url, workspace: workspace, resultHandler: resultHandler, fallback: fallback) + sendRequest(req, params: req.params, url: req.params.textDocument.url, workspace: workspace, resultHandler: resultHandler, fallback: fallback) } func sendRequest( _ req: Request, + params: PositionRequest, url: URL, workspace: Workspace, resultHandler: ((LSPResult) -> LSPResult)? = nil, @@ -518,7 +521,7 @@ extension SourceKitServer { return } - let id = service.send(req.params, queue: DispatchQueue.global()) { result in + let id = service.send(params, queue: DispatchQueue.global()) { result in req.reply(resultHandler?(result) ?? result) } req.cancellationToken.addCancellationHandler { [weak service] in diff --git a/Sources/SourceKit/sourcekitd/SwiftCommand.swift b/Sources/SourceKit/sourcekitd/SwiftCommand.swift index cb130ce6b..6820b0212 100644 --- a/Sources/SourceKit/sourcekitd/SwiftCommand.swift +++ b/Sources/SourceKit/sourcekitd/SwiftCommand.swift @@ -22,14 +22,15 @@ public let builtinSwiftCommands: [String] = [] /// A `Command` that should be executed by Swift's language server. public protocol SwiftCommand: Codable, Hashable { static var identifier: String { get } + static func decode(fromDictionary dictionary: [String: CommandArgumentType]) -> Self? var title: String { get set } + func asCommandArgument() -> CommandArgumentType } extension SwiftCommand { /// Converts this `SwiftCommand` to a generic LSP `Command` object. public func asCommand() throws -> Command { - let data = try JSONEncoder().encode(self) - let argument = try JSONDecoder().decode(CommandArgumentType.self, from: data) + let argument = asCommandArgument() return Command(title: title, command: Self.identifier, arguments: [argument]) } } @@ -50,14 +51,12 @@ extension ExecuteCommandRequest { guard case let .dictionary(dictionary) = argument else { return nil } - guard let data = try? JSONEncoder().encode(dictionary) else { - return nil - } - return try? JSONDecoder().decode(type, from: data) + return type.decode(fromDictionary: dictionary) } } public struct SemanticRefactorCommand: SwiftCommand { + public static var identifier: String { return "semantic.refactor.command" } @@ -80,6 +79,26 @@ public struct SemanticRefactorCommand: SwiftCommand { /// The text document related to the refactoring action. public var textDocument: TextDocumentIdentifier + public static func decode(fromDictionary dictionary: [String: CommandArgumentType]) -> SemanticRefactorCommand? { + guard case .dictionary(let dict)? = dictionary[CodingKeys.textDocument.stringValue], + case .string(let title)? = dictionary[CodingKeys.title.stringValue], + case .string(let actionString)? = dictionary[CodingKeys.actionString.stringValue], + case .int(let line)? = dictionary[CodingKeys.line.stringValue], + case .int(let column)? = dictionary[CodingKeys.column.stringValue], + case .int(let length)? = dictionary[CodingKeys.length.stringValue], + case .string(let urlString)? = dict[TextDocumentIdentifier.CodingKeys.url.stringValue], + let url = URL(string: urlString) else + { + return nil + } + return SemanticRefactorCommand(title: title, + actionString: actionString, + line: line, + column: column, + length: length, + textDocument: TextDocumentIdentifier(url)) + } + public init(title: String, actionString: String, line: Int, column: Int, length: Int, textDocument: TextDocumentIdentifier) { self.title = title self.actionString = actionString @@ -88,4 +107,16 @@ public struct SemanticRefactorCommand: SwiftCommand { self.length = length self.textDocument = textDocument } + + public func asCommandArgument() -> CommandArgumentType { + let textDocumentArgument = CommandArgumentType.dictionary( + [TextDocumentIdentifier.CodingKeys.url.stringValue: .string(textDocument.url.absoluteString)] + ) + return .dictionary([CodingKeys.title.stringValue: .string(title), + CodingKeys.actionString.stringValue: .string(actionString), + CodingKeys.line.stringValue: .int(line), + CodingKeys.column.stringValue: .int(column), + CodingKeys.length.stringValue: .int(length), + CodingKeys.textDocument.stringValue: textDocumentArgument]) + } } diff --git a/Tests/SourceKitTests/CodeActionTests.swift b/Tests/SourceKitTests/CodeActionTests.swift index 0c03583da..d68b0bf63 100644 --- a/Tests/SourceKitTests/CodeActionTests.swift +++ b/Tests/SourceKitTests/CodeActionTests.swift @@ -114,7 +114,7 @@ final class CodeActionTests: XCTestCase { let data = try! JSONEncoder().encode(metadata) return try! JSONDecoder().decode(CommandArgumentType.self, from: data) }() - XCTAssertEqual(expectedMetadata, .dictionary(["textDocument": ["uri": "file:///a.swift"]])) + XCTAssertEqual(expectedMetadata, .dictionary(["sourcekitlsp_textDocument": ["uri": "file:///a.swift"]])) let command = Command(title: "Title", command: "Command", arguments: [1, "text", 2.2, nil]) let codeAction = CodeAction(title: "1") let codeAction2 = CodeAction(title: "2", command: command) diff --git a/Tests/SourceKitTests/ExecuteCommandTests.swift b/Tests/SourceKitTests/ExecuteCommandTests.swift new file mode 100644 index 000000000..819d76810 --- /dev/null +++ b/Tests/SourceKitTests/ExecuteCommandTests.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2019 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 SKSupport +import SKTestSupport +import XCTest + +@testable import SourceKit + +final class ExecuteCommandTests: XCTestCase { + func testLSPCommandMetadataRetrieval() { + var req = ExecuteCommandRequest(command: "", arguments: nil) + XCTAssertNil(req.metadata) + req.arguments = [1, 2, ""] + XCTAssertNil(req.metadata) + let url = URL(fileURLWithPath: "/a.swift") + let textDocument = TextDocumentIdentifier(url) + let metadata = SourceKitLSPCommandMetadata(textDocument: textDocument) + req.arguments = [metadata.asCommandArgument(), 1, 2, ""] + XCTAssertNil(req.metadata) + req.arguments = [1, 2, "", [metadata.asCommandArgument()]] + XCTAssertNil(req.metadata) + req.arguments = [1, 2, "", metadata.asCommandArgument()] + XCTAssertEqual(req.metadata, metadata) + req.arguments = [metadata.asCommandArgument()] + XCTAssertEqual(req.metadata, metadata) + } + + func testLSPCommandMetadataRemoval() { + var req = ExecuteCommandRequest(command: "", arguments: nil) + XCTAssertNil(req.argumentsWithoutLSPMetadata) + req.arguments = [1, 2, ""] + XCTAssertEqual(req.arguments, req.argumentsWithoutLSPMetadata) + let url = URL(fileURLWithPath: "/a.swift") + let textDocument = TextDocumentIdentifier(url) + let metadata = SourceKitLSPCommandMetadata(textDocument: textDocument) + req.arguments = [metadata.asCommandArgument(), 1, 2, ""] + XCTAssertEqual(req.arguments, req.argumentsWithoutLSPMetadata) + req.arguments = [1, 2, "", [metadata.asCommandArgument()]] + XCTAssertEqual(req.arguments, req.argumentsWithoutLSPMetadata) + req.arguments = [1, 2, "", metadata.asCommandArgument()] + XCTAssertEqual([1, 2, ""], req.argumentsWithoutLSPMetadata) + } +} diff --git a/Tests/SourceKitTests/XCTestManifests.swift b/Tests/SourceKitTests/XCTestManifests.swift index 1f9af786a..99cfc77c8 100644 --- a/Tests/SourceKitTests/XCTestManifests.swift +++ b/Tests/SourceKitTests/XCTestManifests.swift @@ -38,6 +38,16 @@ extension DocumentSymbolTest { ] } +extension ExecuteCommandTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__ExecuteCommandTests = [ + ("testLSPCommandMetadataRemoval", testLSPCommandMetadataRemoval), + ("testLSPCommandMetadataRetrieval", testLSPCommandMetadataRetrieval), + ] +} + extension FoldingRangeTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -92,6 +102,7 @@ public func __allTests() -> [XCTestCaseEntry] { testCase(CodeActionTests.__allTests__CodeActionTests), testCase(DocumentColorTests.__allTests__DocumentColorTests), testCase(DocumentSymbolTest.__allTests__DocumentSymbolTest), + testCase(ExecuteCommandTests.__allTests__ExecuteCommandTests), testCase(FoldingRangeTests.__allTests__FoldingRangeTests), testCase(LocalClangTests.__allTests__LocalClangTests), testCase(LocalSwiftTests.__allTests__LocalSwiftTests),