diff --git a/Documentation/LSP Extensions.md b/Documentation/LSP Extensions.md index fb6c5a97f..bd777a94b 100644 --- a/Documentation/LSP Extensions.md +++ b/Documentation/LSP Extensions.md @@ -73,39 +73,99 @@ Added case identifier = 'identifier' ``` -## `textDocument/interface` +## `WorkspaceFolder` -New request that request a textual interface of a module to display in the IDE. Used internally within SourceKit-LSP +Added field: + +```ts +/** + * Build settings that should be used for this workspace. + * + * For arguments that have a single value (like the build configuration), this takes precedence over the global + * options set when launching sourcekit-lsp. For all other options, the values specified in the workspace-specific + * build setup are appended to the global options. + */ +var buildSetup?: WorkspaceBuildSetup +``` -- params: `OpenInterfaceParams` -- result: `InterfaceDetails?` +with ```ts -export interface OpenInterfaceRequest: TextDocumentRequest, Hashable { +/** + * The configuration to build a workspace in. + */ +export enum BuildConfiguration { + case debug = 'debug' + case release = 'release' +} + +/** + * The type of workspace; default workspace type selection logic can be overridden. + */ +export enum WorkspaceType { + buildServer = 'buildServer' + compilationDatabase = 'compilationDatabase' + swiftPM = 'swiftPM' +} + +/// Build settings that should be used for a workspace. +interface WorkspaceBuildSetup { /** - * The document whose compiler arguments should be used to generate the interface. + * The configuration that the workspace should be built in. */ - textDocument: TextDocumentIdentifier + buildConfiguration?: BuildConfiguration /** - * The module to generate an index for. + * The default workspace type to use for this workspace. */ - moduleName: string + defaultWorkspaceType?: WorkspaceType /** - * The module group name. + * The build directory for the workspace. */ - groupName?: string + scratchPath?: DocumentURI + + /** + * Arguments to be passed to any C compiler invocations. + */ + cFlags?: string[] + + /** + * Arguments to be passed to any C++ compiler invocations. + */ + cxxFlags?: string[] + + /** + * Arguments to be passed to any linker invocations. + */ + linkerFlags?: string[] /** - * The symbol USR to search for in the generated module interface. + * Arguments to be passed to any Swift compiler invocations. */ - symbolUSR?: string + swiftFlags?: string[] } +``` + +## `textDocument/completion` + +Added field: + +```ts +/** + * Options to control code completion behavior in Swift + */ +sourcekitlspOptions?: SKCompletionOptions +``` + +with -interface InterfaceDetails { - uri: DocumentURI - position?: Position +```ts +interface SKCompletionOptions { + /** + * The maximum number of completion results to return, or `null` for unlimited. + */ + maxResults?: int } ``` diff --git a/Sources/LanguageServerProtocol/CMakeLists.txt b/Sources/LanguageServerProtocol/CMakeLists.txt index e3f16ce7d..2ccbef4d3 100644 --- a/Sources/LanguageServerProtocol/CMakeLists.txt +++ b/Sources/LanguageServerProtocol/CMakeLists.txt @@ -66,7 +66,6 @@ add_library(LanguageServerProtocol STATIC Requests/InlineValueRequest.swift Requests/LinkedEditingRangeRequest.swift Requests/MonikersRequest.swift - Requests/OpenInterfaceRequest.swift Requests/PollIndexRequest.swift Requests/PrepareRenameRequest.swift Requests/ReferencesRequest.swift diff --git a/Sources/LanguageServerProtocol/Messages.swift b/Sources/LanguageServerProtocol/Messages.swift index 6252992da..3440d67d3 100644 --- a/Sources/LanguageServerProtocol/Messages.swift +++ b/Sources/LanguageServerProtocol/Messages.swift @@ -58,7 +58,6 @@ public let builtinRequests: [_RequestType.Type] = [ InlineValueRequest.self, LinkedEditingRangeRequest.self, MonikersRequest.self, - OpenGeneratedInterfaceRequest.self, PollIndexRequest.self, PrepareRenameRequest.self, ReferencesRequest.self, diff --git a/Sources/LanguageServerProtocol/Requests/OpenInterfaceRequest.swift b/Sources/LanguageServerProtocol/Requests/OpenInterfaceRequest.swift deleted file mode 100644 index 2d353cb6b..000000000 --- a/Sources/LanguageServerProtocol/Requests/OpenInterfaceRequest.swift +++ /dev/null @@ -1,57 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2022 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 a generated interface of a module to display in the IDE. -/// **(LSP Extension)** -public struct OpenGeneratedInterfaceRequest: TextDocumentRequest, Hashable { - public static let method: String = "textDocument/openInterface" - public typealias Response = GeneratedInterfaceDetails? - - /// The document whose compiler arguments should be used to generate the interface. - public var textDocument: TextDocumentIdentifier - - /// The module to generate an index for. - public var moduleName: String - - /// The module group name. - public var groupName: String? - - /// The symbol USR to search for in the generated module interface. - public var symbolUSR: String? - - public init(textDocument: TextDocumentIdentifier, name: String, groupName: String?, symbolUSR: String?) { - self.textDocument = textDocument - self.symbolUSR = symbolUSR - self.moduleName = name - self.groupName = groupName - } - - /// Name of interface module name with group names appended - public var name: String { - if let groupName { - return "\(self.moduleName).\(groupName.replacing("/", with: "."))" - } - return self.moduleName - } -} - -/// The textual output of a module interface. -public struct GeneratedInterfaceDetails: ResponseType, Hashable { - - public var uri: DocumentURI - public var position: Position? - - public init(uri: DocumentURI, position: Position?) { - self.uri = uri - self.position = position - } -} diff --git a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift index 0fcd6b614..c4c17a153 100644 --- a/Sources/SourceKitLSP/Clang/ClangLanguageService.swift +++ b/Sources/SourceKitLSP/Clang/ClangLanguageService.swift @@ -623,7 +623,12 @@ extension ClangLanguageService { return try await forwardRequestToClangd(req) } - func openGeneratedInterface(_ request: OpenGeneratedInterfaceRequest) async throws -> GeneratedInterfaceDetails? { + func openGeneratedInterface( + document: DocumentURI, + moduleName: String, + groupName: String?, + symbolUSR symbol: String? + ) async throws -> GeneratedInterfaceDetails? { throw ResponseError.unknown("unsupported method") } diff --git a/Sources/SourceKitLSP/LanguageService.swift b/Sources/SourceKitLSP/LanguageService.swift index 1b2429e37..9580fac17 100644 --- a/Sources/SourceKitLSP/LanguageService.swift +++ b/Sources/SourceKitLSP/LanguageService.swift @@ -76,6 +76,17 @@ public struct RenameLocation: Sendable { let usage: Usage } +/// The textual output of a module interface. +public struct GeneratedInterfaceDetails: ResponseType, Hashable { + public var uri: DocumentURI + public var position: Position? + + public init(uri: DocumentURI, position: Position?) { + self.uri = uri + self.position = position + } +} + /// Provides language specific functionality to sourcekit-lsp from a specific toolchain. /// /// For example, we may have a language service that provides semantic functionality for c-family using a clangd server, @@ -147,7 +158,20 @@ public protocol LanguageService: AnyObject, Sendable { func completion(_ req: CompletionRequest) async throws -> CompletionList func hover(_ req: HoverRequest) async throws -> HoverResponse? func symbolInfo(_ request: SymbolInfoRequest) async throws -> [SymbolDetails] - func openGeneratedInterface(_ request: OpenGeneratedInterfaceRequest) async throws -> GeneratedInterfaceDetails? + + /// Request a generated interface of a module to display in the IDE. + /// + /// - Parameters: + /// - document: The document whose compiler arguments should be used to generate the interface. + /// - moduleName: The module to generate an index for. + /// - groupName: The module group name. + /// - symbol: The symbol USR to search for in the generated module interface. + func openGeneratedInterface( + document: DocumentURI, + moduleName: String, + groupName: String?, + symbolUSR symbol: String? + ) async throws -> GeneratedInterfaceDetails? /// - Note: Only called as a fallback if the definition could not be found in the index. func definition(_ request: DefinitionRequest) async throws -> LocationsOrLocationLinksResponse? diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 14d81646c..4bc2f78c7 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -743,8 +743,6 @@ extension SourceKitLSPServer: MessageHandler { initialized = true case let request as RequestAndReply: await self.handleRequest(for: request, requestHandler: self.inlayHint) - case let request as RequestAndReply: - await self.handleRequest(for: request, requestHandler: self.openGeneratedInterface) case let request as RequestAndReply: await request.reply { try await pollIndex(request.params) } case let request as RequestAndReply: @@ -1434,11 +1432,19 @@ extension SourceKitLSPServer { } func openGeneratedInterface( - _ req: OpenGeneratedInterfaceRequest, + document: DocumentURI, + moduleName: String, + groupName: String?, + symbolUSR symbol: String?, workspace: Workspace, languageService: LanguageService ) async throws -> GeneratedInterfaceDetails? { - return try await languageService.openGeneratedInterface(req) + return try await languageService.openGeneratedInterface( + document: document, + moduleName: moduleName, + groupName: groupName, + symbolUSR: symbol + ) } /// Find all symbols in the workspace that include a string in their name. @@ -1842,13 +1848,14 @@ extension SourceKitLSPServer { originatorUri: DocumentURI, languageService: LanguageService ) async throws -> Location { - let openInterface = OpenGeneratedInterfaceRequest( - textDocument: TextDocumentIdentifier(originatorUri), - name: moduleName, - groupName: groupName, - symbolUSR: symbolUSR - ) - guard let interfaceDetails = try await languageService.openGeneratedInterface(openInterface) else { + guard + let interfaceDetails = try await languageService.openGeneratedInterface( + document: originatorUri, + moduleName: moduleName, + groupName: groupName, + symbolUSR: symbolUSR + ) + else { throw ResponseError.unknown("Could not generate Swift Interface for \(moduleName)") } let position = interfaceDetails.position ?? Position(line: 0, utf16index: 0) diff --git a/Sources/SourceKitLSP/Swift/OpenInterface.swift b/Sources/SourceKitLSP/Swift/OpenInterface.swift index b81480d52..f3761eee3 100644 --- a/Sources/SourceKitLSP/Swift/OpenInterface.swift +++ b/Sources/SourceKitLSP/Swift/OpenInterface.swift @@ -22,22 +22,34 @@ struct GeneratedInterfaceInfo { extension SwiftLanguageService { public func openGeneratedInterface( - _ request: OpenGeneratedInterfaceRequest + document: DocumentURI, + moduleName: String, + groupName: String?, + symbolUSR symbol: String? ) async throws -> GeneratedInterfaceDetails? { - let name = request.name - let symbol = request.symbolUSR + // Name of interface module name with group names appended + let name = + if let groupName { + "\(moduleName).\(groupName.replacing("/", with: "."))" + } else { + moduleName + } let interfaceFilePath = self.generatedInterfacesPath.appendingPathComponent("\(name).swiftinterface") let interfaceDocURI = DocumentURI(interfaceFilePath) // has interface already been generated if let snapshot = try? self.documentManager.latestSnapshot(interfaceDocURI) { return await self.generatedInterfaceDetails( - request: request, uri: interfaceDocURI, snapshot: snapshot, symbol: symbol ) } else { - let interfaceInfo = try await self.generatedInterfaceInfo(request: request, interfaceURI: interfaceDocURI) + let interfaceInfo = try await self.generatedInterfaceInfo( + document: document, + moduleName: moduleName, + groupName: groupName, + interfaceURI: interfaceDocURI + ) try interfaceInfo.contents.write(to: interfaceFilePath, atomically: true, encoding: String.Encoding.utf8) let snapshot = DocumentSnapshot( uri: interfaceDocURI, @@ -46,7 +58,6 @@ extension SwiftLanguageService { lineTable: LineTable(interfaceInfo.contents) ) let result = await self.generatedInterfaceDetails( - request: request, uri: interfaceDocURI, snapshot: snapshot, symbol: symbol @@ -61,23 +72,27 @@ extension SwiftLanguageService { /// Open the Swift interface for a module. /// /// - Parameters: - /// - request: The OpenGeneratedInterfaceRequest. + /// - document: The document whose compiler arguments should be used to generate the interface. + /// - moduleName: The module to generate an index for. + /// - groupName: The module group name. /// - interfaceURI: The file where the generated interface should be written. /// /// - Important: This opens a document with name `interfaceURI.pseudoPath` in sourcekitd. The caller is responsible /// for ensuring that the document will eventually get closed in sourcekitd again. private func generatedInterfaceInfo( - request: OpenGeneratedInterfaceRequest, + document: DocumentURI, + moduleName: String, + groupName: String?, interfaceURI: DocumentURI ) async throws -> GeneratedInterfaceInfo { let keys = self.keys let skreq = sourcekitd.dictionary([ keys.request: requests.editorOpenInterface, - keys.moduleName: request.moduleName, - keys.groupName: request.groupName, + keys.moduleName: moduleName, + keys.groupName: groupName, keys.name: interfaceURI.pseudoPath, keys.synthesizedExtension: 1, - keys.compilerArgs: await self.buildSettings(for: request.textDocument.uri)?.compilerArgs as [SKDRequestValue]?, + keys.compilerArgs: await self.buildSettings(for: document)?.compilerArgs as [SKDRequestValue]?, ]) let dict = try await self.sourcekitd.send(skreq, fileContents: nil) @@ -85,7 +100,6 @@ extension SwiftLanguageService { } private func generatedInterfaceDetails( - request: OpenGeneratedInterfaceRequest, uri: DocumentURI, snapshot: DocumentSnapshot, symbol: String? diff --git a/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift b/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift index 5168574e3..d6582a018 100644 --- a/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift +++ b/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift @@ -49,55 +49,6 @@ final class SwiftInterfaceTests: XCTestCase { ) } - func testOpenInterface() async throws { - let project = try await SwiftPMTestProject( - files: [ - "MyLibrary/MyLibrary.swift": """ - public struct Lib { - public func foo() {} - public init() {} - } - """, - "Exec/main.swift": "import MyLibrary", - ], - manifest: """ - let package = Package( - name: "MyLibrary", - targets: [ - .target(name: "MyLibrary"), - .executableTarget(name: "Exec", dependencies: ["MyLibrary"]) - ] - ) - """, - enableBackgroundIndexing: true - ) - - let (mainUri, _) = try project.openDocument("main.swift") - let openInterface = OpenGeneratedInterfaceRequest( - textDocument: TextDocumentIdentifier(mainUri), - name: "MyLibrary", - groupName: nil, - symbolUSR: nil - ) - let interfaceDetails = try unwrap(await project.testClient.send(openInterface)) - XCTAssert(interfaceDetails.uri.pseudoPath.hasSuffix("/MyLibrary.swiftinterface")) - let fileContents = try XCTUnwrap( - interfaceDetails.uri.fileURL.flatMap({ try String(contentsOf: $0, encoding: .utf8) }) - ) - XCTAssertTrue( - fileContents.contains( - """ - public struct Lib { - - public func foo() - - public init() - } - """ - ) - ) - } - func testDefinitionInSystemModuleInterface() async throws { let project = try await IndexedSingleSwiftFileTestProject( """