Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,46 @@ public struct OpenInterfaceRequest: TextDocumentRequest, Hashable {
public var textDocument: TextDocumentIdentifier

/// The module to generate an index for.
public var name: String
public var moduleName: String

public init(textDocument: TextDocumentIdentifier, name: String) {
/// The module group name.
public var groupNames: [String]

/// The symbol to search for in the generated module interface.
public var symbol: String?

public init(textDocument: TextDocumentIdentifier, name: String, symbol: String?) {
self.textDocument = textDocument
self.name = name
self.symbol = symbol
// Stdlib Swift modules are all in the "Swift" module, but their symbols return a module name `Swift.***`.
let splitName = name.split(separator: ".")
if splitName.count == 1 {
self.moduleName = name
self.groupNames = []
} else {
self.moduleName = String(splitName[0])
self.groupNames = [String.SubSequence](splitName.dropFirst()).map { String($0)}
}
}

/// Name of interface module name with group names appended
public var name: String {
if groupNames.count > 0 {
return "\(self.moduleName).\(self.groupNames.joined(separator: "."))"
} else {
return self.moduleName
}
}
}

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

public var uri: DocumentURI
public var position: Position?

public init(uri: DocumentURI) {
public init(uri: DocumentURI, position: Position?) {
self.uri = uri
self.position = position
}
}
4 changes: 4 additions & 0 deletions Sources/SourceKitD/sourcekitd_uids.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public struct sourcekitd_keys {
public let expression_type_list: sourcekitd_uid_t
public let filepath: sourcekitd_uid_t
public let fixits: sourcekitd_uid_t
public let groupname: sourcekitd_uid_t
public let id: sourcekitd_uid_t
public let is_system: sourcekitd_uid_t
public let kind: sourcekitd_uid_t
Expand Down Expand Up @@ -115,6 +116,7 @@ public struct sourcekitd_keys {
expression_type_list = api.uid_get_from_cstr("key.expression_type_list")!
filepath = api.uid_get_from_cstr("key.filepath")!
fixits = api.uid_get_from_cstr("key.fixits")!
groupname = api.uid_get_from_cstr("key.groupname")!
id = api.uid_get_from_cstr("key.id")!
is_system = api.uid_get_from_cstr("key.is_system")!
kind = api.uid_get_from_cstr("key.kind")!
Expand Down Expand Up @@ -176,6 +178,7 @@ public struct sourcekitd_requests {
public let codecomplete_close: sourcekitd_uid_t
public let cursorinfo: sourcekitd_uid_t
public let expression_type: sourcekitd_uid_t
public let find_usr: sourcekitd_uid_t
public let variable_type: sourcekitd_uid_t
public let relatedidents: sourcekitd_uid_t
public let semantic_refactoring: sourcekitd_uid_t
Expand All @@ -192,6 +195,7 @@ public struct sourcekitd_requests {
codecomplete_close = api.uid_get_from_cstr("source.request.codecomplete.close")!
cursorinfo = api.uid_get_from_cstr("source.request.cursorinfo")!
expression_type = api.uid_get_from_cstr("source.request.expression.type")!
find_usr = api.uid_get_from_cstr("source.request.editor.find_usr")!
variable_type = api.uid_get_from_cstr("source.request.variable.type")!
relatedidents = api.uid_get_from_cstr("source.request.relatedidents")!
semantic_refactoring = api.uid_get_from_cstr("source.request.semantic.refactoring")!
Expand Down
51 changes: 37 additions & 14 deletions Sources/SourceKitLSP/SourceKitServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1307,20 +1307,7 @@ extension SourceKitServer {

// If this symbol is a module then generate a textual interface
if case .success(let symbols) = result, let symbol = symbols.first, symbol.kind == .module, let name = symbol.name {
let openInterface = OpenInterfaceRequest(textDocument: req.params.textDocument, name: name)
let request = Request(openInterface, id: req.id, clientID: ObjectIdentifier(self),
cancellation: req.cancellationToken, reply: { (result: Result<OpenInterfaceRequest.Response, ResponseError>) in
switch result {
case .success(let interfaceDetails?):
let loc = Location(uri: interfaceDetails.uri, range: Range(Position(line: 0, utf16index: 0)))
req.reply(.locations([loc]))
case .success(nil):
req.reply(.failure(.unknown("Could not generate Swift Interface for \(name)")))
case .failure(let error):
req.reply(.failure(error))
}
})
languageService.openInterface(request)
self.respondWithInterface(req, moduleName: name, symbol: nil, languageService: languageService)
return
}

Expand All @@ -1335,6 +1322,19 @@ extension SourceKitServer {

switch extractedResult {
case .success(let resolved):
// if first resolved location is in `.swiftinterface` file. Use moduleName to return
// textual interface
if let firstResolved = resolved.first,
let moduleName = firstResolved.occurrence?.location.moduleName,
firstResolved.location.uri.fileURL?.pathExtension == "swiftinterface" {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is checking the extension of location uri enough here?

self.respondWithInterface(
req,
moduleName: moduleName,
symbol: firstResolved.occurrence?.symbol.usr,
languageService: languageService
)
return
}
let locs = resolved.map(\.location)
// If we're unable to handle the definition request using our index, see if the
// language service can handle it (e.g. clangd can provide AST based definitions).
Expand All @@ -1354,6 +1354,29 @@ extension SourceKitServer {
languageService.symbolInfo(request)
}

func respondWithInterface(
_ req: Request<DefinitionRequest>,
moduleName: String,
symbol: String?,
languageService: ToolchainLanguageServer
) {
let openInterface = OpenInterfaceRequest(textDocument: req.params.textDocument, name: moduleName, symbol: symbol)
let request = Request(openInterface, id: req.id, clientID: ObjectIdentifier(self),
cancellation: req.cancellationToken, reply: { (result: Result<OpenInterfaceRequest.Response, ResponseError>) in
switch result {
case .success(let interfaceDetails?):
let position = interfaceDetails.position ?? Position(line: 0, utf16index: 0)
let loc = Location(uri: interfaceDetails.uri, range: Range(position))
req.reply(.locations([loc]))
case .success(nil):
req.reply(.failure(.unknown("Could not generate Swift Interface for \(moduleName)")))
case .failure(let error):
req.reply(.failure(error))
}
})
languageService.openInterface(request)
}

func implementation(
_ req: Request<ImplementationRequest>,
workspace: Workspace,
Expand Down
99 changes: 86 additions & 13 deletions Sources/SourceKitLSP/Swift/OpenInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,46 @@ import Foundation
import SourceKitD
import LanguageServerProtocol
import LSPLogging
import SKSupport

struct InterfaceInfo {
var contents: String
}

struct FindUSRInfo {
let position: Position?
}

extension SwiftLanguageServer {
public func openInterface(_ request: LanguageServerProtocol.Request<LanguageServerProtocol.OpenInterfaceRequest>) {
let uri = request.params.textDocument.uri
let moduleName = request.params.name
let moduleName = request.params.moduleName
let name = request.params.name
let symbol = request.params.symbol
self.queue.async {
let interfaceFilePath = self.generatedInterfacesPath.appendingPathComponent("\(moduleName).swiftinterface")
let interfaceFilePath = self.generatedInterfacesPath.appendingPathComponent("\(name).swiftinterface")
let interfaceDocURI = DocumentURI(interfaceFilePath)
self._openInterface(request: request, uri: uri, name: moduleName, interfaceURI: interfaceDocURI) { result in
switch result {
case .success(let interfaceInfo):
do {
try interfaceInfo.contents.write(to: interfaceFilePath, atomically: true, encoding: String.Encoding.utf8)
request.reply(.success(InterfaceDetails(uri: interfaceDocURI)))
} catch {
request.reply(.failure(ResponseError.unknown(error.localizedDescription)))
// has interface already been generated
if let snapshot = self.documentManager.latestSnapshot(interfaceDocURI) {
self._findUSRAndRespond(request: request, uri: interfaceDocURI, snapshot: snapshot, symbol: symbol)
} else {
// generate interface
self._openInterface(request: request, uri: uri, name: moduleName, interfaceURI: interfaceDocURI) { result in
switch result {
case .success(let interfaceInfo):
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)
self._findUSRAndRespond(request: request, uri: interfaceDocURI, snapshot: snapshot, symbol: symbol)
} catch {
request.reply(.failure(ResponseError.unknown(error.localizedDescription)))
}
case .failure(let error):
log("open interface failed: \(error)", level: .warning)
request.reply(.failure(ResponseError(error)))
}
case .failure(let error):
log("open interface failed: \(error)", level: .warning)
request.reply(.failure(ResponseError(error)))
}
}
}
Expand All @@ -60,6 +76,9 @@ extension SwiftLanguageServer {
let skreq = SKDRequestDictionary(sourcekitd: sourcekitd)
skreq[keys.request] = requests.editor_open_interface
skreq[keys.modulename] = name
if request.params.groupNames.count > 0 {
skreq[keys.groupname] = request.params.groupNames
}
skreq[keys.name] = interfaceURI.pseudoPath
skreq[keys.synthesizedextensions] = 1
if let compileCommand = self.commandsByFile[uri] {
Expand All @@ -81,4 +100,58 @@ extension SwiftLanguageServer {
}
}
}

private func _findUSRAndRespond(
request: LanguageServerProtocol.Request<LanguageServerProtocol.OpenInterfaceRequest>,
uri: DocumentURI,
snapshot: DocumentSnapshot,
symbol: String?
) {
self._findUSR(request: request, uri: uri, snapshot: snapshot, symbol: symbol) { result in
switch result {
case .success(let info):
request.reply(.success(InterfaceDetails(uri: uri, position: info.position)))
case .failure:
request.reply(.success(InterfaceDetails(uri: uri, position: nil)))
}
}
}

private func _findUSR(
request: LanguageServerProtocol.Request<LanguageServerProtocol.OpenInterfaceRequest>,
uri: DocumentURI,
snapshot: DocumentSnapshot,
symbol: String?,
completion: @escaping (Swift.Result<FindUSRInfo, SKDError>) -> Void
) {
guard let symbol = symbol else {
return completion(.success(FindUSRInfo(position: nil)))
}
let keys = self.keys
let skreq = SKDRequestDictionary(sourcekitd: sourcekitd)

skreq[keys.request] = requests.find_usr
skreq[keys.sourcefile] = uri.pseudoPath
skreq[keys.usr] = symbol

let handle = self.sourcekitd.send(skreq, self.queue) { result in
switch result {
case .success(let dict):
if let offset: Int = dict[keys.offset],
let position = snapshot.positionOf(utf8Offset: offset) {
return completion(.success(FindUSRInfo(position: position)))
} else {
return completion(.success(FindUSRInfo(position: nil)))
}
case .failure(let error):
return completion(.failure(error))
}
}

if let handle = handle {
request.cancellationToken.addCancellationHandler { [weak self] in
self?.sourcekitd.cancel(handle)
}
}
}
}
2 changes: 1 addition & 1 deletion Tests/SourceKitLSPTests/SwiftInterfaceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ final class SwiftInterfaceTests: XCTestCase {
try ws.buildAndIndex()
let importedModule = ws.testLoc("lib:import")
try ws.openDocument(importedModule.url, language: .swift)
let openInterface = OpenInterfaceRequest(textDocument: importedModule.docIdentifier, name: "lib")
let openInterface = OpenInterfaceRequest(textDocument: importedModule.docIdentifier, name: "lib", symbol: nil)
let interfaceDetails = try XCTUnwrap(ws.sk.sendSync(openInterface))
XCTAssertTrue(interfaceDetails.uri.pseudoPath.hasSuffix("/lib.swiftinterface"))
let fileContents = try XCTUnwrap(interfaceDetails.uri.fileURL.flatMap({ try String(contentsOf: $0, encoding: .utf8) }))
Expand Down