Skip to content

Commit 080e664

Browse files
committed
Load semantic tokens from a document using a sourcekitd request instead of the 0,0 edit
This allows us to cancel the semantic tokens request. It is also the last step to allow us to open and edit documents in syntactic-only mode. It also means that we no longer need to send a `WorkspaceSemanticTokensRefreshRequest` to the client after sourcekitd has updated its semantic tokens since with the new design the `DocumentSemanticTokensRangeRequest` will simply return the results once it has the updated semantic token.
1 parent e141933 commit 080e664

File tree

8 files changed

+32
-286
lines changed

8 files changed

+32
-286
lines changed

Sources/SourceKitD/sourcekitd_uids.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ public struct sourcekitd_requests {
178178
public let codecomplete_close: sourcekitd_uid_t
179179
public let cursorinfo: sourcekitd_uid_t
180180
public let diagnostics: sourcekitd_uid_t
181+
public let semantic_tokens: sourcekitd_uid_t
181182
public let expression_type: sourcekitd_uid_t
182183
public let find_usr: sourcekitd_uid_t
183184
public let variable_type: sourcekitd_uid_t
@@ -196,6 +197,7 @@ public struct sourcekitd_requests {
196197
codecomplete_close = api.uid_get_from_cstr("source.request.codecomplete.close")!
197198
cursorinfo = api.uid_get_from_cstr("source.request.cursorinfo")!
198199
diagnostics = api.uid_get_from_cstr("source.request.diagnostics")!
200+
semantic_tokens = api.uid_get_from_cstr("source.request.semantic_tokens")!
199201
expression_type = api.uid_get_from_cstr("source.request.expression.type")!
200202
find_usr = api.uid_get_from_cstr("source.request.editor.find_usr")!
201203
variable_type = api.uid_get_from_cstr("source.request.variable.type")!

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ add_library(SourceKitLSP STATIC
33
CapabilityRegistry.swift
44
DocumentManager.swift
55
IndexStoreDB+MainFilesProvider.swift
6-
RangeAdjuster.swift
76
ResponseError+Init.swift
87
Sequence+AsyncMap.swift
98
SourceKitIndexDelegate.swift
@@ -25,7 +24,6 @@ target_sources(SourceKitLSP PRIVATE
2524
Swift/SemanticRefactorCommand.swift
2625
Swift/SemanticRefactoring.swift
2726
Swift/SemanticTokens.swift
28-
Swift/SemanticTokensManager.swift
2927
Swift/SourceKitD+ResponseError.swift
3028
Swift/SwiftCommand.swift
3129
Swift/SwiftLanguageServer.swift

Sources/SourceKitLSP/CapabilityRegistry.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,6 @@ public final class CapabilityRegistry {
7575
clientCapabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration == true
7676
}
7777

78-
public var clientHasSemanticTokenRefreshSupport: Bool {
79-
clientCapabilities.workspace?.semanticTokens?.refreshSupport == true
80-
}
81-
8278
public var clientHasDiagnosticsCodeDescriptionSupport: Bool {
8379
clientCapabilities.textDocument?.publishDiagnostics?.codeDescriptionSupport == true
8480
}

Sources/SourceKitLSP/RangeAdjuster.swift

Lines changed: 0 additions & 71 deletions
This file was deleted.

Sources/SourceKitLSP/Swift/SemanticTokens.swift

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,27 @@ import LanguageServerProtocol
1515
import SwiftIDEUtils
1616
import SwiftParser
1717
import SwiftSyntax
18+
import SourceKitD
1819

1920
extension SwiftLanguageServer {
21+
/// Requests the semantic highlighting tokens for the given snapshot from sourcekitd.
22+
private func semanticHighlightingTokens(for snapshot: DocumentSnapshot) async throws -> [SyntaxHighlightingToken]? {
23+
guard let buildSettings = await self.buildSettings(for: snapshot.uri), !buildSettings.isFallback else {
24+
return nil
25+
}
26+
27+
let skreq = SKDRequestDictionary(sourcekitd: self.sourcekitd)
28+
skreq[keys.request] = requests.semantic_tokens
29+
skreq[keys.sourcefile] = snapshot.uri.pseudoPath
30+
31+
// FIXME: SourceKit should probably cache this for us.
32+
skreq[keys.compilerargs] = buildSettings.compilerArgs
33+
34+
let dict = try await sourcekitd.send(skreq)
35+
print(dict.description)
36+
return semanticTokens(of: dict, for: snapshot)
37+
}
38+
2039
/// Computes an array of syntax highlighting tokens from the syntax tree that
2140
/// have been merged with any semantic tokens from SourceKit. If the provided
2241
/// range is non-empty, this function restricts its output to only those
@@ -29,13 +48,16 @@ extension SwiftLanguageServer {
2948
for snapshot: DocumentSnapshot,
3049
in range: Range<Position>? = nil
3150
) async -> [SyntaxHighlightingToken] {
32-
let tree = await syntaxTreeManager.syntaxTree(for: snapshot)
33-
let semanticTokens = await semanticTokensManager.semanticTokens(for: snapshot.id)
34-
let range =
35-
range.flatMap({ $0.byteSourceRange(in: snapshot) })
36-
?? ByteSourceRange(offset: 0, length: tree.totalLength.utf8Length)
51+
async let tree = syntaxTreeManager.syntaxTree(for: snapshot)
52+
async let semanticTokens = await orLog { try await semanticHighlightingTokens(for: snapshot) }
53+
54+
let range = if let range = range.flatMap({ $0.byteSourceRange(in: snapshot) }) {
55+
range
56+
} else {
57+
ByteSourceRange(offset: 0, length: await tree.totalLength.utf8Length)
58+
}
3759
return
38-
tree
60+
await tree
3961
.classifications(in: range)
4062
.flatMap({ $0.highlightingTokens(in: snapshot) })
4163
.mergingTokens(with: semanticTokens ?? [])

Sources/SourceKitLSP/Swift/SemanticTokensManager.swift

Lines changed: 0 additions & 90 deletions
This file was deleted.

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 1 addition & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ public actor SwiftLanguageServer: ToolchainLanguageServer {
116116
private var inFlightPublishDiagnosticsTasks: [DocumentURI: Task<Void, Never>] = [:]
117117

118118
let syntaxTreeManager = SyntaxTreeManager()
119-
let semanticTokensManager = SemanticTokensManager()
120119

121120
nonisolated var keys: sourcekitd_keys { return sourcekitd.keys }
122121
nonisolated var requests: sourcekitd_requests { return sourcekitd.requests }
@@ -195,7 +194,7 @@ public actor SwiftLanguageServer: ToolchainLanguageServer {
195194
}
196195

197196
/// Returns the semantic tokens in `response` for the given `snapshot`.
198-
private func semanticTokens(
197+
func semanticTokens(
199198
of response: SKDResponseDictionary,
200199
for snapshot: DocumentSnapshot
201200
) -> [SyntaxHighlightingToken]? {
@@ -208,48 +207,6 @@ public actor SwiftLanguageServer: ToolchainLanguageServer {
208207

209208
return tokens
210209
}
211-
212-
/// Inform the client about changes to the syntax highlighting tokens.
213-
private func requestTokensRefresh() async {
214-
guard let sourceKitServer else {
215-
logger.fault("Cannot request a token refresh because SourceKitServer has been destructed")
216-
return
217-
}
218-
if capabilityRegistry.clientHasSemanticTokenRefreshSupport {
219-
Task {
220-
do {
221-
_ = try await sourceKitServer.sendRequestToClient(WorkspaceSemanticTokensRefreshRequest())
222-
} catch {
223-
logger.error("refreshing tokens failed: \(error.forLogging)")
224-
}
225-
}
226-
}
227-
}
228-
229-
func handleDocumentUpdate(uri: DocumentURI) async {
230-
guard let snapshot = documentManager.latestSnapshot(uri) else {
231-
return
232-
}
233-
// Make the magic 0,0 replacetext request to update diagnostics and semantic tokens.
234-
235-
let req = SKDRequestDictionary(sourcekitd: sourcekitd)
236-
req[keys.request] = requests.editor_replacetext
237-
req[keys.name] = uri.pseudoPath
238-
req[keys.offset] = 0
239-
req[keys.length] = 0
240-
req[keys.sourcetext] = ""
241-
242-
if let dict = try? self.sourcekitd.sendSync(req) {
243-
let isSemaStage = dict[keys.diagnostic_stage] as sourcekitd_uid_t? == sourcekitd.values.diag_stage_sema
244-
if isSemaStage, let semanticTokens = semanticTokens(of: dict, for: snapshot) {
245-
// Only update semantic tokens if the 0,0 replacetext request returned semantic information.
246-
await semanticTokensManager.setSemanticTokens(for: snapshot.id, semanticTokens: semanticTokens)
247-
}
248-
if isSemaStage {
249-
await requestTokensRefresh()
250-
}
251-
}
252-
}
253210
}
254211

255212
extension SwiftLanguageServer {
@@ -412,8 +369,6 @@ extension SwiftLanguageServer {
412369
req[keys.name] = uri.pseudoPath
413370

414371
_ = try? self.sourcekitd.sendSync(req)
415-
416-
await semanticTokensManager.discardSemanticTokens(for: note.textDocument.uri)
417372
}
418373

419374
/// Cancels any in-flight tasks to send a `PublishedDiagnosticsNotification` after edits.
@@ -508,11 +463,6 @@ extension SwiftLanguageServer {
508463
postEditSnapshot: postEditSnapshot,
509464
edits: ConcurrentEdits(fromSequential: edits)
510465
)
511-
await semanticTokensManager.registerEdit(
512-
preEditSnapshot: preEditSnapshot.id,
513-
postEditSnapshot: postEditSnapshot.id,
514-
edits: note.contentChanges
515-
)
516466

517467
publishDiagnosticsIfNeeded(for: note.textDocument.uri)
518468
}
@@ -1343,54 +1293,12 @@ extension SwiftLanguageServer: SKDNotificationHandler {
13431293
self.documentManager = DocumentManager()
13441294
}
13451295

1346-
guard let dict = notification.value else {
1347-
logger.fault(
1348-
"""
1349-
Could not decode sourcekitd notification
1350-
\(notification.forLogging)
1351-
"""
1352-
)
1353-
return
1354-
}
1355-
13561296
logger.debug(
13571297
"""
13581298
Received notification from sourcekitd
13591299
\(notification.forLogging)
13601300
"""
13611301
)
1362-
1363-
if let kind: sourcekitd_uid_t = dict[self.keys.notification],
1364-
kind == self.values.notification_documentupdate,
1365-
let name: String = dict[self.keys.name]
1366-
{
1367-
1368-
let uri: DocumentURI
1369-
1370-
// Paths are expected to be absolute; on Windows, this means that the
1371-
// path is either drive letter prefixed (and thus `PathGetDriveNumberW`
1372-
// will provide the driver number OR it is a UNC path and `PathIsUNCW`
1373-
// will return `true`. On Unix platforms, the path will start with `/`
1374-
// which takes care of both a regular absolute path and a POSIX
1375-
// alternate root path.
1376-
1377-
// TODO: this is not completely portable, e.g. MacOS 9 HFS paths are
1378-
// unhandled.
1379-
#if os(Windows)
1380-
let isPath: Bool = name.withCString(encodedAs: UTF16.self) {
1381-
!PathIsURLW($0)
1382-
}
1383-
#else
1384-
let isPath: Bool = name.starts(with: "/")
1385-
#endif
1386-
if isPath {
1387-
// If sourcekitd returns us a path, translate it back into a URL
1388-
uri = DocumentURI(URL(fileURLWithPath: name))
1389-
} else {
1390-
uri = DocumentURI(string: name)
1391-
}
1392-
await self.handleDocumentUpdate(uri: uri)
1393-
}
13941302
}
13951303
}
13961304

0 commit comments

Comments
 (0)