Skip to content

Commit 3124c96

Browse files
authored
Merge pull request #651 from apple/syn-city
2 parents e7701f6 + bba47a4 commit 3124c96

File tree

8 files changed

+117
-58
lines changed

8 files changed

+117
-58
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ find_package(LLBuild QUIET)
2121
find_package(ArgumentParser CONFIG REQUIRED)
2222
find_package(SwiftCollections QUIET)
2323
find_package(SwiftSystem CONFIG REQUIRED)
24+
find_package(SwiftSyntax CONFIG REQUIRED)
2425

2526
include(SwiftSupport)
2627

Package.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ let package = Package(
4949
"SourceKitD",
5050
"SKSwiftPMWorkspace",
5151
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
52+
.product(name: "SwiftSyntax", package: "swift-syntax"),
53+
.product(name: "SwiftParser", package: "swift-syntax"),
5254
],
5355
exclude: ["CMakeLists.txt"]),
5456

@@ -241,12 +243,14 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
241243
.package(name: "SwiftPM", url: "https://github.com/apple/swift-package-manager.git", .branch("main")),
242244
.package(url: "https://github.com/apple/swift-tools-support-core.git", .branch("main")),
243245
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.0.1")),
246+
.package(url: "https://github.com/apple/swift-syntax.git", .branch("main")),
244247
]
245248
} else {
246249
package.dependencies += [
247250
.package(name: "IndexStoreDB", path: "../indexstore-db"),
248251
.package(name: "SwiftPM", path: "../swiftpm"),
249252
.package(path: "../swift-tools-support-core"),
250-
.package(path: "../swift-argument-parser")
253+
.package(path: "../swift-argument-parser"),
254+
.package(path: "../swift-syntax")
251255
]
252256
}

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ target_link_libraries(SourceKitLSP PUBLIC
4141
SKCore
4242
SKSwiftPMWorkspace
4343
SourceKitD
44+
SwiftSyntax::SwiftBasicFormat
45+
SwiftSyntax::SwiftParser
46+
SwiftSyntax::SwiftDiagnostics
47+
SwiftSyntax::SwiftSyntax
4448
TSCUtility)
4549
target_link_libraries(SourceKitLSP PRIVATE
4650
$<$<NOT:$<PLATFORM_ID:Darwin>>:FoundationXML>)
51+

