Skip to content

Commit 94e393f

Browse files
Merge pull request swiftlang#2198 from matthewbastien/docc-swift-symbols
Always try to render some documentation for Swift files
1 parent 0540e51 commit 94e393f

File tree

5 files changed

+188
-60
lines changed

5 files changed

+188
-60
lines changed

Sources/DocCDocumentation/DoccDocumentationError.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ import Foundation
1414
package import LanguageServerProtocol
1515

1616
package enum DocCDocumentationError: LocalizedError {
17-
case noDocumentation
17+
case unsupportedLanguage(Language)
18+
case noDocumentableSymbols
1819
case indexNotAvailable
1920
case symbolNotFound(String)
2021

2122
var errorDescription: String? {
2223
switch self {
23-
case .noDocumentation:
24-
return "No documentation could be rendered for the position in this document"
24+
case .unsupportedLanguage(let language):
25+
return "Documentation preview is not available for \(language.description) files"
26+
case .noDocumentableSymbols:
27+
return "No documentable symbols were found in this Swift file"
2528
case .indexNotAvailable:
2629
return "The index is not availble to complete the request"
2730
case .symbolNotFound(let symbolName):

Sources/SourceKitLSP/Clang/ClangLanguageService.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,12 @@ extension ClangLanguageService {
488488

489489
#if canImport(DocCDocumentation)
490490
func doccDocumentation(_ req: DoccDocumentationRequest) async throws -> DoccDocumentationResponse {
491-
throw ResponseError.requestFailed(doccDocumentationError: .noDocumentation)
491+
guard let sourceKitLSPServer else {
492+
throw ResponseError.unknown("Connection to the editor closed")
493+
}
494+
495+
let snapshot = try sourceKitLSPServer.documentManager.latestSnapshot(req.textDocument.uri)
496+
throw ResponseError.requestFailed(doccDocumentationError: .unsupportedLanguage(snapshot.language))
492497
}
493498
#endif
494499

Sources/SourceKitLSP/Documentation/DoccDocumentationHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ extension DocumentationLanguageService {
105105
catalogURL: catalogURL
106106
)
107107
default:
108-
throw ResponseError.requestFailed(doccDocumentationError: .noDocumentation)
108+
throw ResponseError.requestFailed(doccDocumentationError: .unsupportedLanguage(snapshot.language))
109109
}
110110
}
111111
}

Sources/SourceKitLSP/Swift/DoccDocumentation.swift

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ extension SwiftLanguageService {
4848
position: snapshot.absolutePosition(of: position)
4949
)
5050
else {
51-
throw ResponseError.requestFailed(doccDocumentationError: .noDocumentation)
51+
throw ResponseError.requestFailed(doccDocumentationError: .noDocumentableSymbols)
5252
}
5353
// Retrieve the symbol graph as well as information about the symbol
5454
let symbolPosition = await adjustPositionToStartOfIdentifier(
@@ -130,30 +130,67 @@ fileprivate struct DocumentableSymbol {
130130
}
131131
}
132132

133+
init?(node: any SyntaxProtocol) {
134+
if let namedDecl = node.asProtocol(NamedDeclSyntax.self) {
135+
self = DocumentableSymbol(node: namedDecl, position: namedDecl.name.positionAfterSkippingLeadingTrivia)
136+
} else if let initDecl = node.as(InitializerDeclSyntax.self) {
137+
self = DocumentableSymbol(node: initDecl, position: initDecl.initKeyword.positionAfterSkippingLeadingTrivia)
138+
} else if let deinitDecl = node.as(DeinitializerDeclSyntax.self) {
139+
self = DocumentableSymbol(node: deinitDecl, position: deinitDecl.deinitKeyword.positionAfterSkippingLeadingTrivia)
140+
} else if let functionDecl = node.as(FunctionDeclSyntax.self) {
141+
self = DocumentableSymbol(node: functionDecl, position: functionDecl.name.positionAfterSkippingLeadingTrivia)
142+
} else if let subscriptDecl = node.as(SubscriptDeclSyntax.self) {
143+
self = DocumentableSymbol(node: subscriptDecl, position: subscriptDecl.positionAfterSkippingLeadingTrivia)
144+
} else if let variableDecl = node.as(VariableDeclSyntax.self) {
145+
guard let identifier = variableDecl.bindings.only?.pattern.as(IdentifierPatternSyntax.self) else {
146+
return nil
147+
}
148+
self = DocumentableSymbol(node: variableDecl, position: identifier.positionAfterSkippingLeadingTrivia)
149+
} else if let enumCaseDecl = node.as(EnumCaseDeclSyntax.self) {
150+
guard let name = enumCaseDecl.elements.only?.name else {
151+
return nil
152+
}
153+
self = DocumentableSymbol(node: enumCaseDecl, position: name.positionAfterSkippingLeadingTrivia)
154+
} else {
155+
return nil
156+
}
157+
}
158+
133159
static func findNearestSymbol(syntaxTree: SourceFileSyntax, position: AbsolutePosition) -> DocumentableSymbol? {
134-
guard let token = syntaxTree.token(at: position) else {
160+
let token: TokenSyntax
161+
if let tokenAtPosition = syntaxTree.token(at: position) {
162+
token = tokenAtPosition
163+
} else if position >= syntaxTree.endPosition, let lastToken = syntaxTree.lastToken(viewMode: .sourceAccurate) {
164+
// token(at:) returns nil if position is at the end of the document.
165+
token = lastToken
166+
} else if position < syntaxTree.position, let firstToken = syntaxTree.firstToken(viewMode: .sourceAccurate) {
167+
// No case in practice where this happens but good to cover anyway
168+
token = firstToken
169+
} else {
135170
return nil
136171
}
137-
return token.ancestorOrSelf { node in
138-
if let namedDecl = node.asProtocol(NamedDeclSyntax.self) {
139-
return DocumentableSymbol(node: namedDecl, position: namedDecl.name.positionAfterSkippingLeadingTrivia)
140-
} else if let initDecl = node.as(InitializerDeclSyntax.self) {
141-
return DocumentableSymbol(node: initDecl, position: initDecl.initKeyword.positionAfterSkippingLeadingTrivia)
142-
} else if let functionDecl = node.as(FunctionDeclSyntax.self) {
143-
return DocumentableSymbol(node: functionDecl, position: functionDecl.name.positionAfterSkippingLeadingTrivia)
144-
} else if let variableDecl = node.as(VariableDeclSyntax.self) {
145-
guard let identifier = variableDecl.bindings.only?.pattern.as(IdentifierPatternSyntax.self) else {
146-
return nil
147-
}
148-
return DocumentableSymbol(node: variableDecl, position: identifier.positionAfterSkippingLeadingTrivia)
149-
} else if let enumCaseDecl = node.as(EnumCaseDeclSyntax.self) {
150-
guard let name = enumCaseDecl.elements.only?.name else {
151-
return nil
152-
}
153-
return DocumentableSymbol(node: enumCaseDecl, position: name.positionAfterSkippingLeadingTrivia)
172+
// Check if the current token is within a valid documentable symbol
173+
if let symbol = token.ancestorOrSelf(mapping: { DocumentableSymbol(node: $0) }) {
174+
return symbol
175+
}
176+
// Walk forward through the tokens until we find a documentable symbol
177+
var previousToken: TokenSyntax? = token
178+
while let nextToken = previousToken?.nextToken(viewMode: .sourceAccurate) {
179+
if let symbol = nextToken.ancestorOrSelf(mapping: { DocumentableSymbol(node: $0) }) {
180+
return symbol
154181
}
155-
return nil
182+
previousToken = nextToken
183+
}
184+
// Walk backwards through the tokens until we find a documentable symbol
185+
previousToken = token
186+
while let nextToken = previousToken?.previousToken(viewMode: .sourceAccurate) {
187+
if let symbol = nextToken.ancestorOrSelf(mapping: { DocumentableSymbol(node: $0) }) {
188+
return symbol
189+
}
190+
previousToken = nextToken
156191
}
192+
// We couldn't find anything
193+
return nil
157194
}
158195
}
159196
#endif

0 commit comments

Comments
 (0)