@@ -19,8 +19,9 @@ import SwiftParser
1919import SwiftSyntax
2020
2121extension 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