Sources/SourceKitLSP/DocumentManager.swift

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,14 @@ public final class DocumentManager {
153153
// Remove all tokens in the updated range and shift later ones.
154154
let rangeAdjuster = RangeAdjuster(edit: edit)!
155155

156-
document.latestTokens.withMutableTokensOfEachKind { tokens in
157-
tokens = Array(tokens.lazy
158-
.compactMap {
159-
var token = $0
160-
if let adjustedRange = rangeAdjuster.adjust(token.range) {
161-
token.range = adjustedRange
162-
return token
163-
} else {
164-
return nil
165-
}
166-
})
156+
document.latestTokens.semantic = document.latestTokens.semantic.compactMap {
157+
var token = $0
158+
if let adjustedRange = rangeAdjuster.adjust(token.range) {
159+
token.range = adjustedRange
160+
return token
161+
} else {
162+
return nil
163+
}
167164
}
168165
} else {
169166
// Full text replacement.

Sources/SourceKitLSP/DocumentTokens.swift

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,100 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import LanguageServerProtocol
14+
import SwiftSyntax
1415

1516
/// Syntax highlighting tokens for a particular document.
1617
public struct DocumentTokens {
17-
/// Lexical tokens, e.g. keywords, raw identifiers, ...
18-
public var lexical: [SyntaxHighlightingToken] = []
18+
/// The syntax tree representing the entire document.
19+
public var syntaxTree: SourceFileSyntax?
1920
/// Semantic tokens, e.g. variable references, type references, ...
2021
public var semantic: [SyntaxHighlightingToken] = []
22+
}
2123

22-
private var merged: [SyntaxHighlightingToken] {
23-
lexical.mergingTokens(with: semantic)
24+
extension DocumentSnapshot {
25+
/// Computes an array of syntax highlighting tokens from the syntax tree that
26+
/// have been merged with any semantic tokens from SourceKit. If the provided
27+
/// range is non-empty, this function restricts its output to only those
28+
/// tokens whose ranges overlap it. If no range is provided, tokens for the
29+
/// entire document are returned.
30+
///
31+
/// - Parameter range: The range of tokens to restrict this function to, if any.
32+
/// - Returns: An array of syntax highlighting tokens.
33+
public func mergedAndSortedTokens(in range: Range<Position>? = nil) -> [SyntaxHighlightingToken] {
34+
guard let tree = self.tokens.syntaxTree else {
35+
return self.tokens.semantic
36+
}
37+
let range = range.flatMap({ $0.byteSourceRange(in: self) })
38+
?? ByteSourceRange(offset: 0, length: tree.byteSize)
39+
return tree
40+
.classifications(in: range)
41+
.flatMap({ $0.highlightingTokens(in: self) })
42+
.mergingTokens(with: self.tokens.semantic)
43+
.sorted { $0.start < $1.start }
2444
}
25-
public var mergedAndSorted: [SyntaxHighlightingToken] {
26-
merged.sorted { $0.start < $1.start }
45+
}
46+
47+
extension Range where Bound == Position {
48+
fileprivate func byteSourceRange(in snapshot: DocumentSnapshot) -> ByteSourceRange? {
49+
return snapshot.utf8OffsetRange(of: self).map({ ByteSourceRange(offset: $0.startIndex, length: $0.count) })
2750
}
51+
}
2852

29-
/// Modifies the syntax highlighting tokens of each kind
30-
/// (lexical and semantic) according to `action`.
31-
public mutating func withMutableTokensOfEachKind(_ action: (inout [SyntaxHighlightingToken]) -> Void) {
32-
action(&lexical)
33-
action(&semantic)
53+
extension SyntaxClassifiedRange {
54+
fileprivate func highlightingTokens(in snapshot: DocumentSnapshot) -> [SyntaxHighlightingToken] {
55+
guard let (kind, modifiers) = self.kind.highlightingKindAndModifiers else {
56+
return []
57+
}
58+
59+
guard
60+
let start: Position = snapshot.positionOf(utf8Offset: self.offset),
61+
let end: Position = snapshot.positionOf(utf8Offset: self.endOffset)
62+
else {
63+
return []
64+
}
65+
66+
let multiLineRange = start..<end
67+
let ranges = multiLineRange.splitToSingleLineRanges(in: snapshot)
68+
69+
return ranges.map {
70+
SyntaxHighlightingToken(
71+
range: $0,
72+
kind: kind,
73+
modifiers: modifiers
74+
)
75+
}
3476
}
77+
}
3578

36-
// Replace all lexical tokens in `range`.
37-
public mutating func replaceLexical(in range: Range<Position>, with newTokens: [SyntaxHighlightingToken]) {
38-
lexical.removeAll { $0.range.overlaps(range) }
39-
lexical += newTokens
79+
extension SyntaxClassification {
80+
fileprivate var highlightingKindAndModifiers: (SyntaxHighlightingToken.Kind, SyntaxHighlightingToken.Modifiers)? {
81+
switch self {
82+
case .none:
83+
return nil
84+
case .editorPlaceholder:
85+
return nil
86+
case .stringInterpolationAnchor:
87+
return nil
88+
case .keyword:
89+
return (.keyword, [])
90+
case .identifier, .typeIdentifier, .dollarIdentifier:
91+
return (.identifier, [])
92+
case .operatorIdentifier:
93+
return (.operator, [])
94+
case .integerLiteral, .floatingLiteral:
95+
return (.number, [])
96+
case .stringLiteral:
97+
return (.string, [])
98+
case .poundDirectiveKeyword:
99+
return (.macro, [])
100+
case .buildConfigId, .objectLiteral:
101+
return (.macro, [])
102+
case .attribute:
103+
return (.modifier, [])
104+
case .lineComment, .blockComment:
105+
return (.comment, [])
106+
case .docLineComment, .docBlockComment:
107+
return (.comment, .documentation)
108+
}
40109
}
41110
}

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import LSPLogging
1717
import SKCore
1818
import SKSupport
1919
import SourceKitD
20+
import SwiftSyntax
21+
import SwiftParser
2022

2123
#if os(Windows)
2224
import WinSDK
@@ -167,14 +169,13 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
167169

168170
/// Updates the lexical tokens for the given `snapshot`.
169171
/// Must be called on `self.queue`.
170-
private func updateLexicalTokens(
171-
response: SKDResponseDictionary,
172+
private func updateSyntacticTokens(
172173
for snapshot: DocumentSnapshot
173174
) {
174175
dispatchPrecondition(condition: .onQueue(queue))
175176

176177
let uri = snapshot.document.uri
177-
let docTokens = updatedLexicalTokens(response: response, for: snapshot)
178+
let docTokens = updateSyntaxTree(for: snapshot)
178179

179180
do {
180181
try documentManager.updateTokens(uri, tokens: docTokens)
@@ -184,31 +185,13 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
184185
}
185186

186187
/// Returns the updated lexical tokens for the given `snapshot`.
187-
private func updatedLexicalTokens(
188-
response: SKDResponseDictionary,
188+
private func updateSyntaxTree(
189189
for snapshot: DocumentSnapshot
190190
) -> DocumentTokens {
191191
logExecutionTime(level: .debug) {
192192
var docTokens = snapshot.tokens
193193

194-
guard let offset: Int = response[keys.offset],
195-
let length: Int = response[keys.length],
196-
let start: Position = snapshot.positionOf(utf8Offset: offset),
197-
let end: Position = snapshot.positionOf(utf8Offset: offset + length) else {
198-
// This e.g. happens in the case of empty edits
199-
log("did not update lexical/syntactic tokens, no range found", level: .debug)
200-
return docTokens
201-
}
202-
203-
let range = start..<end
204-
205-
if let syntaxMap: SKDResponseArray = response[keys.syntaxmap] {
206-
let tokenParser = SyntaxHighlightingTokenParser(sourcekitd: sourcekitd)
207-
var tokens: [SyntaxHighlightingToken] = []
208-
tokenParser.parseTokens(syntaxMap, in: snapshot, into: &tokens)
209-
210-
docTokens.replaceLexical(in: range, with: tokens)
211-
}
194+
docTokens.syntaxTree = Parser.parse(source: snapshot.text)
212195

213196
return docTokens
214197
}
@@ -438,7 +421,7 @@ extension SwiftLanguageServer {
438421
}
439422
self.publishDiagnostics(
440423
response: dict, for: snapshot, compileCommand: compileCmd)
441-
self.updateLexicalTokens(response: dict, for: snapshot)
424+
self.updateSyntacticTokens(for: snapshot)
442425
}
443426

444427
public func documentUpdatedBuildSettings(_ uri: DocumentURI, change: FileBuildSettingsChange) {
@@ -503,7 +486,7 @@ extension SwiftLanguageServer {
503486
return
504487
}
505488
self.publishDiagnostics(response: dict, for: snapshot, compileCommand: compileCommand)
506-
self.updateLexicalTokens(response: dict, for: snapshot)
489+
self.updateSyntacticTokens(for: snapshot)
507490
}
508491
}
509492

@@ -557,8 +540,8 @@ extension SwiftLanguageServer {
557540

558541
self.adjustDiagnosticRanges(of: note.textDocument.uri, for: edit)
559542
} updateDocumentTokens: { (after: DocumentSnapshot) in
560-
if let dict = lastResponse {
561-
return self.updatedLexicalTokens(response: dict, for: after)
543+
if lastResponse != nil {
544+
return self.updateSyntaxTree(for: after)
562545
} else {
563546
return DocumentTokens()
564547
}
@@ -876,7 +859,7 @@ extension SwiftLanguageServer {
876859
return
877860
}
878861

879-
let tokens = snapshot.tokens.mergedAndSorted
862+
let tokens = snapshot.mergedAndSortedTokens()
880863
let encodedTokens = tokens.lspEncoded
881864

882865
req.reply(DocumentSemanticTokensResponse(data: encodedTokens))
@@ -899,7 +882,7 @@ extension SwiftLanguageServer {
899882
return
900883
}
901884

902-
let tokens = snapshot.tokens.mergedAndSorted.filter { $0.range.overlaps(range) }
885+
let tokens = snapshot.mergedAndSortedTokens(in: range)
903886
let encodedTokens = tokens.lspEncoded
904887

905888
req.reply(DocumentSemanticTokensResponse(data: encodedTokens))

Sources/SourceKitLSP/Swift/SyntaxHighlightingTokenParser.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ struct SyntaxHighlightingTokenParser {
187187

188188
extension Range where Bound == Position {
189189
/// Splits a potentially multi-line range to multiple single-line ranges.
190-
fileprivate func splitToSingleLineRanges(in snapshot: DocumentSnapshot) -> [Self] {
190+
func splitToSingleLineRanges(in snapshot: DocumentSnapshot) -> [Self] {
191191
if isEmpty {
192192
return []
193193
}

Tests/SourceKitLSPTests/SemanticTokensTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ final class SemanticTokensTests: XCTestCase {
496496
"""
497497
let tokens = openAndPerformSemanticTokensRequest(text: text)
498498
XCTAssertEqual(tokens, [
499-
Token(line: 0, utf16index: 0, length: 5, kind: .modifier),
499+
Token(line: 0, utf16index: 0, length: 5, kind: .keyword),
500500
Token(line: 0, utf16index: 6, length: 8, kind: .keyword),
501501
Token(line: 0, utf16index: 15, length: 2, kind: .operator),
502502
Token(line: 0, utf16index: 19, length: 20, kind: .identifier),

0 commit comments

Comments
 (0)