Skip to content

Commit 8556023

Browse files
authored
Merge pull request #1146 from gremlinflat/syntax-highlighting-tokens-refactor
Introduce ```SyntaxHighlightingTokens``` instead of ```[SyntaxHighlightingToken]```
2 parents 7ee04d1 + cd4f7c9 commit 8556023

File tree

8 files changed

+181
-151
lines changed

8 files changed

+181
-151
lines changed

Sources/SKTestSupport/Array+SyntaxHighlightingToken.swift

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

Sources/SKTestSupport/SkipUnless.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,12 @@ public enum SkipUnless {
106106
let response = try unwrap(
107107
await testClient.send(DocumentSemanticTokensRequest(textDocument: TextDocumentIdentifier(uri)))
108108
)
109-
let tokens = [SyntaxHighlightingToken](lspEncodedTokens: response.data)
109+
110+
let tokens = SyntaxHighlightingTokens(lspEncodedTokens: response.data)
110111

111112
// If we don't have semantic token support in sourcekitd, the second token is an identifier based on the syntax
112113
// tree, not a property.
113-
return tokens != [
114+
return tokens.tokens != [
114115
SyntaxHighlightingToken(
115116
range: Position(line: 0, utf16index: 0)..<Position(line: 0, utf16index: 1),
116117
kind: .number,

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ target_sources(SourceKitLSP PRIVATE
3939
Swift/SwiftLanguageService.swift
4040
Swift/SymbolInfo.swift
4141
Swift/SyntaxHighlightingToken.swift
42+
Swift/SyntaxHighlightingTokens.swift
4243
Swift/SyntaxHighlightingTokenParser.swift
4344
Swift/SyntaxTreeManager.swift
4445
Swift/VariableTypeInfo.swift

Sources/SourceKitLSP/Swift/SemanticTokens.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import SwiftSyntax
1919

2020
extension SwiftLanguageService {
2121
/// Requests the semantic highlighting tokens for the given snapshot from sourcekitd.
22-
private func semanticHighlightingTokens(for snapshot: DocumentSnapshot) async throws -> [SyntaxHighlightingToken]? {
22+
private func semanticHighlightingTokens(for snapshot: DocumentSnapshot) async throws -> SyntaxHighlightingTokens? {
2323
guard let buildSettings = await self.buildSettings(for: snapshot.uri), !buildSettings.isFallback else {
2424
return nil
2525
}
@@ -35,6 +35,7 @@ extension SwiftLanguageService {
3535
guard let skTokens: SKDResponseArray = dict[keys.semanticTokens] else {
3636
return nil
3737
}
38+
3839
return SyntaxHighlightingTokenParser(sourcekitd: sourcekitd).parseTokens(skTokens, in: snapshot)
3940
}
4041

@@ -49,7 +50,7 @@ extension SwiftLanguageService {
4950
private func mergedAndSortedTokens(
5051
for snapshot: DocumentSnapshot,
5152
in range: Range<Position>? = nil
52-
) async throws -> [SyntaxHighlightingToken] {
53+
) async throws -> SyntaxHighlightingTokens {
5354
async let tree = syntaxTreeManager.syntaxTree(for: snapshot)
5455
let semanticTokens = await orLog("Loading semantic tokens") { try await semanticHighlightingTokens(for: snapshot) }
5556

@@ -59,11 +60,16 @@ extension SwiftLanguageService {
5960
} else {
6061
ByteSourceRange(offset: 0, length: await tree.totalLength.utf8Length)
6162
}
62-
return
63+
64+
let tokens =
6365
await tree
6466
.classifications(in: range)
65-
.flatMap({ $0.highlightingTokens(in: snapshot) })
66-
.mergingTokens(with: semanticTokens ?? [])
67+
.map { $0.highlightingTokens(in: snapshot) }
68+
.reduce(into: SyntaxHighlightingTokens(tokens: [])) { $0.tokens += $1.tokens }
69+
70+
return
71+
tokens
72+
.mergingTokens(with: semanticTokens ?? SyntaxHighlightingTokens(tokens: []))
6773
.sorted { $0.start < $1.start }
6874
}
6975

@@ -102,28 +108,30 @@ extension Range where Bound == Position {
102108
}
103109

104110
extension SyntaxClassifiedRange {
105-
fileprivate func highlightingTokens(in snapshot: DocumentSnapshot) -> [SyntaxHighlightingToken] {
111+
fileprivate func highlightingTokens(in snapshot: DocumentSnapshot) -> SyntaxHighlightingTokens {
106112
guard let (kind, modifiers) = self.kind.highlightingKindAndModifiers else {
107-
return []
113+
return SyntaxHighlightingTokens(tokens: [])
108114
}
109115

110116
guard
111117
let start: Position = snapshot.positionOf(utf8Offset: self.offset),
112118
let end: Position = snapshot.positionOf(utf8Offset: self.endOffset)
113119
else {
114-
return []
120+
return SyntaxHighlightingTokens(tokens: [])
115121
}
116122

117123
let multiLineRange = start..<end
118124
let ranges = multiLineRange.splitToSingleLineRanges(in: snapshot)
119125

120-
return ranges.map {
126+
let tokens = ranges.map {
121127
SyntaxHighlightingToken(
122128
range: $0,
123129
kind: kind,
124130
modifiers: modifiers
125131
)
126132
}
133+
134+
return SyntaxHighlightingTokens(tokens: tokens)
127135
}
128136
}
129137

Sources/SourceKitLSP/Swift/SyntaxHighlightingToken.swift

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -48,48 +48,6 @@ public struct SyntaxHighlightingToken: Hashable {
4848
}
4949
}
5050

51-
extension Array where Element == SyntaxHighlightingToken {
52-
/// The LSP representation of syntax highlighting tokens. Note that this
53-
/// requires the tokens in this array to be sorted.
54-
public var lspEncoded: [UInt32] {
55-
var previous = Position(line: 0, utf16index: 0)
56-
var rawTokens: [UInt32] = []
57-
rawTokens.reserveCapacity(count * 5)
58-
59-
for token in self {
60-
let lineDelta = token.start.line - previous.line
61-
let charDelta =
62-
token.start.utf16index - (
63-
// The character delta is relative to the previous token's start
64-
// only if the token is on the previous token's line.
65-
previous.line == token.start.line ? previous.utf16index : 0)
66-
67-
// We assert that the tokens are actually sorted
68-
assert(lineDelta >= 0)
69-
assert(charDelta >= 0)
70-
71-
previous = token.start
72-
rawTokens += [
73-
UInt32(lineDelta),
74-
UInt32(charDelta),
75-
UInt32(token.utf16length),
76-
token.kind.tokenType,
77-
token.modifiers.rawValue,
78-
]
79-
}
80-
81-
return rawTokens
82-
}
83-
84-
/// Merges the tokens in this array into a new token array,
85-
/// preferring the given array's tokens if duplicate ranges are
86-
/// found.
87-
public func mergingTokens(with other: [SyntaxHighlightingToken]) -> [SyntaxHighlightingToken] {
88-
let otherRanges = Set(other.map(\.range))
89-
return filter { !otherRanges.contains($0.range) } + other
90-
}
91-
}
92-
9351
extension SemanticTokenTypes {
9452
/// **(LSP Extension)**
9553
public static let identifier = Self("identifier")

Sources/SourceKitLSP/Swift/SyntaxHighlightingTokenParser.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ struct SyntaxHighlightingTokenParser {
2525
private func parseTokens(
2626
_ response: SKDResponseDictionary,
2727
in snapshot: DocumentSnapshot,
28-
into tokens: inout [SyntaxHighlightingToken]
28+
into tokens: inout SyntaxHighlightingTokens
2929
) {
3030
let keys = sourcekitd.keys
3131

@@ -52,7 +52,7 @@ struct SyntaxHighlightingTokenParser {
5252
let multiLineRange = start..<end
5353
let ranges = multiLineRange.splitToSingleLineRanges(in: snapshot)
5454

55-
tokens += ranges.map {
55+
tokens.tokens += ranges.map {
5656
SyntaxHighlightingToken(
5757
range: $0,
5858
kind: kind,
@@ -70,16 +70,16 @@ struct SyntaxHighlightingTokenParser {
7070
private func parseTokens(
7171
_ response: SKDResponseArray,
7272
in snapshot: DocumentSnapshot,
73-
into tokens: inout [SyntaxHighlightingToken]
73+
into tokens: inout SyntaxHighlightingTokens
7474
) {
7575
response.forEach { (_, value) in
7676
parseTokens(value, in: snapshot, into: &tokens)
7777
return true
7878
}
7979
}
8080

81-
func parseTokens(_ response: SKDResponseArray, in snapshot: DocumentSnapshot) -> [SyntaxHighlightingToken] {
82-
var tokens: [SyntaxHighlightingToken] = []
81+
func parseTokens(_ response: SKDResponseArray, in snapshot: DocumentSnapshot) -> SyntaxHighlightingTokens {
82+
var tokens: SyntaxHighlightingTokens = SyntaxHighlightingTokens(tokens: [])
8383
parseTokens(response, in: snapshot, into: &tokens)
8484
return tokens
8585
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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+
import SourceKitD
16+
17+
/// A wrapper around an array of syntax highlighting tokens.
18+
public struct SyntaxHighlightingTokens {
19+
public var tokens: [SyntaxHighlightingToken]
20+
21+
public init(tokens: [SyntaxHighlightingToken]) {
22+
self.tokens = tokens
23+
}
24+
25+
/// The LSP representation of syntax highlighting tokens. Note that this
26+
/// requires the tokens in this array to be sorted.
27+
public var lspEncoded: [UInt32] {
28+
var previous = Position(line: 0, utf16index: 0)
29+
var rawTokens: [UInt32] = []
30+
rawTokens.reserveCapacity(tokens.count * 5)
31+
32+
for token in self.tokens {
33+
let lineDelta = token.start.line - previous.line
34+
let charDelta =
35+
token.start.utf16index - (
36+
// The character delta is relative to the previous token's start
37+
// only if the token is on the previous token's line.
38+
previous.line == token.start.line ? previous.utf16index : 0)
39+
40+
// We assert that the tokens are actually sorted
41+
assert(lineDelta >= 0)
42+
assert(charDelta >= 0)
43+
44+
previous = token.start
45+
rawTokens += [
46+
UInt32(lineDelta),
47+
UInt32(charDelta),
48+
UInt32(token.utf16length),
49+
token.kind.tokenType,
50+
token.modifiers.rawValue,
51+
]
52+
}
53+
54+
return rawTokens
55+
}
56+
57+
/// Merges the tokens in this array into a new token array,
58+
/// preferring the given array's tokens if duplicate ranges are
59+
/// found.
60+
public func mergingTokens(with other: SyntaxHighlightingTokens) -> SyntaxHighlightingTokens {
61+
let otherRanges = Set(other.tokens.map(\.range))
62+
return SyntaxHighlightingTokens(tokens: tokens.filter { !otherRanges.contains($0.range) } + other.tokens)
63+
}
64+
65+
public func mergingTokens(with other: [SyntaxHighlightingToken]) -> SyntaxHighlightingTokens {
66+
let otherRanges = Set(other.map(\.range))
67+
return SyntaxHighlightingTokens(tokens: tokens.filter { !otherRanges.contains($0.range) } + other)
68+
}
69+
70+
/// Sorts the tokens in this array by their start position.
71+
public func sorted(_ areInIncreasingOrder: (SyntaxHighlightingToken, SyntaxHighlightingToken) -> Bool)
72+
-> SyntaxHighlightingTokens
73+
{
74+
SyntaxHighlightingTokens(tokens: tokens.sorted(by: areInIncreasingOrder))
75+
}
76+
}
77+
78+
extension SyntaxHighlightingTokens {
79+
/// Decodes the LSP representation of syntax highlighting tokens
80+
public init(lspEncodedTokens rawTokens: [UInt32]) {
81+
self.init(tokens: [])
82+
assert(rawTokens.count.isMultiple(of: 5))
83+
self.tokens.reserveCapacity(rawTokens.count / 5)
84+
85+
var current = Position(line: 0, utf16index: 0)
86+
87+
for i in stride(from: 0, to: rawTokens.count, by: 5) {
88+
let lineDelta = Int(rawTokens[i])
89+
let charDelta = Int(rawTokens[i + 1])
90+
let length = Int(rawTokens[i + 2])
91+
let rawKind = rawTokens[i + 3]
92+
let rawModifiers = rawTokens[i + 4]
93+
94+
current.line += lineDelta
95+
96+
if lineDelta == 0 {
97+
current.utf16index += charDelta
98+
} else {
99+
current.utf16index = charDelta
100+
}
101+
102+
let kind = SemanticTokenTypes.all[Int(rawKind)]
103+
let modifiers = SemanticTokenModifiers(rawValue: rawModifiers)
104+
105+
self.tokens.append(
106+
SyntaxHighlightingToken(
107+
start: current,
108+
utf16length: length,
109+
kind: kind,
110+
modifiers: modifiers
111+
)
112+
)
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)