Skip to content

Always try to render some documentation for Swift files #2198

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
9 changes: 6 additions & 3 deletions Sources/DocCDocumentation/DoccDocumentationError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ import Foundation
package import LanguageServerProtocol

package enum DocCDocumentationError: LocalizedError {
case noDocumentation
case unsupportedLanguage(Language)
case noDocumentableSymbols
case indexNotAvailable
case symbolNotFound(String)

var errorDescription: String? {
switch self {
case .noDocumentation:
return "No documentation could be rendered for the position in this document"
case .unsupportedLanguage(let language):
return "Documentation preview is not available for \(language.description) files"
case .noDocumentableSymbols:
return "No documentable symbols were found in this Swift file"
case .indexNotAvailable:
return "The index is not availble to complete the request"
case .symbolNotFound(let symbolName):
Expand Down
7 changes: 6 additions & 1 deletion Sources/SourceKitLSP/Clang/ClangLanguageService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,12 @@ extension ClangLanguageService {

#if canImport(DocCDocumentation)
func doccDocumentation(_ req: DoccDocumentationRequest) async throws -> DoccDocumentationResponse {
throw ResponseError.requestFailed(doccDocumentationError: .noDocumentation)
guard let sourceKitLSPServer else {
throw ResponseError.unknown("Connection to the editor closed")
}

let snapshot = try sourceKitLSPServer.documentManager.latestSnapshot(req.textDocument.uri)
throw ResponseError.requestFailed(doccDocumentationError: .unsupportedLanguage(snapshot.language))
}
#endif

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ extension DocumentationLanguageService {
catalogURL: catalogURL
)
default:
throw ResponseError.requestFailed(doccDocumentationError: .noDocumentation)
throw ResponseError.requestFailed(doccDocumentationError: .unsupportedLanguage(snapshot.language))
}
}
}
Expand Down
69 changes: 48 additions & 21 deletions Sources/SourceKitLSP/Swift/DoccDocumentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ extension SwiftLanguageService {
position: snapshot.absolutePosition(of: position)
)
else {
throw ResponseError.requestFailed(doccDocumentationError: .noDocumentation)
throw ResponseError.requestFailed(doccDocumentationError: .noDocumentableSymbols)
}
// Retrieve the symbol graph as well as information about the symbol
let symbolPosition = await adjustPositionToStartOfIdentifier(
Expand Down Expand Up @@ -130,30 +130,57 @@ fileprivate struct DocumentableSymbol {
}
}

static func findNearestSymbol(syntaxTree: SourceFileSyntax, position: AbsolutePosition) -> DocumentableSymbol? {
guard let token = syntaxTree.token(at: position) else {
init?(node: any SyntaxProtocol) {
if let namedDecl = node.asProtocol(NamedDeclSyntax.self) {
self = DocumentableSymbol(node: namedDecl, position: namedDecl.name.positionAfterSkippingLeadingTrivia)
} else if let initDecl = node.as(InitializerDeclSyntax.self) {
self = DocumentableSymbol(node: initDecl, position: initDecl.initKeyword.positionAfterSkippingLeadingTrivia)
} else if let deinitDecl = node.as(DeinitializerDeclSyntax.self) {
self = DocumentableSymbol(node: deinitDecl, position: deinitDecl.deinitKeyword.positionAfterSkippingLeadingTrivia)
} else if let functionDecl = node.as(FunctionDeclSyntax.self) {
self = DocumentableSymbol(node: functionDecl, position: functionDecl.name.positionAfterSkippingLeadingTrivia)
} else if let subscriptDecl = node.as(SubscriptDeclSyntax.self) {
self = DocumentableSymbol(node: subscriptDecl, position: subscriptDecl.positionAfterSkippingLeadingTrivia)
} else if let variableDecl = node.as(VariableDeclSyntax.self) {
guard let identifier = variableDecl.bindings.only?.pattern.as(IdentifierPatternSyntax.self) else {
return nil
}
self = DocumentableSymbol(node: variableDecl, position: identifier.positionAfterSkippingLeadingTrivia)
} else if let enumCaseDecl = node.as(EnumCaseDeclSyntax.self) {
guard let name = enumCaseDecl.elements.only?.name else {
return nil
}
self = DocumentableSymbol(node: enumCaseDecl, position: name.positionAfterSkippingLeadingTrivia)
} else {
return nil
}
return token.ancestorOrSelf { node in
if let namedDecl = node.asProtocol(NamedDeclSyntax.self) {
return DocumentableSymbol(node: namedDecl, position: namedDecl.name.positionAfterSkippingLeadingTrivia)
} else if let initDecl = node.as(InitializerDeclSyntax.self) {
return DocumentableSymbol(node: initDecl, position: initDecl.initKeyword.positionAfterSkippingLeadingTrivia)
} else if let functionDecl = node.as(FunctionDeclSyntax.self) {
return DocumentableSymbol(node: functionDecl, position: functionDecl.name.positionAfterSkippingLeadingTrivia)
} else if let variableDecl = node.as(VariableDeclSyntax.self) {
guard let identifier = variableDecl.bindings.only?.pattern.as(IdentifierPatternSyntax.self) else {
return nil
}
return DocumentableSymbol(node: variableDecl, position: identifier.positionAfterSkippingLeadingTrivia)
} else if let enumCaseDecl = node.as(EnumCaseDeclSyntax.self) {
guard let name = enumCaseDecl.elements.only?.name else {
return nil
}
return DocumentableSymbol(node: enumCaseDecl, position: name.positionAfterSkippingLeadingTrivia)
}

static func findNearestSymbol(syntaxTree: SourceFileSyntax, position: AbsolutePosition) -> DocumentableSymbol? {
// token(at:) can return nil if the position is at the end of the document. Fall back to using the last token in this case.
let token = syntaxTree.token(at: position) ?? syntaxTree.lastToken(viewMode: .all)
// Check if the current token is within a valid documentable symbol
if let token, let symbol = token.ancestorOrSelf(mapping: { DocumentableSymbol(node: $0) }) {
return symbol
}
// Walk forward through the tokens until we find a documentable symbol
var previousToken = token
while let nextToken = previousToken?.nextToken(viewMode: .all) {
if let symbol = nextToken.ancestorOrSelf(mapping: { DocumentableSymbol(node: $0) }) {
return symbol
}
return nil
previousToken = nextToken
}
// Walk backwards through the tokens until we find a documentable symbol
previousToken = token
while let nextToken = previousToken?.previousToken(viewMode: .all) {
if let symbol = nextToken.ancestorOrSelf(mapping: { DocumentableSymbol(node: $0) }) {
return symbol
}
previousToken = nextToken
}
// We couldn't find anything
return nil
}
}
#endif
Loading