Skip to content

Commit d3bac1d

Browse files
wip
1 parent eab73dc commit d3bac1d

2 files changed

Lines changed: 436 additions & 66 deletions

File tree

Sources/SwiftLanguageService/SemanticTokens.swift

Lines changed: 167 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ import SwiftParser
1919
import SwiftSyntax
2020

2121
extension SwiftLanguageService {
22-
/// Requests the semantic highlighting tokens for the given snapshot from sourcekitd.
23-
private func semanticHighlightingTokens(for snapshot: DocumentSnapshot) async throws -> SyntaxHighlightingTokens? {
22+
/// Returns semantic reference tokens from SourceKit for the given snapshot, or `nil` if
23+
/// no real compile command is available or the request fails.
24+
private func sourceKitReferenceTokens(for snapshot: DocumentSnapshot) async throws -> SyntaxHighlightingTokens? {
2425
guard let compileCommand = await self.compileCommand(for: snapshot.uri, fallbackAfterTimeout: false),
2526
!compileCommand.isFallback
2627
else {
@@ -58,30 +59,43 @@ extension SwiftLanguageService {
5859
) async throws -> SyntaxHighlightingTokens {
5960
try Task.checkCancellation()
6061

61-
async let tree = syntaxTreeManager.syntaxTree(for: snapshot)
62-
let semanticTokens = await orLog("Loading semantic tokens") { try await semanticHighlightingTokens(for: snapshot) }
62+
let tree = await syntaxTreeManager.syntaxTree(for: snapshot)
6363

64-
let range =
64+
let byteRange =
6565
if let range {
6666
snapshot.byteSourceRange(of: range)
6767
} else {
68-
await tree.range
68+
tree.range
6969
}
7070

7171
try Task.checkCancellation()
7272

73-
let tokens =
74-
await tree
75-
.classifications(in: range)
73+
// Syntactic classification tokens (keywords, literals, comments, etc.)
74+
let syntaxTokens =
75+
tree
76+
.classifications(in: byteRange)
7677
.map { $0.highlightingTokens(in: snapshot) }
7778
.reduce(into: SyntaxHighlightingTokens(tokens: [])) { $0.tokens += $1.tokens }
7879

80+
// Declaration name tokens derived from the syntax tree
81+
let declarationVisitor = DeclarationHighlightingVisitor(snapshot: snapshot)
82+
declarationVisitor.walk(tree)
83+
let declarationTokens = SyntaxHighlightingTokens(tokens: declarationVisitor.tokens)
84+
7985
try Task.checkCancellation()
8086

81-
return
82-
tokens
83-
.mergingTokens(with: semanticTokens ?? SyntaxHighlightingTokens(tokens: []))
87+
let skTokens = await orLog("Loading SourceKit reference tokens") {
88+
try await sourceKitReferenceTokens(for: snapshot)
89+
}
90+
91+
let merged =
92+
syntaxTokens
93+
.mergingTokens(with: declarationTokens)
94+
.mergingTokens(with: skTokens ?? SyntaxHighlightingTokens(tokens: []))
8495
.sorted { $0.start < $1.start }
96+
97+
guard let range else { return merged }
98+
return SyntaxHighlightingTokens(tokens: merged.tokens.filter { range.overlaps($0.range) })
8599
}
86100

87101
package func documentSemanticTokens(
@@ -169,3 +183,144 @@ extension SyntaxClassification {
169183
}
170184
}
171185
}
186+
187+
// MARK: - Declaration Highlighting
188+
extension SwiftLanguageService {
189+
/// A `SyntaxVisitor` that emits semantic highlighting tokens for declaration name tokens.
190+
///
191+
/// SourceKit's `source.request.semantic_tokens` response covers symbol *usages* but omits the
192+
/// declaration sites themselves. This visitor walks the AST and emits properly-typed tokens
193+
/// (e.g. `.variable`, `.property`, `.struct`) with the `.declaration` modifier for every named
194+
/// declaration so that editors can highlight them distinctly from plain identifiers.
195+
private final class DeclarationHighlightingVisitor: SyntaxVisitor {
196+
var tokens: [SyntaxHighlightingToken] = []
197+
private let snapshot: DocumentSnapshot
198+
199+
/// Returns `true` if `node` is directly enclosed in a type body (struct, class, enum, actor,
200+
/// protocol, or extension member list) rather than a function or closure body.
201+
private func isInTypeMemberScope(_ node: some SyntaxProtocol) -> Bool {
202+
var current = node.parent
203+
while let parent = current {
204+
if parent.isProtocol((any DeclGroupSyntax).self) { return true }
205+
if parent.is(FunctionDeclSyntax.self) || parent.is(InitializerDeclSyntax.self)
206+
|| parent.is(ClosureExprSyntax.self) { return false }
207+
current = parent.parent
208+
}
209+
return false
210+
}
211+
212+
init(snapshot: DocumentSnapshot) {
213+
self.snapshot = snapshot
214+
super.init(viewMode: .sourceAccurate)
215+
}
216+
217+
private func emit(_ token: TokenSyntax, kind: SemanticTokenTypes, modifiers: SemanticTokenModifiers) {
218+
let range = token.trimmedRange
219+
guard !range.isEmpty else { return }
220+
let lspRange = snapshot.absolutePositionRange(of: range)
221+
tokens += lspRange.splitToSingleLineRanges(in: snapshot).map {
222+
SyntaxHighlightingToken(range: $0, kind: kind, modifiers: modifiers)
223+
}
224+
}
225+
226+
// MARK: Type declarations
227+
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
228+
emit(node.name, kind: .struct, modifiers: [.declaration])
229+
return .visitChildren
230+
}
231+
232+
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
233+
emit(node.name, kind: .class, modifiers: [.declaration])
234+
return .visitChildren
235+
}
236+
237+
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
238+
emit(node.name, kind: .enum, modifiers: [.declaration])
239+
return .visitChildren
240+
}
241+
242+
override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
243+
emit(node.name, kind: .actor, modifiers: [.declaration])
244+
return .visitChildren
245+
}
246+
247+
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
248+
emit(node.name, kind: .interface, modifiers: [.declaration])
249+
return .visitChildren
250+
}
251+
252+
// MARK: Type aliases and associated types
253+
override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind {
254+
emit(node.name, kind: .typeParameter, modifiers: [.declaration])
255+
return .visitChildren
256+
}
257+
258+
override func visit(_ node: AssociatedTypeDeclSyntax) -> SyntaxVisitorContinueKind {
259+
emit(node.name, kind: .typeParameter, modifiers: [.declaration])
260+
return .visitChildren
261+
}
262+
263+
// MARK: Function and method declarations
264+
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
265+
var modifiers: SemanticTokenModifiers = [.declaration]
266+
if node.modifiers.contains(where: {
267+
$0.name.tokenKind == .keyword(.static) || $0.name.tokenKind == .keyword(.class)
268+
}) {
269+
modifiers.insert(.static)
270+
}
271+
emit(node.name, kind: isInTypeMemberScope(node) ? .method : .function, modifiers: modifiers)
272+
return .visitChildren
273+
}
274+
275+
// MARK: Variable and property declarations
276+
override func visit(_ node: PatternBindingSyntax) -> SyntaxVisitorContinueKind {
277+
var modifiers: SemanticTokenModifiers = [.declaration]
278+
if let varDecl = node.parent?.parent?.as(VariableDeclSyntax.self),
279+
varDecl.modifiers.contains(where: {
280+
$0.name.tokenKind == .keyword(.static) || $0.name.tokenKind == .keyword(.class)
281+
})
282+
{
283+
modifiers.insert(.static)
284+
}
285+
let kind: SemanticTokenTypes = isInTypeMemberScope(node) ? .property : .variable
286+
emitAllIdentifiers(in: node.pattern, kind: kind, modifiers: modifiers)
287+
return .visitChildren
288+
}
289+
290+
private func emitAllIdentifiers(in pattern: PatternSyntax, kind: SemanticTokenTypes, modifiers: SemanticTokenModifiers) {
291+
if let idPattern = pattern.as(IdentifierPatternSyntax.self) {
292+
emit(idPattern.identifier, kind: kind, modifiers: modifiers)
293+
} else if let tuplePattern = pattern.as(TuplePatternSyntax.self) {
294+
for element in tuplePattern.elements {
295+
emitAllIdentifiers(in: element.pattern, kind: kind, modifiers: modifiers)
296+
}
297+
} else if let valueBinding = pattern.as(ValueBindingPatternSyntax.self) {
298+
emitAllIdentifiers(in: valueBinding.pattern, kind: kind, modifiers: modifiers)
299+
}
300+
}
301+
302+
// MARK: Optional binding conditions (if let x = ..., guard let x = ...)
303+
override func visit(_ node: OptionalBindingConditionSyntax) -> SyntaxVisitorContinueKind {
304+
emitAllIdentifiers(in: node.pattern, kind: .variable, modifiers: [.declaration])
305+
return .visitChildren
306+
}
307+
308+
// MARK: Switch case patterns (case let x, case let (a, b))
309+
override func visit(_ node: SwitchCaseItemSyntax) -> SyntaxVisitorContinueKind {
310+
emitAllIdentifiers(in: node.pattern, kind: .variable, modifiers: [.declaration])
311+
return .visitChildren
312+
}
313+
314+
// MARK: Enum case declarations
315+
override func visit(_ node: EnumCaseElementSyntax) -> SyntaxVisitorContinueKind {
316+
emit(node.name, kind: .enumMember, modifiers: [.declaration])
317+
return .visitChildren
318+
}
319+
320+
// MARK: Macro declarations
321+
override func visit(_ node: MacroDeclSyntax) -> SyntaxVisitorContinueKind {
322+
emit(node.name, kind: .macro, modifiers: [.declaration])
323+
return .visitChildren
324+
}
325+
}
326+
}

0 commit comments

Comments
 (0)