Skip to content
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
11 changes: 9 additions & 2 deletions Sources/ClangLanguageService/ClangLanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -511,8 +511,15 @@ extension ClangLanguageService {
package func symbolGraph(
forOnDiskContentsOf symbolDocumentUri: DocumentURI,
at location: SymbolLocation
) async throws -> String? {
return nil
) async throws -> String {
throw ResponseError.internalError("Symbol graph is currently not supported for clang files")
}

package func symbolGraph(
for snapshot: SourceKitLSP.DocumentSnapshot,
at position: LanguageServerProtocol.Position
) async throws -> (symbolGraph: String, usr: String, overrideDocComments: [String]) {
throw ResponseError.internalError("Symbol graph is currently not supported for clang files")
}

package func documentSymbolHighlight(_ req: DocumentHighlightRequest) async throws -> [DocumentHighlight]? {
Expand Down
240 changes: 178 additions & 62 deletions Sources/DocumentationLanguageService/DoccDocumentationHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Markdown
import SKUtilities
import SourceKitLSP
import SemanticIndex
import SKLogging

extension DocumentationLanguageService {
package func doccDocumentation(_ req: DoccDocumentationRequest) async throws -> DoccDocumentationResponse {
Expand All @@ -29,7 +30,6 @@ extension DocumentationLanguageService {
guard let workspace = await sourceKitLSPServer.workspaceForDocument(uri: req.textDocument.uri) else {
throw ResponseError.workspaceNotOpen(req.textDocument.uri)
}
let documentationManager = workspace.doccDocumentationManager
let snapshot = try documentManager.latestSnapshot(req.textDocument.uri)
var moduleName: String? = nil
var catalogURL: URL? = nil
Expand All @@ -40,81 +40,197 @@ extension DocumentationLanguageService {

switch snapshot.language {
case .tutorial:
return try await documentationManager.renderDocCDocumentation(
tutorialFile: snapshot.text,
return try await tutorialDocumentation(
for: snapshot,
in: workspace,
moduleName: moduleName,
catalogURL: catalogURL
)
case .markdown:
guard case .symbol(let symbolName) = MarkdownTitleFinder.find(parsing: snapshot.text) else {
// This is an article that can be rendered on its own
return try await documentationManager.renderDocCDocumentation(
markupFile: snapshot.text,
moduleName: moduleName,
catalogURL: catalogURL
)
return try await markdownDocumentation(
for: snapshot,
in: workspace,
moduleName: moduleName,
catalogURL: catalogURL
)
case .swift:
guard let position = req.position else {
throw ResponseError.invalidParams("A position must be provided for Swift files")
}
guard let moduleName, symbolName == moduleName else {
// This is a symbol extension page. Find the symbol so that we can include it in the request.
guard let index = workspace.index(checkedFor: .deletedFiles) else {
throw ResponseError.requestFailed(doccDocumentationError: .indexNotAvailable)
}
guard let symbolLink = DocCSymbolLink(linkString: symbolName),
let symbolOccurrence = try await index.primaryDefinitionOrDeclarationOccurrence(
ofDocCSymbolLink: symbolLink,
fetchSymbolGraph: { location in
guard let symbolWorkspace = try await workspaceForDocument(uri: location.documentUri) else {
throw ResponseError.internalError("Unable to find language service for \(location.documentUri)")
}
let languageService = try await sourceKitLSPServer.primaryLanguageService(
for: location.documentUri,
.swift,
in: symbolWorkspace
)
return try await languageService.symbolGraph(forOnDiskContentsOf: location.documentUri, at: location)

return try await swiftDocumentation(
for: snapshot,
at: position,
in: workspace,
moduleName: moduleName,
catalogURL: catalogURL
)
default:
throw ResponseError.requestFailed(doccDocumentationError: .unsupportedLanguage(snapshot.language))
}
}

private func tutorialDocumentation(
for snapshot: DocumentSnapshot,
in workspace: Workspace,
moduleName: String?,
catalogURL: URL?
) async throws -> DoccDocumentationResponse {
return try await workspace.doccDocumentationManager.renderDocCDocumentation(
tutorialFile: snapshot.text,
moduleName: moduleName,
catalogURL: catalogURL
)
}

private func markdownDocumentation(
for snapshot: DocumentSnapshot,
in workspace: Workspace,
moduleName: String?,
catalogURL: URL?
) async throws -> DoccDocumentationResponse {
guard let sourceKitLSPServer else {
throw ResponseError.internalError("SourceKit-LSP is shutting down")
}
let documentationManager = workspace.doccDocumentationManager
guard case .symbol(let symbolName) = MarkdownTitleFinder.find(parsing: snapshot.text) else {
// This is an article that can be rendered on its own
return try await documentationManager.renderDocCDocumentation(
markupFile: snapshot.text,
moduleName: moduleName,
catalogURL: catalogURL
)
}
guard let moduleName, symbolName == moduleName else {
// This is a symbol extension page. Find the symbol so that we can include it in the request.
guard let index = workspace.index(checkedFor: .deletedFiles) else {
throw ResponseError.requestFailed(doccDocumentationError: .indexNotAvailable)
}
guard let symbolLink = DocCSymbolLink(linkString: symbolName),
let symbolOccurrence = try await index.primaryDefinitionOrDeclarationOccurrence(
ofDocCSymbolLink: symbolLink,
fetchSymbolGraph: { location in
guard let symbolWorkspace = try await workspaceForDocument(uri: location.documentUri) else {
throw ResponseError.internalError("Unable to find language service for \(location.documentUri)")
}
)
else {
throw ResponseError.requestFailed(doccDocumentationError: .symbolNotFound(symbolName))
}
let symbolDocumentUri = symbolOccurrence.location.documentUri
guard let symbolWorkspace = try await workspaceForDocument(uri: symbolDocumentUri) else {
throw ResponseError.internalError("Unable to find language service for \(symbolDocumentUri)")
}
let languageService = try await sourceKitLSPServer.primaryLanguageService(
for: symbolDocumentUri,
.swift,
in: symbolWorkspace
)
let symbolGraph = try await languageService.symbolGraph(
forOnDiskContentsOf: symbolDocumentUri,
at: symbolOccurrence.location
)
guard let symbolGraph else {
throw ResponseError.internalError("Unable to retrieve symbol graph for \(symbolOccurrence.symbol.name)")
}
return try await documentationManager.renderDocCDocumentation(
symbolUSR: symbolOccurrence.symbol.usr,
symbolGraph: symbolGraph,
markupFile: snapshot.text,
moduleName: moduleName,
catalogURL: catalogURL
let languageService = try await sourceKitLSPServer.primaryLanguageService(
for: location.documentUri,
.swift,
in: symbolWorkspace
)
return try await languageService.symbolGraph(
forOnDiskContentsOf: location.documentUri,
at: location
)
}
)
else {
throw ResponseError.requestFailed(doccDocumentationError: .symbolNotFound(symbolName))
}
// This is a page representing the module itself.
// Create a dummy symbol graph and tell SwiftDocC to convert the module name.
// The version information isn't really all that important since we're creating
// what is essentially an empty symbol graph.
let symbolDocumentUri = symbolOccurrence.location.documentUri
guard let symbolWorkspace = try await workspaceForDocument(uri: symbolDocumentUri) else {
throw ResponseError.internalError("Unable to find language service for \(symbolDocumentUri)")
}
let languageService = try await sourceKitLSPServer.primaryLanguageService(
for: symbolDocumentUri,
.swift,
in: symbolWorkspace
)
let symbolGraph = try await languageService.symbolGraph(
forOnDiskContentsOf: symbolDocumentUri,
at: symbolOccurrence.location
)
return try await documentationManager.renderDocCDocumentation(
symbolUSR: moduleName,
symbolGraph: emptySymbolGraph(forModule: moduleName),
symbolUSR: symbolOccurrence.symbol.usr,
symbolGraph: symbolGraph,
markupFile: snapshot.text,
moduleName: moduleName,
catalogURL: catalogURL
)
default:
throw ResponseError.requestFailed(doccDocumentationError: .unsupportedLanguage(snapshot.language))
}
// This is a page representing the module itself.
// Create a dummy symbol graph and tell SwiftDocC to convert the module name.
// The version information isn't really all that important since we're creating
// what is essentially an empty symbol graph.
return try await documentationManager.renderDocCDocumentation(
symbolUSR: moduleName,
symbolGraph: emptySymbolGraph(forModule: moduleName),
markupFile: snapshot.text,
moduleName: moduleName,
catalogURL: catalogURL
)
}

private func swiftDocumentation(
for snapshot: DocumentSnapshot,
at position: Position,
in workspace: Workspace,
moduleName: String?,
catalogURL: URL?
) async throws -> DoccDocumentationResponse {
guard let sourceKitLSPServer else {
throw ResponseError.internalError("SourceKit-LSP is shutting down")
}
let documentationManager = workspace.doccDocumentationManager
let (symbolGraph, symbolUSR, overrideDocComments) = try await sourceKitLSPServer.primaryLanguageService(
for: snapshot.uri,
snapshot.language,
in: workspace
).symbolGraph(for: snapshot, at: position)
// Locate the documentation extension and include it in the request if one exists
let markupExtensionFile = await orLog("Finding markup extension file for symbol \(symbolUSR)") {
try await findMarkupExtensionFile(
workspace: workspace,
documentationManager: documentationManager,
catalogURL: catalogURL,
for: symbolUSR,
fetchSymbolGraph: { location in
guard let symbolWorkspace = try await workspaceForDocument(uri: location.documentUri) else {
throw ResponseError.internalError("Unable to find language service for \(location.documentUri)")
}
let languageService = try await sourceKitLSPServer.primaryLanguageService(
for: location.documentUri,
.swift,
in: symbolWorkspace
)
return try await languageService.symbolGraph(forOnDiskContentsOf: location.documentUri, at: location)
}
)
}
return try await documentationManager.renderDocCDocumentation(
symbolUSR: symbolUSR,
symbolGraph: symbolGraph,
overrideDocComments: overrideDocComments,
markupFile: markupExtensionFile,
moduleName: moduleName,
catalogURL: catalogURL
)
}

private func findMarkupExtensionFile(
workspace: Workspace,
documentationManager: DocCDocumentationManager,
catalogURL: URL?,
for symbolUSR: String,
fetchSymbolGraph: @Sendable (SymbolLocation) async throws -> String?
) async throws -> String? {
guard let catalogURL else {
return nil
}
let catalogIndex = try await documentationManager.catalogIndex(for: catalogURL)
guard let index = workspace.index(checkedFor: .deletedFiles),
let symbolInformation = try await index.doccSymbolInformation(
ofUSR: symbolUSR,
fetchSymbolGraph: fetchSymbolGraph
),
let markupExtensionFileURL = catalogIndex.documentationExtension(for: symbolInformation)
else {
return nil
}
return try? documentManager.latestSnapshotOrDisk(
DocumentURI(markupExtensionFileURL),
language: .markdown
)?.text
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,15 @@ package actor DocumentationLanguageService: LanguageService, Sendable {
package func symbolGraph(
forOnDiskContentsOf symbolDocumentUri: DocumentURI,
at location: SymbolLocation
) async throws -> String? {
return nil
) async throws -> String {
throw ResponseError.internalError("Not applicable")
}

package func symbolGraph(
for snapshot: SourceKitLSP.DocumentSnapshot,
at position: LanguageServerProtocol.Position
) async throws -> (symbolGraph: String, usr: String, overrideDocComments: [String]) {
throw ResponseError.internalError("Not applicable")
}

package func openGeneratedInterface(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extension LanguageServiceRegistry {
registry.register(ClangLanguageService.self, for: [.c, .cpp, .objective_c, .objective_cpp])
registry.register(SwiftLanguageService.self, for: [.swift])
#if canImport(DocumentationLanguageService)
registry.register(DocumentationLanguageService.self, for: [.markdown, .tutorial])
registry.register(DocumentationLanguageService.self, for: [.markdown, .tutorial, .swift])
#endif
return registry
}()
Expand Down
9 changes: 8 additions & 1 deletion Sources/SourceKitLSP/LanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,19 @@ package protocol LanguageService: AnyObject, Sendable {
func doccDocumentation(_ req: DoccDocumentationRequest) async throws -> DoccDocumentationResponse
func symbolInfo(_ request: SymbolInfoRequest) async throws -> [SymbolDetails]

/// Retrieve the symbol graph for the given position in the given snapshot, including the USR of the symbol at the
/// given position and the doc comments of the symbol at that position.
func symbolGraph(
for snapshot: DocumentSnapshot,
at position: Position
) async throws -> (symbolGraph: String, usr: String, overrideDocComments: [String])

/// Return the symbol graph at the given location for the contents of the document as they are on-disk (opposed to the
/// in-memory modified version of the document).
func symbolGraph(
forOnDiskContentsOf symbolDocumentUri: DocumentURI,
at location: SymbolLocation
) async throws -> String?
) async throws -> String

/// Request a generated interface of a module to display in the IDE.
///
Expand Down
Loading