Skip to content

Commit 09b66ee

Browse files
committed
Apply stylistic suggestions regarding semantic tokens
- Rename SemanticToken to SyntaxHighlightingToken Since we use these tokens for more than just semantic highlighting and they are an internal structure, we rename them for clarity. - Move support methods into Array<SyntaxHighlightingToken> - Move syntax highlighting token decoder into SKTestSupport The decoder is only needed for testing. - Improve some doc comments in SyntaxHighlightingToken - Update SyntaxHighlightingToken.Kind to match the LSP spec - Update SyntaxHighlightingToken.Modifiers.lspName - Clean up LSP encoding of tokens - Rename DocumentTokens.sorted to .mergedAndSorted - Slightly improve naming in addLexicalTokens - Rename DocumenTokens.withEachKind - Add doc comment clarifying DocumentSnapshot.tokens - Pass snapshot to parse methods in token parser - Update function/method classifications for tokens - Log unknown token kinds - Require token updates to be on SwiftLanguageServer's queue - Move token refresh request into new method - Clarify doc comment in DocumentManager.addLexicalTokens
1 parent 4bddd07 commit 09b66ee

File tree

6 files changed

+271
-221
lines changed

6 files changed

+271
-221
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 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 SourceKitLSP
14+
import LanguageServerProtocol
15+
16+
extension Array where Element == SyntaxHighlightingToken {
17+
/// Decodes the LSP representation of syntax highlighting tokens
18+
public init(lspEncodedTokens rawTokens: [UInt32]) {
19+
self.init()
20+
assert(rawTokens.count.isMultiple(of: 5))
21+
reserveCapacity(rawTokens.count / 5)
22+
23+
var current = Position(line: 0, utf16index: 0)
24+
25+
for i in stride(from: 0, to: rawTokens.count, by: 5) {
26+
let lineDelta = Int(rawTokens[i])
27+
let charDelta = Int(rawTokens[i + 1])
28+
let length = Int(rawTokens[i + 2])
29+
let rawKind = rawTokens[i + 3]
30+
let rawModifiers = rawTokens[i + 4]
31+
32+
guard let kind = SyntaxHighlightingToken.Kind(rawValue: rawKind) else { continue }
33+
let modifiers = SyntaxHighlightingToken.Modifiers(rawValue: rawModifiers)
34+
35+
current.line += lineDelta
36+
37+
if lineDelta == 0 {
38+
current.utf16index += charDelta
39+
} else {
40+
current.utf16index = charDelta
41+
}
42+
43+
append(SyntaxHighlightingToken(
44+
start: current,
45+
length: length,
46+
kind: kind,
47+
modifiers: modifiers
48+
))
49+
}
50+
}
51+
}

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ target_sources(SourceKitLSP PRIVATE
2727
Swift/ExpressionTypeInfo.swift
2828
Swift/SemanticRefactorCommand.swift
2929
Swift/SemanticRefactoring.swift
30-
Swift/SemanticTokenParser.swift
3130
Swift/SourceKitD+ResponseError.swift
3231
Swift/SwiftCommand.swift
3332
Swift/SwiftLanguageServer.swift
33+
Swift/SyntaxHighlightingToken.swift
3434
Swift/VariableTypeInfo.swift)
3535
set_target_properties(SourceKitLSP PROPERTIES
3636
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})

Sources/SourceKitLSP/DocumentManager.swift

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,22 @@ import SKSupport
1717

