Skip to content

Commit 46cc2e6

Browse files
authored
Merge pull request #1510 from ahoppen/translate-clangd-semantic-token-map
Translate the semantic token legend used by clangd to the semantic token legend used by SourceKit-LSP
2 parents b7b0900 + 80a35aa commit 46cc2e6

File tree

11 files changed

+312
-36
lines changed

11 files changed

+312
-36
lines changed

Documentation/LSP Extensions.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,35 @@ Added field (this is an extension from clangd that SourceKit-LSP re-exposes):
3535
codeActions: CodeAction[]?
3636
```
3737
38+
## Semantic token modifiers
39+
40+
Added the following cases from clangd
41+
42+
```ts
43+
deduced = 'deduced'
44+
virtual = 'virtual'
45+
dependentName = 'dependentName'
46+
usedAsMutableReference = 'usedAsMutableReference'
47+
usedAsMutablePointer = 'usedAsMutablePointer'
48+
constructorOrDestructor = 'constructorOrDestructor'
49+
userDefined = 'userDefined'
50+
functionScope = 'functionScope'
51+
classScope = 'classScope'
52+
fileScope = 'fileScope'
53+
globalScope = 'globalScope'
54+
```
55+
3856
## Semantic token types
3957
58+
Added the following cases from clangd
59+
60+
```ts
61+
bracket = 'bracket'
62+
label = 'label'
63+
concept = 'concept'
64+
unknown = 'unknown'
65+
```
66+
4067
Added case
4168
4269
```ts

