Skip to content

Commit 9bbb8f3

Browse files
authored
Merge pull request #1854 from matthewbastien/documentation-language-service
Handle `*.md` and `*.tutorial` files from Swift DocC
2 parents 21dfaf0 + 9f3de1b commit 9bbb8f3

File tree

8 files changed

+318
-1
lines changed

8 files changed

+318
-1
lines changed

Contributor Documentation/LSP Extensions.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,3 +622,10 @@ export interface GetReferenceDocumentResult {
622622
content: string;
623623
}
624624
```
625+
626+
## Languages
627+
628+
Added a new language with the identifier `tutorial` to support the `*.tutorial` files that
629+
Swift DocC uses to define tutorials and tutorial overviews in its documentation catalogs.
630+
It is expected that editors send document events for `tutorial` and `markdown` files if
631+
they wish to request information about these files from SourceKit-LSP.

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
632632
}
633633

634634
switch language {
635-
case .swift:
635+
case .swift, .markdown, .tutorial:
636636
return await toolchainRegistry.preferredToolchain(containing: [\.sourcekitd, \.swift, \.swiftc])
637637
case .c, .cpp, .objective_c, .objective_cpp:
638638
return await toolchainRegistry.preferredToolchain(containing: [\.clang, \.clangd])

Sources/LanguageServerProtocol/SupportTypes/Language.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ extension Language: CustomStringConvertible, CustomDebugStringConvertible {
9292
case .shellScript: return "Shell Script (Bash)"
9393
case .sql: return "SQL"
9494
case .swift: return "Swift"
95+
case .tutorial: return "Tutorial"
9596
case .typeScript: return "TypeScript"
9697
case .typeScriptReact: return "TypeScript React"
9798
case .tex: return "TeX"
@@ -153,6 +154,7 @@ public extension Language {
153154
static let shellScript = Language(rawValue: "shellscript") // Shell Script (Bash)
154155
static let sql = Language(rawValue: "sql")
155156
static let swift = Language(rawValue: "swift")
157+
static let tutorial = Language(rawValue: "tutorial") // LSP Extension: Swift DocC Tutorial
156158
static let typeScript = Language(rawValue: "typescript")
157159
static let typeScriptReact = Language(rawValue: "typescriptreact") // TypeScript React
158160
static let tex = Language(rawValue: "tex")

Sources/SKTestSupport/Utils.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ extension Language {
2626
var fileExtension: String {
2727
switch self {
2828
case .objective_c: return "m"
29+
case .markdown: return "md"
30+
case .tutorial: return "tutorial"
2931
default: return self.rawValue
3032
}
3133
}
@@ -37,6 +39,8 @@ extension Language {
3739
case "m": self = .objective_c
3840
case "mm": self = .objective_cpp
3941
case "swift": self = .swift
42+
case "md": self = .markdown
43+
case "tutorial": self = .tutorial
4044
default: return nil
4145
}
4246
}

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ target_sources(SourceKitLSP PRIVATE
2424
Clang/ClangLanguageService.swift
2525
Clang/SemanticTokenTranslator.swift
2626
)
27+
target_sources(SourceKitLSP PRIVATE
28+
Documentation/DocumentationLanguageService.swift
29+
)
2730
target_sources(SourceKitLSP PRIVATE
2831
Swift/AdjustPositionToStartOfIdentifier.swift
2932
Swift/ClosureCompletionFormat.swift
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
#if compiler(>=6)
16+
package import LanguageServerProtocol
17+
package import SKOptions
18+
package import SwiftSyntax
19+
package import ToolchainRegistry
20+
#else
21+
import LanguageServerProtocol
22+
import SKOptions
23+
import SwiftSyntax
24+
import ToolchainRegistry
25+
#endif
26+
27+
package actor DocumentationLanguageService: LanguageService, Sendable {
28+
package init?(
29+
sourceKitLSPServer: SourceKitLSPServer,
30+
toolchain: Toolchain,
31+
options: SourceKitLSPOptions,
32+
testHooks: TestHooks,
33+
workspace: Workspace
34+
) async throws {}
35+
36+
package nonisolated func canHandle(workspace: Workspace) -> Bool {
37+
return true
38+
}
39+
40+
package func initialize(
41+
_ initialize: InitializeRequest
42+
) async throws -> InitializeResult {
43+
return InitializeResult(
44+
capabilities: ServerCapabilities()
45+
)
46+
}
47+
48+
package func clientInitialized(_ initialized: InitializedNotification) async {
49+
// Nothing to set up
50+
}
51+
52+
package func shutdown() async {
53+
// Nothing to tear down
54+
}
55+
56+
package func addStateChangeHandler(
57+
handler: @escaping @Sendable (LanguageServerState, LanguageServerState) -> Void
58+
) async {
59+
// There is no underlying language server with which to report state
60+
}
61+
62+
package func openDocument(
63+
_ notification: DidOpenTextDocumentNotification,
64+
snapshot: DocumentSnapshot
65+
) async {
66+
// The DocumentationLanguageService does not do anything with document events
67+
}
68+
69+
package func closeDocument(_ notification: DidCloseTextDocumentNotification) async {
70+
// The DocumentationLanguageService does not do anything with document events
71+
}
72+
73+
package func reopenDocument(_ notification: ReopenTextDocumentNotification) async {
74+
// The DocumentationLanguageService does not do anything with document events
75+
}
76+
77+
package func changeDocument(
78+
_ notification: DidChangeTextDocumentNotification,
79+
preEditSnapshot: DocumentSnapshot,
80+
postEditSnapshot: DocumentSnapshot,
81+
edits: [SwiftSyntax.SourceEdit]
82+
) async {
83+
// The DocumentationLanguageService does not do anything with document events
84+
}
85+
86+
package func willSaveDocument(_ notification: WillSaveTextDocumentNotification) async {
87+
// The DocumentationLanguageService does not do anything with document events
88+
}
89+
90+
package func didSaveDocument(_ notification: DidSaveTextDocumentNotification) async {
91+
// The DocumentationLanguageService does not do anything with document events
92+
}
93+
94+
package func documentUpdatedBuildSettings(_ uri: DocumentURI) async {
95+
// The DocumentationLanguageService does not do anything with document events
96+
}
97+
98+
package func documentDependenciesUpdated(_ uris: Set<DocumentURI>) async {
99+
// The DocumentationLanguageService does not do anything with document events
100+
}
101+
102+
package func completion(_ req: CompletionRequest) async throws -> CompletionList {
103+
CompletionList(isIncomplete: false, items: [])
104+
}
105+
106+
package func hover(_ req: HoverRequest) async throws -> HoverResponse? {
107+
nil
108+
}
109+
110+
package func symbolInfo(_ request: SymbolInfoRequest) async throws -> [SymbolDetails] {
111+
[]
112+
}
113+
114+
package func openGeneratedInterface(
115+
document: DocumentURI,
116+
moduleName: String,
117+
groupName: String?,
118+
symbolUSR symbol: String?
119+
) async throws -> GeneratedInterfaceDetails? {
120+
nil
121+
}
122+
123+
package func definition(_ request: DefinitionRequest) async throws -> LocationsOrLocationLinksResponse? {
124+
nil
125+
}
126+
127+
package func declaration(_ request: DeclarationRequest) async throws -> LocationsOrLocationLinksResponse? {
128+
nil
129+
}
130+
131+
package func documentSymbolHighlight(_ req: DocumentHighlightRequest) async throws -> [DocumentHighlight]? {
132+
nil
133+
}
134+
135+
package func foldingRange(_ req: FoldingRangeRequest) async throws -> [FoldingRange]? {
136+
nil
137+
}
138+
139+
package func documentSymbol(_ req: DocumentSymbolRequest) async throws -> DocumentSymbolResponse? {
140+
nil
141+
}
142+
143+
package func documentColor(_ req: DocumentColorRequest) async throws -> [ColorInformation] {
144+
[]
145+
}
146+
147+
package func documentSemanticTokens(
148+
_ req: DocumentSemanticTokensRequest
149+
) async throws -> DocumentSemanticTokensResponse? {
150+
nil
151+
}
152+
153+
package func documentSemanticTokensDelta(
154+
_ req: DocumentSemanticTokensDeltaRequest
155+
) async throws -> DocumentSemanticTokensDeltaResponse? {
156+
nil
157+
}
158+
159+
package func documentSemanticTokensRange(
160+
_ req: DocumentSemanticTokensRangeRequest
161+
) async throws -> DocumentSemanticTokensResponse? {
162+
nil
163+
}
164+
165+
package func colorPresentation(_ req: ColorPresentationRequest) async throws -> [ColorPresentation] {
166+
[]
167+
}
168+
169+
package func codeAction(_ req: CodeActionRequest) async throws -> CodeActionRequestResponse? {
170+
nil
171+
}
172+
173+
package func inlayHint(_ req: InlayHintRequest) async throws -> [InlayHint] {
174+
[]
175+
}
176+
177+
package func codeLens(_ req: CodeLensRequest) async throws -> [CodeLens] {
178+
[]
179+
}
180+
181+
package func documentDiagnostic(_ req: DocumentDiagnosticsRequest) async throws -> DocumentDiagnosticReport {
182+
.full(RelatedFullDocumentDiagnosticReport(items: []))
183+
}
184+
185+
package func documentFormatting(_ req: DocumentFormattingRequest) async throws -> [TextEdit]? {
186+
nil
187+
}
188+
189+
package func documentRangeFormatting(
190+
_ req: LanguageServerProtocol.DocumentRangeFormattingRequest
191+
) async throws -> [LanguageServerProtocol.TextEdit]? {
192+
return nil
193+
}
194+
195+
package func documentOnTypeFormatting(_ req: DocumentOnTypeFormattingRequest) async throws -> [TextEdit]? {
196+
return nil
197+
}
198+
199+
package func rename(_ request: RenameRequest) async throws -> (edits: WorkspaceEdit, usr: String?) {
200+
(edits: WorkspaceEdit(), usr: nil)
201+
}
202+
203+
package func editsToRename(
204+
locations renameLocations: [RenameLocation],
205+
in snapshot: DocumentSnapshot,
206+
oldName: CrossLanguageName,
207+
newName: CrossLanguageName
208+
) async throws -> [TextEdit] {
209+
[]
210+
}
211+
212+
package func prepareRename(
213+
_ request: PrepareRenameRequest
214+
) async throws -> (prepareRename: PrepareRenameResponse, usr: String?)? {
215+
nil
216+
}
217+
218+
package func indexedRename(_ request: IndexedRenameRequest) async throws -> WorkspaceEdit? {
219+
nil
220+
}
221+
222+
package func editsToRenameParametersInFunctionBody(
223+
snapshot: DocumentSnapshot,
224+
renameLocation: RenameLocation,
225+
newName: CrossLanguageName
226+
) async -> [TextEdit] {
227+
[]
228+
}
229+
230+
package func executeCommand(_ req: ExecuteCommandRequest) async throws -> LSPAny? {
231+
nil
232+
}
233+
234+
package func getReferenceDocument(_ req: GetReferenceDocumentRequest) async throws -> GetReferenceDocumentResponse {
235+
GetReferenceDocumentResponse(content: "")
236+
}
237+
238+
package func syntacticDocumentTests(
239+
for uri: DocumentURI,
240+
in workspace: Workspace
241+
) async throws -> [AnnotatedTestItem]? {
242+
nil
243+
}
244+
245+
package func canonicalDeclarationPosition(
246+
of position: Position,
247+
in uri: DocumentURI
248+
) async -> Position? {
249+
nil
250+
}
251+
252+
package func crash() async {
253+
// There's no way to crash the DocumentationLanguageService
254+
}
255+
}

Sources/SourceKitLSP/LanguageServerType.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@ import LanguageServerProtocol
1717
enum LanguageServerType: Hashable {
1818
case clangd
1919
case swift
20+
case documentation
2021

2122
init?(language: Language) {
2223
switch language {
2324
case .c, .cpp, .objective_c, .objective_cpp:
2425
self = .clangd
2526
case .swift:
2627
self = .swift
28+
case .markdown, .tutorial:
29+
self = .documentation
2730
default:
2831
return nil
2932
}
@@ -44,6 +47,8 @@ enum LanguageServerType: Hashable {
4447
return ClangLanguageService.self
4548
case .swift:
4649
return SwiftLanguageService.self
50+
case .documentation:
51+
return DocumentationLanguageService.self
4752
}
4853
}
4954
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import LanguageServerProtocol
14+
import SKTestSupport
15+
import SourceKitLSP
16+
import XCTest
17+
18+
final class DocumentationLanguageServiceTests: XCTestCase {
19+
func testHandlesMarkdownFiles() async throws {
20+
try await assertHandles(language: .markdown)
21+
}
22+
23+
func testHandlesTutorialFiles() async throws {
24+
try await assertHandles(language: .tutorial)
25+
}
26+
}
27+
28+
fileprivate func assertHandles(language: Language) async throws {
29+
let testClient = try await TestSourceKitLSPClient()
30+
let uri = DocumentURI(for: language)
31+
testClient.openDocument("", uri: uri)
32+
33+
// The DocumentationLanguageService doesn't do much right now except to enable handling `*.md`
34+
// and `*.tutorial` files for the purposes of fulfilling documentation requests. We'll just
35+
// issue a completion request here to make sure that an empty list is returned and that
36+
// SourceKit-LSP does not respond with an error on requests for Markdown and Tutorial files.
37+
let completions = try await testClient.send(
38+
CompletionRequest(textDocument: .init(uri), position: .init(line: 0, utf16index: 0))
39+
)
40+
XCTAssertEqual(completions, .init(isIncomplete: false, items: []))
41+
}

0 commit comments

Comments
 (0)