1818
public struct DocumentTokens {
1919
/// Lexical tokens, e.g. keywords, raw identifiers, ...
20-
public var lexical: [SemanticToken] = []
20+
public var lexical: [SyntaxHighlightingToken] = []
2121
/// Syntactic tokens, e.g. declarations, etc.
22-
public var syntactic: [SemanticToken] = []
22+
public var syntactic: [SyntaxHighlightingToken] = []
2323
/// Semantic tokens, e.g. variable references, type references, ...
24-
public var semantic: [SemanticToken] = []
24+
public var semantic: [SyntaxHighlightingToken] = []
2525

26-
public var merged: [SemanticToken] {
27-
[lexical, syntactic, semantic].reduce([], mergeSemanticTokens)
26+
public var merged: [SyntaxHighlightingToken] {
27+
[lexical, syntactic, semantic].reduce([]) { $0.mergingTokens(with: $1) }
2828
}
29-
public var sorted: [SemanticToken] {
29+
public var mergedAndSorted: [SyntaxHighlightingToken] {
3030
merged.sorted { $0.start < $1.start }
3131
}
3232

33-
public mutating func withEachKind(_ action: (inout [SemanticToken]) -> Void) {
33+
/// Modifies the syntax highlighting tokens of each kind
34+
/// (lexical, syntactic, semantic) according to `action`.
35+
public mutating func withMutableTokensOfEachKind(_ action: (inout [SyntaxHighlightingToken]) -> Void) {
3436
action(&lexical)
3537
action(&syntactic)
3638
action(&semantic)
@@ -41,6 +43,9 @@ public struct DocumentSnapshot {
4143
public var document: Document
4244
public var version: Int
4345
public var lineTable: LineTable
46+
/// Syntax highlighting tokens for the document. Note that
47+
/// `uri` + `latestVersion` only uniquely identifies a snapshot's content,
48+
/// the tokens are updated independently and only used internally.
4449
public var tokens: DocumentTokens
4550

4651
public var text: String { lineTable.content }
@@ -173,7 +178,7 @@ public final class DocumentManager {
173178
character.isWhitespace || character.isPunctuation || character.isSymbol
174179
}
175180

176-
func update(tokens: inout [SemanticToken]) {
181+
func update(tokens: inout [SyntaxHighlightingToken]) {
177182
tokens = Array(tokens.lazy
178183
.filter {
179184
// Only keep tokens that don't overlap with or are directly
@@ -197,7 +202,7 @@ public final class DocumentManager {
197202
})
198203
}
199204

200-
document.latestTokens.withEachKind(update(tokens:))
205+
document.latestTokens.withMutableTokensOfEachKind(update(tokens:))
201206
} else {
202207
// Full text replacement.
203208
document.latestLineTable = LineTable(edit.text)
@@ -218,7 +223,7 @@ public final class DocumentManager {
218223
@discardableResult
219224
public func replaceSemanticTokens(
220225
_ uri: DocumentURI,
221-
tokens: [SemanticToken]
226+
tokens: [SyntaxHighlightingToken]
222227
) throws -> DocumentSnapshot {
223228
return try queue.sync {
224229
guard let document = documents[uri] else {
@@ -237,7 +242,7 @@ public final class DocumentManager {
237242
@discardableResult
238243
public func replaceSyntacticTokens(
239244
_ uri: DocumentURI,
240-
tokens: [SemanticToken]
245+
tokens: [SyntaxHighlightingToken]
241246
) throws -> DocumentSnapshot {
242247
return try queue.sync {
243248
guard let document = documents[uri] else {
@@ -256,24 +261,25 @@ public final class DocumentManager {
256261
@discardableResult
257262
public func addLexicalTokens(
258263
_ uri: DocumentURI,
259-
tokens: [SemanticToken]
264+
tokens newTokens: [SyntaxHighlightingToken]
260265
) throws -> DocumentSnapshot {
261266
return try queue.sync {
262267
guard let document = documents[uri] else {
263268
throw Error.missingDocument(uri)
264269
}
265270

266-
if !tokens.isEmpty {
267-
// Remove all tokens that overlap with previous tokens
271+
if !newTokens.isEmpty {
272+
// Remove all tokens from `documentTokens.latestTokens.lexical`
273+
// that overlap with a token in `tokens`.
268274

269-
func removeAllOverlapping(tokens existingTokens: inout [SemanticToken]) {
275+
func removeAllOverlapping(tokens existingTokens: inout [SyntaxHighlightingToken]) {
270276
existingTokens.removeAll { existing in
271-
tokens.contains { existing.range.overlaps($0.range) }
277+
newTokens.contains { existing.range.overlaps($0.range) }
272278
}
273279
}
274280

275-
document.latestTokens.withEachKind(removeAllOverlapping(tokens:))
276-
document.latestTokens.lexical += tokens
281+
document.latestTokens.withMutableTokensOfEachKind(removeAllOverlapping(tokens:))
282+
document.latestTokens.lexical += newTokens
277283
}
278284

279285
return document.latestSnapshot

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -162,18 +162,18 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
162162
}
163163

164164
/// Update lexical and syntactic tokens for the given `snapshot`.
165-
func updateLexicalAndSyntacticTokens(
165+
/// Should only be called on `queue`.
166+
private func updateLexicalAndSyntacticTokens(
166167
response: SKDResponseDictionary,
167168
for snapshot: DocumentSnapshot
168169
) {
170+
dispatchPrecondition(condition: .onQueue(queue))
171+
169172
let uri = snapshot.document.uri
170173

171174
if let syntaxMap: SKDResponseArray = response[keys.syntaxmap] {
172-
let tokenParser = SemanticTokenParser(
173-
sourcekitd: sourcekitd,
174-
snapshot: snapshot
175-
)
176-
let tokens = tokenParser.parseTokens(syntaxMap)
175+
let tokenParser = SyntaxHighlightingTokenParser(sourcekitd: sourcekitd)
176+
let tokens = tokenParser.parseTokens(syntaxMap, in: snapshot)
177177
do {
178178
try documentManager.addLexicalTokens(uri, tokens: tokens)
179179
} catch {
@@ -182,50 +182,51 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
182182
}
183183

184184
if let substructure: SKDResponseArray = response[keys.substructure] {
185-
let tokenParser = SemanticTokenParser(
186-
sourcekitd: sourcekitd,
187-
snapshot: snapshot,
188-
useName: true
189-
)
190-
let tokens = tokenParser.parseTokens(substructure)
185+
let tokenParser = SyntaxHighlightingTokenParser(sourcekitd: sourcekitd, useName: true)
186+
let tokens = tokenParser.parseTokens(substructure, in: snapshot)
191187
do {
192188
try documentManager.replaceSyntacticTokens(uri, tokens: tokens)
193189
} catch {
194-
log("updating lexical tokens for \(uri) failed: \(error)", level: .warning)
190+
log("updating syntactic tokens for \(uri) failed: \(error)", level: .warning)
195191
}
196192
}
197193
}
198194

199195
/// Update semantic tokens for the given `snapshot`.
200-
func updateSemanticTokens(
196+
/// Should only be called on `queue`.
197+
private func updateSemanticTokens(
201198
response: SKDResponseDictionary,
202199
for snapshot: DocumentSnapshot
203200
) {
201+
dispatchPrecondition(condition: .onQueue(queue))
202+
204203
let uri = snapshot.document.uri
205204
guard let skTokens: SKDResponseArray = response[keys.annotations] else {
206205
return
207206
}
208207

209-
let tokenParser = SemanticTokenParser(
210-
sourcekitd: sourcekitd,
211-
snapshot: snapshot
212-
)
213-
let tokens = tokenParser.parseTokens(skTokens)
208+
let tokenParser = SyntaxHighlightingTokenParser(sourcekitd: sourcekitd)
209+
let tokens = tokenParser.parseTokens(skTokens, in: snapshot)
214210

215211
do {
216212
try documentManager.replaceSemanticTokens(uri, tokens: tokens)
217-
if clientCapabilities.workspace?.semanticTokens?.refreshSupport ?? false {
218-
_ = client.send(WorkspaceSemanticTokensRefreshRequest(), queue: queue) { result in
219-
if let error = result.failure {
220-
log("refreshing semantic tokens for \(uri) failed: \(error)", level: .warning)
221-
}
222-
}
223-
}
213+
224214
} catch {
225215
log("updating semantic tokens for \(uri) failed: \(error)", level: .warning)
226216
}
227217
}
228218

219+
/// Inform the client about changes to the syntax highlighting tokens.
220+
private func requestTokensRefresh() {
221+
if clientCapabilities.workspace?.semanticTokens?.refreshSupport ?? false {
222+
_ = client.send(WorkspaceSemanticTokensRefreshRequest(), queue: queue) { result in
223+
if let error = result.failure {
224+
log("refreshing tokens failed: \(error)", level: .warning)
225+
}
226+
}
227+
}
228+
}
229+
229230
/// Publish diagnostics for the given `snapshot`. We withhold semantic diagnostics if we are using
230231
/// fallback arguments.
231232
///
@@ -288,6 +289,7 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
288289
if let dict = try? self.sourcekitd.sendSync(req) {
289290
publishDiagnostics(response: dict, for: snapshot, compileCommand: compileCommand)
290291
updateSemanticTokens(response: dict, for: snapshot)
292+
requestTokensRefresh()
291293
}
292294
}
293295
}
@@ -323,8 +325,8 @@ extension SwiftLanguageServer {
323325
commands: builtinSwiftCommands),
324326
semanticTokensProvider: SemanticTokensOptions(
325327
legend: SemanticTokensLegend(
326-
tokenTypes: SemanticToken.Kind.allCases.map(\.lspName),
327-
tokenModifiers: SemanticToken.Modifiers.allCases.compactMap(\.lspName)),
328+
tokenTypes: SyntaxHighlightingToken.Kind.allCases.map(\.lspName),
329+
tokenModifiers: SyntaxHighlightingToken.Modifiers.allCases.map { $0.lspName! }),
328330
range: .bool(true),
329331
full: .bool(true))
330332
))
@@ -788,8 +790,8 @@ extension SwiftLanguageServer {
788790
return
789791
}
790792

791-
let tokens = snapshot.tokens.sorted
792-
let encodedTokens = encodeToIntArray(semanticTokens: tokens)
793+
let tokens = snapshot.tokens.mergedAndSorted
794+
let encodedTokens = tokens.lspEncoded
793795

794796
req.reply(DocumentSemanticTokensResponse(data: encodedTokens))
795797
}
@@ -811,8 +813,8 @@ extension SwiftLanguageServer {
811813
return
812814
}
813815

814-
let tokens = snapshot.tokens.sorted.filter { $0.range.overlaps(range) }
815-
let encodedTokens = encodeToIntArray(semanticTokens: tokens)
816+
let tokens = snapshot.tokens.mergedAndSorted.filter { $0.range.overlaps(range) }
817+
let encodedTokens = tokens.lspEncoded
816818

817819
req.reply(DocumentSemanticTokensResponse(data: encodedTokens))
818820
}

0 commit comments

Comments
 (0)