Skip to content

A couple of improvements for generated interfaces #1460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/LanguageServerProtocol/Messages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public let builtinRequests: [_RequestType.Type] = [
InlineValueRequest.self,
LinkedEditingRangeRequest.self,
MonikersRequest.self,
OpenInterfaceRequest.self,
OpenGeneratedInterfaceRequest.self,
PollIndexRequest.self,
PrepareRenameRequest.self,
ReferencesRequest.self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
//
//===----------------------------------------------------------------------===//

/// Request a textual interface of a module to display in the IDE.
/// Request a generated interface of a module to display in the IDE.
/// **(LSP Extension)**
public struct OpenInterfaceRequest: TextDocumentRequest, Hashable {
public struct OpenGeneratedInterfaceRequest: TextDocumentRequest, Hashable {
public static let method: String = "textDocument/openInterface"
public typealias Response = InterfaceDetails?
public typealias Response = GeneratedInterfaceDetails?

/// The document whose compiler arguments should be used to generate the interface.
public var textDocument: TextDocumentIdentifier
Expand Down Expand Up @@ -45,7 +45,7 @@ public struct OpenInterfaceRequest: TextDocumentRequest, Hashable {
}

/// The textual output of a module interface.
public struct InterfaceDetails: ResponseType, Hashable {
public struct GeneratedInterfaceDetails: ResponseType, Hashable {

public var uri: DocumentURI
public var position: Position?
Expand Down
2 changes: 1 addition & 1 deletion Sources/SourceKitLSP/Clang/ClangLanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ extension ClangLanguageService {
return try await forwardRequestToClangd(req)
}

func openInterface(_ request: OpenInterfaceRequest) async throws -> InterfaceDetails? {
func openGeneratedInterface(_ request: OpenGeneratedInterfaceRequest) async throws -> GeneratedInterfaceDetails? {
throw ResponseError.unknown("unsupported method")
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/SourceKitLSP/LanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ 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 openInterface(_ request: OpenInterfaceRequest) async throws -> InterfaceDetails?
func openGeneratedInterface(_ request: OpenGeneratedInterfaceRequest) 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?
Expand Down
16 changes: 8 additions & 8 deletions Sources/SourceKitLSP/SourceKitLSPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -711,8 +711,8 @@ extension SourceKitLSPServer: MessageHandler {
await self.handleRequest(for: request, requestHandler: self.completion)
case let request as RequestAndReply<HoverRequest>:
await self.handleRequest(for: request, requestHandler: self.hover)
case let request as RequestAndReply<OpenInterfaceRequest>:
await self.handleRequest(for: request, requestHandler: self.openInterface)
case let request as RequestAndReply<OpenGeneratedInterfaceRequest>:
await self.handleRequest(for: request, requestHandler: self.openGeneratedInterface)
case let request as RequestAndReply<DeclarationRequest>:
await self.handleRequest(for: request, requestHandler: self.declaration)
case let request as RequestAndReply<DefinitionRequest>:
Expand Down Expand Up @@ -1466,12 +1466,12 @@ extension SourceKitLSPServer {
return try await languageService.hover(req)
}

func openInterface(
_ req: OpenInterfaceRequest,
func openGeneratedInterface(
_ req: OpenGeneratedInterfaceRequest,
workspace: Workspace,
languageService: LanguageService
) async throws -> InterfaceDetails? {
return try await languageService.openInterface(req)
) async throws -> GeneratedInterfaceDetails? {
return try await languageService.openGeneratedInterface(req)
}

/// Find all symbols in the workspace that include a string in their name.
Expand Down Expand Up @@ -1808,13 +1808,13 @@ extension SourceKitLSPServer {
originatorUri: DocumentURI,
languageService: LanguageService
) async throws -> Location {
let openInterface = OpenInterfaceRequest(
let openInterface = OpenGeneratedInterfaceRequest(
textDocument: TextDocumentIdentifier(originatorUri),
name: moduleName,
groupName: groupName,
symbolUSR: symbolUSR
)
guard let interfaceDetails = try await languageService.openInterface(openInterface) else {
guard let interfaceDetails = try await languageService.openGeneratedInterface(openInterface) else {
throw ResponseError.unknown("Could not generate Swift Interface for \(moduleName)")
}
let position = interfaceDetails.position ?? Position(line: 0, utf16index: 0)
Expand Down
83 changes: 42 additions & 41 deletions Sources/SourceKitLSP/Swift/OpenInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,82 +16,83 @@ import LanguageServerProtocol
import SKSupport
import SourceKitD

struct InterfaceInfo {
struct GeneratedInterfaceInfo {
var contents: String
}

extension SwiftLanguageService {
public func openInterface(_ request: OpenInterfaceRequest) async throws -> InterfaceDetails? {
let uri = request.textDocument.uri
let moduleName = request.moduleName
public func openGeneratedInterface(
_ request: OpenGeneratedInterfaceRequest
) async throws -> GeneratedInterfaceDetails? {
let name = request.name
let symbol = request.symbolUSR
let interfaceFilePath = self.generatedInterfacesPath.appendingPathComponent("\(name).swiftinterface")
let interfaceDocURI = DocumentURI(interfaceFilePath)
// has interface already been generated
if let snapshot = try? self.documentManager.latestSnapshot(interfaceDocURI) {
Comment on lines 31 to 32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure I understand, once the client has opened the interface, it gets populated in the documentManager?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the client should send a textDocument/didOpen notification to SourceKit-LSP, which will then add it to the document manager like any other source file.

return await self.interfaceDetails(request: request, uri: interfaceDocURI, snapshot: snapshot, symbol: symbol)
return await self.generatedInterfaceDetails(
request: request,
uri: interfaceDocURI,
snapshot: snapshot,
symbol: symbol
)
} else {
// generate interface
let interfaceInfo = try await self.openInterface(
let interfaceInfo = try await self.generatedInterfaceInfo(request: request, interfaceURI: interfaceDocURI)
try interfaceInfo.contents.write(to: interfaceFilePath, atomically: true, encoding: String.Encoding.utf8)
let snapshot = DocumentSnapshot(
uri: interfaceDocURI,
language: .swift,
version: 0,
lineTable: LineTable(interfaceInfo.contents)
)
let result = await self.generatedInterfaceDetails(
request: request,
uri: uri,
name: moduleName,
interfaceURI: interfaceDocURI
uri: interfaceDocURI,
snapshot: snapshot,
symbol: symbol
)
do {
// write to file
try interfaceInfo.contents.write(to: interfaceFilePath, atomically: true, encoding: String.Encoding.utf8)
// store snapshot
let snapshot = try self.documentManager.open(
interfaceDocURI,
language: .swift,
version: 0,
text: interfaceInfo.contents
)
return await self.interfaceDetails(request: request, uri: interfaceDocURI, snapshot: snapshot, symbol: symbol)
} catch {
throw ResponseError.unknown(error.localizedDescription)
_ = await orLog("Closing generated interface") {
try await self.sourcekitd.send(closeDocumentSourcekitdRequest(uri: interfaceDocURI), fileContents: nil)
}
return result
}
}

/// Open the Swift interface for a module.
///
/// - Parameters:
/// - request: The OpenInterfaceRequest.
/// - uri: The document whose compiler arguments should be used to generate the interface.
/// - name: The name of the module whose interface should be generated.
/// - request: The OpenGeneratedInterfaceRequest.
/// - interfaceURI: The file where the generated interface should be written.
private func openInterface(
request: OpenInterfaceRequest,
uri: DocumentURI,
name: String,
///
/// - 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,
interfaceURI: DocumentURI
) async throws -> InterfaceInfo {
) async throws -> GeneratedInterfaceInfo {
let keys = self.keys
let skreq = sourcekitd.dictionary([
keys.request: requests.editorOpenInterface,
keys.moduleName: name,
keys.moduleName: request.moduleName,
keys.groupName: request.groupName,
keys.name: interfaceURI.pseudoPath,
keys.synthesizedExtension: 1,
keys.compilerArgs: await self.buildSettings(for: uri)?.compilerArgs as [SKDRequestValue]?,
keys.compilerArgs: await self.buildSettings(for: request.textDocument.uri)?.compilerArgs as [SKDRequestValue]?,
])

let dict = try await self.sourcekitd.send(skreq, fileContents: nil)
return InterfaceInfo(contents: dict[keys.sourceText] ?? "")
return GeneratedInterfaceInfo(contents: dict[keys.sourceText] ?? "")
}

private func interfaceDetails(
request: OpenInterfaceRequest,
private func generatedInterfaceDetails(
request: OpenGeneratedInterfaceRequest,
uri: DocumentURI,
snapshot: DocumentSnapshot,
symbol: String?
) async -> InterfaceDetails {
) async -> GeneratedInterfaceDetails {
do {
guard let symbol = symbol else {
return InterfaceDetails(uri: uri, position: nil)
return GeneratedInterfaceDetails(uri: uri, position: nil)
}
let keys = self.keys
let skreq = sourcekitd.dictionary([
Expand All @@ -102,12 +103,12 @@ extension SwiftLanguageService {

let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
if let offset: Int = dict[keys.offset] {
return InterfaceDetails(uri: uri, position: snapshot.positionOf(utf8Offset: offset))
return GeneratedInterfaceDetails(uri: uri, position: snapshot.positionOf(utf8Offset: offset))
} else {
return InterfaceDetails(uri: uri, position: nil)
return GeneratedInterfaceDetails(uri: uri, position: nil)
}
} catch {
return InterfaceDetails(uri: uri, position: nil)
return GeneratedInterfaceDetails(uri: uri, position: nil)
}
}
}
2 changes: 1 addition & 1 deletion Sources/SourceKitLSP/Swift/SwiftLanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ extension SwiftLanguageService {
])
}

private func closeDocumentSourcekitdRequest(uri: DocumentURI) -> SKDRequestDictionary {
func closeDocumentSourcekitdRequest(uri: DocumentURI) -> SKDRequestDictionary {
return sourcekitd.dictionary([
keys.request: requests.editorClose,
keys.name: uri.pseudoPath,
Expand Down
2 changes: 1 addition & 1 deletion Tests/SourceKitLSPTests/SwiftInterfaceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ final class SwiftInterfaceTests: XCTestCase {
)

let (mainUri, _) = try project.openDocument("main.swift")
let openInterface = OpenInterfaceRequest(
let openInterface = OpenGeneratedInterfaceRequest(
textDocument: TextDocumentIdentifier(mainUri),
name: "MyLibrary",
groupName: nil,
Expand Down