Sources/LanguageServerProtocol/SupportTypes/SemanticTokenModifiers.swift

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ public struct SemanticTokenModifiers: OptionSet, Hashable, Sendable {
3434
public static let documentation = Self(rawValue: 1 << 8)
3535
public static let defaultLibrary = Self(rawValue: 1 << 9)
3636

37+
// The following are LSP extensions from clangd
38+
public static let deduced = Self(rawValue: 1 << 10)
39+
public static let virtual = Self(rawValue: 1 << 11)
40+
public static let dependentName = Self(rawValue: 1 << 12)
41+
public static let usedAsMutableReference = Self(rawValue: 1 << 13)
42+
public static let usedAsMutablePointer = Self(rawValue: 1 << 14)
43+
public static let constructorOrDestructor = Self(rawValue: 1 << 15)
44+
public static let userDefined = Self(rawValue: 1 << 16)
45+
public static let functionScope = Self(rawValue: 1 << 17)
46+
public static let classScope = Self(rawValue: 1 << 18)
47+
public static let fileScope = Self(rawValue: 1 << 19)
48+
public static let globalScope = Self(rawValue: 1 << 20)
49+
3750
public var name: String? {
3851
switch self {
3952
case .declaration: return "declaration"
@@ -46,13 +59,24 @@ public struct SemanticTokenModifiers: OptionSet, Hashable, Sendable {
4659
case .modification: return "modification"
4760
case .documentation: return "documentation"
4861
case .defaultLibrary: return "defaultLibrary"
62+
case .deduced: return "deduced"
63+
case .virtual: return "virtual"
64+
case .dependentName: return "dependentName"
65+
case .usedAsMutableReference: return "usedAsMutableReference"
66+
case .usedAsMutablePointer: return "usedAsMutablePointer"
67+
case .constructorOrDestructor: return "constructorOrDestructor"
68+
case .userDefined: return "userDefined"
69+
case .functionScope: return "functionScope"
70+
case .classScope: return "classScope"
71+
case .fileScope: return "fileScope"
72+
case .globalScope: return "globalScope"
4973
default: return nil
5074
}
5175
}
5276

5377
/// All available modifiers, in ascending order of the bit index
5478
/// they are represented with (starting at the rightmost bit).
55-
public static let predefined: [Self] = [
79+
public static let all: [Self] = [
5680
.declaration,
5781
.definition,
5882
.readonly,
@@ -63,5 +87,16 @@ public struct SemanticTokenModifiers: OptionSet, Hashable, Sendable {
6387
.modification,
6488
.documentation,
6589
.defaultLibrary,
90+
.deduced,
91+
.virtual,
92+
.dependentName,
93+
.usedAsMutableReference,
94+
.usedAsMutablePointer,
95+
.constructorOrDestructor,
96+
.userDefined,
97+
.functionScope,
98+
.classScope,
99+
.fileScope,
100+
.globalScope,
66101
]
67102
}

Sources/LanguageServerProtocol/SupportTypes/SemanticTokenTypes.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,18 @@ public struct SemanticTokenTypes: Hashable, Sendable {
5050
/// since 3.17.0
5151
public static let decorator = Self("decorator")
5252

53-
public static let predefined: [Self] = [
53+
// The following are LSP extensions from clangd
54+
public static let bracket = Self("bracket")
55+
public static let label = Self("label")
56+
public static let concept = Self("concept")
57+
public static let unknown = Self("unknown")
58+
59+
/// An identifier that hasn't been further classified
60+
///
61+
/// **(LSP Extension)**
62+
public static let identifier = Self("identifier")
63+
64+
public static let all: [Self] = [
5465
.namespace,
5566
.type,
5667
.class,
@@ -73,5 +84,11 @@ public struct SemanticTokenTypes: Hashable, Sendable {
7384
.number,
7485
.regexp,
7586
.operator,
87+
.decorator,
88+
.bracket,
89+
.label,
90+
.concept,
91+
.unknown,
92+
.identifier,
7693
]
7794
}

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_library(SourceKitLSP STATIC
1111
MessageHandlingDependencyTracker.swift
1212
Rename.swift
1313
ResponseError+Init.swift
14+
SemanticTokensLegend+SourceKitLSPLegend.swift
1415
SourceKitIndexDelegate.swift
1516
SourceKitLSPCommandMetadata.swift
1617
SourceKitLSPServer.swift
@@ -22,7 +23,9 @@ add_library(SourceKitLSP STATIC
2223
Workspace.swift
2324
)
2425
target_sources(SourceKitLSP PRIVATE
25-
Clang/ClangLanguageService.swift)
26+
Clang/ClangLanguageService.swift
27+
Clang/SemanticTokenTranslator.swift
28+
)
2629
target_sources(SourceKitLSP PRIVATE
2730
Swift/AdjustPositionToStartOfIdentifier.swift
2831
Swift/CodeActions/AddDocumentation.swift

Sources/SourceKitLSP/Clang/ClangLanguageService.swift

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ actor ClangLanguageService: LanguageService, MessageHandler {
9595
/// opened with.
9696
private var openDocuments: [DocumentURI: Language] = [:]
9797

98+
/// Type to map `clangd`'s semantic token legend to SourceKit-LSP's.
99+
private var semanticTokensTranslator: SemanticTokensLegendTranslator? = nil
100+
98101
/// While `clangd` is running, its PID.
99102
#if os(Windows)
100103
private var hClangd: HANDLE = INVALID_HANDLE_VALUE
@@ -412,6 +415,12 @@ extension ClangLanguageService {
412415

413416
let result = try await clangd.send(initialize)
414417
self.capabilities = result.capabilities
418+
if let legend = result.capabilities.semanticTokensProvider?.legend {
419+
self.semanticTokensTranslator = SemanticTokensLegendTranslator(
420+
clangdLegend: legend,
421+
sourceKitLSPLegend: SemanticTokensLegend.sourceKitLSPLegend
422+
)
423+
}
415424
return result
416425
}
417426

@@ -537,19 +546,50 @@ extension ClangLanguageService {
537546
}
538547

539548
func documentSemanticTokens(_ req: DocumentSemanticTokensRequest) async throws -> DocumentSemanticTokensResponse? {
540-
return try await forwardRequestToClangd(req)
549+
guard var response = try await forwardRequestToClangd(req) else {
550+
return nil
551+
}
552+
if let semanticTokensTranslator {
553+
response.data = semanticTokensTranslator.translate(response.data)
554+
}
555+
return response
541556
}
542557

543558
func documentSemanticTokensDelta(
544559
_ req: DocumentSemanticTokensDeltaRequest
545560
) async throws -> DocumentSemanticTokensDeltaResponse? {
546-
return try await forwardRequestToClangd(req)
561+
guard var response = try await forwardRequestToClangd(req) else {
562+
return nil
563+
}
564+
if let semanticTokensTranslator {
565+
switch response {
566+
case .tokens(var tokens):
567+
tokens.data = semanticTokensTranslator.translate(tokens.data)
568+
response = .tokens(tokens)
569+
case .delta(var delta):
570+
delta.edits = delta.edits.map {
571+
var edit = $0
572+
if let data = edit.data {
573+
edit.data = semanticTokensTranslator.translate(data)
574+
}
575+
return edit
576+
}
577+
response = .delta(delta)
578+
}
579+
}
580+
return response
547581
}
548582

549583
func documentSemanticTokensRange(
550584
_ req: DocumentSemanticTokensRangeRequest
551585
) async throws -> DocumentSemanticTokensResponse? {
552-
return try await forwardRequestToClangd(req)
586+
guard var response = try await forwardRequestToClangd(req) else {
587+
return nil
588+
}
589+
if let semanticTokensTranslator {
590+
response.data = semanticTokensTranslator.translate(response.data)
591+
}
592+
return response
553593
}
554594

555595
func colorPresentation(_ req: ColorPresentationRequest) async throws -> [ColorPresentation] {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 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 LSPLogging
14+
import LanguageServerProtocol
15+
16+
/// `clangd` might use a different semantic token legend than SourceKit-LSP.
17+
///
18+
/// This type allows translation the semantic tokens from `clangd` into the token legend that is used by SourceKit-LSP.
19+
struct SemanticTokensLegendTranslator {
20+
private enum Translation {
21+
/// The token type or modifier from clangd does not exist in SourceKit-LSP
22+
case doesNotExistInSourceKitLSP
23+
24+
/// The token type or modifier exists in SourceKit-LSP but it uses a different index. We need to translate the
25+
/// clangd index to this SourceKit-LSP index.
26+
case translation(UInt32)
27+
}
28+
29+
/// For all token types whose representation in clang differs from the representation in SourceKit-LSP, maps the
30+
/// index of that token type in clangd’s token type legend to the corresponding representation in SourceKit-LSP.
31+
private let tokenTypeTranslations: [UInt32: Translation]
32+
33+
/// For all token modifiers whose representation in clang differs from the representation in SourceKit-LSP, maps the
34+
/// index of that token modifier in clangd’s token type legend to the corresponding representation in SourceKit-LSP.
35+
private let tokenModifierTranslations: [UInt32: Translation]
36+
37+
/// A bitmask that has all bits set to 1 that are used for clangd token modifiers which have a different
38+
/// representation in SourceKit-LSP. If a token modifier does not have any bits set in common with this bitmask, no
39+
/// token mapping needs to be performed.
40+
private let tokenModifierTranslationBitmask: UInt32
41+
42+
/// For token types in clangd that do not exist in SourceKit-LSP's token legend, we need to map their token types to
43+
/// some valid SourceKit-LSP token type. Use the token type with this index.
44+
private let tokenTypeFallbackIndex: UInt32
45+
46+
init(clangdLegend: SemanticTokensLegend, sourceKitLSPLegend: SemanticTokensLegend) {
47+
var tokenTypeTranslations: [UInt32: Translation] = [:]
48+
for (index, tokenType) in clangdLegend.tokenTypes.enumerated() {
49+
switch sourceKitLSPLegend.tokenTypes.firstIndex(of: tokenType) {
50+
case index:
51+
break
52+
case nil:
53+
logger.error("Token type '\(tokenType, privacy: .public)' from clangd does not exist in SourceKit-LSP's legend")
54+
tokenTypeTranslations[UInt32(index)] = .doesNotExistInSourceKitLSP
55+
case let sourceKitLSPIndex?:
56+
logger.info(
57+
"Token type '\(tokenType, privacy: .public)' from clangd at index \(index) translated to \(sourceKitLSPIndex)"
58+
)
59+
tokenTypeTranslations[UInt32(index)] = .translation(UInt32(sourceKitLSPIndex))
60+
}
61+
}
62+
self.tokenTypeTranslations = tokenTypeTranslations
63+
64+
var tokenModifierTranslations: [UInt32: Translation] = [:]
65+
for (index, tokenModifier) in clangdLegend.tokenModifiers.enumerated() {
66+
switch sourceKitLSPLegend.tokenModifiers.firstIndex(of: tokenModifier) {
67+
case index:
68+
break
69+
case nil:
70+
logger.error(
71+
"Token modifier '\(tokenModifier, privacy: .public)' from clangd does not exist in SourceKit-LSP's legend"
72+
)
73+
tokenModifierTranslations[UInt32(index)] = .doesNotExistInSourceKitLSP
74+
case let sourceKitLSPIndex?:
75+
logger.error(
76+
"Token modifier '\(tokenModifier, privacy: .public)' from clangd at index \(index) translated to \(sourceKitLSPIndex)"
77+
)
78+
tokenModifierTranslations[UInt32(index)] = .translation(UInt32(sourceKitLSPIndex))
79+
}
80+
}
81+
self.tokenModifierTranslations = tokenModifierTranslations
82+
83+
var tokenModifierTranslationBitmask: UInt32 = 0
84+
for translatedIndex in tokenModifierTranslations.keys {
85+
tokenModifierTranslationBitmask.setBitToOne(at: Int(translatedIndex))
86+
}
87+
self.tokenModifierTranslationBitmask = tokenModifierTranslationBitmask
88+
89+
self.tokenTypeFallbackIndex = UInt32(
90+
sourceKitLSPLegend.tokenTypes.firstIndex(of: SemanticTokenTypes.unknown.name) ?? 0
91+
)
92+
}
93+
94+
func translate(_ data: [UInt32]) -> [UInt32] {
95+
var data = data
96+
// Translate token types, which are at offset n + 3.
97+
for i in stride(from: 3, to: data.count, by: 5) {
98+
switch tokenTypeTranslations[data[i]] {
99+
case .doesNotExistInSourceKitLSP: data[i] = tokenTypeFallbackIndex
100+
case .translation(let translatedIndex): data[i] = translatedIndex
101+
case nil: break
102+
}
103+
}
104+
105+
// Translate token modifiers, which are at offset n + 4
106+
for i in stride(from: 4, to: data.count, by: 5) {
107+
guard data[i] & tokenModifierTranslationBitmask != 0 else {
108+
// Fast path: There is nothing to translate
109+
continue
110+
}
111+
var translatedModifiersBitmask: UInt32 = 0
112+
for (clangdModifier, sourceKitLSPModifier) in tokenModifierTranslations {
113+
guard data[i].hasBitSet(at: Int(clangdModifier)) else {
114+
continue
115+
}
116+
switch sourceKitLSPModifier {
117+
case .doesNotExistInSourceKitLSP: break
118+
case .translation(let sourceKitLSPIndex): translatedModifiersBitmask.setBitToOne(at: Int(sourceKitLSPIndex))
119+
}
120+
}
121+
data[i] = data[i] & ~tokenModifierTranslationBitmask | translatedModifiersBitmask
122+
}
123+
124+
return data
125+
}
126+
}
127+
128+
fileprivate extension UInt32 {
129+
mutating func hasBitSet(at index: Int) -> Bool {
130+
return self & (1 << index) != 0
131+
}
132+
133+
mutating func setBitToOne(at index: Int) {
134+
self |= 1 << index
135+
}
136+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 LanguageServerProtocol
14+
15+
extension SemanticTokenTypes {
16+
// LSP doesn’t know about actors. Display actors as classes.
17+
public static var actor: Self { Self.class }
18+
19+
/// Token types are looked up by index
20+
public var tokenType: UInt32 {
21+
UInt32(Self.all.firstIndex(of: self)!)
22+
}
23+
}
24+
25+
extension SemanticTokensLegend {
26+
/// The semantic tokens legend that is used between SourceKit-LSP and the editor.
27+
static let sourceKitLSPLegend = SemanticTokensLegend(
28+
tokenTypes: SemanticTokenTypes.all.map(\.name),
29+
tokenModifiers: SemanticTokenModifiers.all.compactMap(\.name)
30+
)
31+
}

0 commit comments

Comments
 (0)