diff --git a/Sources/LanguageServerProtocol/CMakeLists.txt b/Sources/LanguageServerProtocol/CMakeLists.txt index bac242a72..c262e035d 100644 --- a/Sources/LanguageServerProtocol/CMakeLists.txt +++ b/Sources/LanguageServerProtocol/CMakeLists.txt @@ -64,6 +64,7 @@ add_library(LanguageServerProtocol STATIC Requests/InlineValueRefreshRequest.swift Requests/InlineValueRequest.swift Requests/LinkedEditingRangeRequest.swift + Requests/MacroExpansionRequest.swift Requests/MonikersRequest.swift Requests/OpenInterfaceRequest.swift Requests/PollIndexRequest.swift @@ -107,6 +108,7 @@ add_library(LanguageServerProtocol STATIC SupportTypes/LocationLink.swift SupportTypes/LocationsOrLocationLinksResponse.swift SupportTypes/LSPAny.swift + SupportTypes/MacroExpansion.swift SupportTypes/MarkupContent.swift SupportTypes/NotebookCellTextDocumentFilter.swift SupportTypes/NotebookDocument.swift diff --git a/Sources/LanguageServerProtocol/Error.swift b/Sources/LanguageServerProtocol/Error.swift index c2de5b598..de5972911 100644 --- a/Sources/LanguageServerProtocol/Error.swift +++ b/Sources/LanguageServerProtocol/Error.swift @@ -107,6 +107,11 @@ extension ResponseError { message: "request cancelled by server" ) + public static var unsupportedMethod: ResponseError = ResponseError( + code: .unknownErrorCode, + message: "unsupported method" + ) + public static func workspaceNotOpen(_ uri: DocumentURI) -> ResponseError { return ResponseError(code: .workspaceNotOpen, message: "No workspace containing '\(uri)' found") } diff --git a/Sources/LanguageServerProtocol/Messages.swift b/Sources/LanguageServerProtocol/Messages.swift index 38c4c1d32..b8a3687dd 100644 --- a/Sources/LanguageServerProtocol/Messages.swift +++ b/Sources/LanguageServerProtocol/Messages.swift @@ -55,6 +55,7 @@ public let builtinRequests: [_RequestType.Type] = [ InlineValueRefreshRequest.self, InlineValueRequest.self, LinkedEditingRangeRequest.self, + MacroExpansionRequest.self, MonikersRequest.self, OpenInterfaceRequest.self, PollIndexRequest.self, diff --git a/Sources/LanguageServerProtocol/Requests/MacroExpansionRequest.swift b/Sources/LanguageServerProtocol/Requests/MacroExpansionRequest.swift new file mode 100644 index 000000000..2a3e37d43 --- /dev/null +++ b/Sources/LanguageServerProtocol/Requests/MacroExpansionRequest.swift @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// Request the expansion of the macro at a given use site. +/// **LSP Extension**. +/// +/// - Parameters: +/// - textDocument: The document in which the macro is used. +/// - range: The range at which the macro is used. +/// +/// - Returns: The macro expansion. +public struct MacroExpansionRequest: TextDocumentRequest, Hashable { + public static let method: String = "sourcekit-lsp/macroExpansion" + public typealias Response = [MacroExpansion] + + /// The document in which the macro is used. + public var textDocument: TextDocumentIdentifier + + /// The position at which the macro is used. + @CustomCodable + public var range: Range + + public init( + textDocument: TextDocumentIdentifier, + range: Range + ) { + self.textDocument = textDocument + self._range = CustomCodable(wrappedValue: range) + } +} diff --git a/Sources/LanguageServerProtocol/SupportTypes/MacroExpansion.swift b/Sources/LanguageServerProtocol/SupportTypes/MacroExpansion.swift new file mode 100644 index 000000000..d1d48aabe --- /dev/null +++ b/Sources/LanguageServerProtocol/SupportTypes/MacroExpansion.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// The expansion of a macro in a source file. +public struct MacroExpansion: ResponseType, Hashable { + /// The position in the source file where the expansion would be inserted. + public let position: Position + + /// The Swift code that the macro expands to. + public let sourceText: String + + public init(position: Position, sourceText: String) { + self.position = position + self.sourceText = sourceText + } +} diff --git a/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift b/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift index 609427f77..b1a258010 100644 --- a/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift +++ b/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift @@ -572,6 +572,10 @@ extension ClangLanguageServerShim { return try await forwardRequestToClangd(req) } + func macroExpansion(_ req: MacroExpansionRequest) async throws -> [MacroExpansion] { + throw ResponseError.unsupportedMethod + } + func foldingRange(_ req: FoldingRangeRequest) async throws -> [FoldingRange]? { guard self.capabilities?.foldingRangeProvider?.isSupported ?? false else { return nil @@ -580,7 +584,7 @@ extension ClangLanguageServerShim { } func openInterface(_ request: OpenInterfaceRequest) async throws -> InterfaceDetails? { - throw ResponseError.unknown("unsupported method") + throw ResponseError.unsupportedMethod } // MARK: - Other diff --git a/Sources/SourceKitLSP/SourceKitServer.swift b/Sources/SourceKitLSP/SourceKitServer.swift index eea1cfabc..5892c03e2 100644 --- a/Sources/SourceKitLSP/SourceKitServer.swift +++ b/Sources/SourceKitLSP/SourceKitServer.swift @@ -624,6 +624,8 @@ extension SourceKitServer: MessageHandler { requestHandler: self.documentDiagnostic, fallback: .full(.init(items: [])) ) + case let request as Request: + await self.handleRequest(for: request, requestHandler: self.macroExpansion, fallback: []) default: reply(.failure(ResponseError.methodNotFound(R.method))) } @@ -1386,6 +1388,14 @@ extension SourceKitServer { return try await languageService.documentDiagnostic(req) } + func macroExpansion( + _ request: MacroExpansionRequest, + workspace: Workspace, + languageService: ToolchainLanguageServer + ) async throws -> [MacroExpansion] { + return try await languageService.macroExpansion(request) + } + /// Converts a location from the symbol index to an LSP location. /// /// - Parameter location: The symbol index location diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift index bb48be76d..788a83b53 100644 --- a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift +++ b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift @@ -577,11 +577,11 @@ extension SwiftLanguageServer { /// Returns true if the `ToolchainLanguageServer` will take ownership of the request. public func definition(_ request: DefinitionRequest) async throws -> LocationsOrLocationLinksResponse? { - throw ResponseError.unknown("unsupported method") + throw ResponseError.unsupportedMethod } public func declaration(_ request: DeclarationRequest) async throws -> LocationsOrLocationLinksResponse? { - throw ResponseError.unknown("unsupported method") + throw ResponseError.unsupportedMethod } public func hover(_ req: HoverRequest) async throws -> HoverResponse? { @@ -1299,6 +1299,32 @@ extension SwiftLanguageServer { return .full(RelatedFullDocumentDiagnosticReport(items: diagnostics)) } + public func macroExpansion(_ req: MacroExpansionRequest) async throws -> [MacroExpansion] { + let command = SemanticRefactorCommand( + title: "Expand Macro", + actionString: "source.refactoring.kind.expand.macro", + positionRange: req.range, + textDocument: req.textDocument + ) + + do { + let refactor = try await semanticRefactoring(command) + + guard let edits = refactor.edit.changes?[req.textDocument.uri] else { + return [] + } + + return edits.map { edit in + MacroExpansion( + position: edit.range.lowerBound, + sourceText: edit.newText + ) + } + } catch SemanticRefactoringError.noEditsNeeded { + return [] + } + } + public func executeCommand(_ req: ExecuteCommandRequest) async throws -> LSPAny? { // TODO: If there's support for several types of commands, we might need to structure this similarly to the code actions request. guard let sourceKitServer else { diff --git a/Sources/SourceKitLSP/ToolchainLanguageServer.swift b/Sources/SourceKitLSP/ToolchainLanguageServer.swift index 471a8e19e..6d012a268 100644 --- a/Sources/SourceKitLSP/ToolchainLanguageServer.swift +++ b/Sources/SourceKitLSP/ToolchainLanguageServer.swift @@ -104,6 +104,7 @@ public protocol ToolchainLanguageServer: AnyObject { func codeAction(_ req: CodeActionRequest) async throws -> CodeActionRequestResponse? func inlayHint(_ req: InlayHintRequest) async throws -> [InlayHint] func documentDiagnostic(_ req: DocumentDiagnosticsRequest) async throws -> DocumentDiagnosticReport + func macroExpansion(_ req: MacroExpansionRequest) async throws -> [MacroExpansion] // MARK: - Other