1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import LSPLogging
import LanguageServerProtocol
import SourceKitD
import SwiftIDEUtils
import SwiftParser
import SwiftSyntax
extension SwiftLanguageService {
/// Requests the semantic highlighting tokens for the given snapshot from sourcekitd.
private func semanticHighlightingTokens(for snapshot: DocumentSnapshot) async throws -> SyntaxHighlightingTokens? {
guard let buildSettings = await self.buildSettings(for: snapshot.uri), !buildSettings.isFallback else {
return nil
}
let skreq = sourcekitd.dictionary([
keys.request: requests.semanticTokens,
keys.sourceFile: snapshot.uri.pseudoPath,
keys.compilerArgs: buildSettings.compilerArgs as [SKDRequestValue],
])
let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
guard let skTokens: SKDResponseArray = dict[keys.semanticTokens] else {
return nil
}
return SyntaxHighlightingTokenParser(sourcekitd: sourcekitd).parseTokens(skTokens, in: snapshot)
}
/// Computes an array of syntax highlighting tokens from the syntax tree that
/// have been merged with any semantic tokens from SourceKit. If the provided
/// range is non-empty, this function restricts its output to only those
/// tokens whose ranges overlap it. If no range is provided, tokens for the
/// entire document are returned.
///
/// - Parameter range: The range of tokens to restrict this function to, if any.
/// - Returns: An array of syntax highlighting tokens.
private func mergedAndSortedTokens(
for snapshot: DocumentSnapshot,
in range: Range<Position>? = nil
) async throws -> SyntaxHighlightingTokens {
async let tree = syntaxTreeManager.syntaxTree(for: snapshot)
let semanticTokens = await orLog("Loading semantic tokens") { try await semanticHighlightingTokens(for: snapshot) }
let range =
if let range = range.flatMap({ snapshot.byteSourceRange(of: $0) }) {
range
} else {
ByteSourceRange(offset: 0, length: await tree.totalLength.utf8Length)
}
let tokens =
await tree
.classifications(in: range)
.map { $0.highlightingTokens(in: snapshot) }
.reduce(into: SyntaxHighlightingTokens(tokens: [])) { $0.tokens += $1.tokens }
return
tokens
.mergingTokens(with: semanticTokens ?? SyntaxHighlightingTokens(tokens: []))
.sorted { $0.start < $1.start }
}
public func documentSemanticTokens(
_ req: DocumentSemanticTokensRequest
) async throws -> DocumentSemanticTokensResponse? {
let snapshot = try self.documentManager.latestSnapshot(req.textDocument.uri)
let tokens = try await mergedAndSortedTokens(for: snapshot)
let encodedTokens = tokens.lspEncoded
return DocumentSemanticTokensResponse(data: encodedTokens)
}
public func documentSemanticTokensDelta(
_ req: DocumentSemanticTokensDeltaRequest
) async throws -> DocumentSemanticTokensDeltaResponse? {
return nil
}
public func documentSemanticTokensRange(
_ req: DocumentSemanticTokensRangeRequest
) async throws -> DocumentSemanticTokensResponse? {
let snapshot = try self.documentManager.latestSnapshot(req.textDocument.uri)
let tokens = try await mergedAndSortedTokens(for: snapshot, in: req.range)
let encodedTokens = tokens.lspEncoded
return DocumentSemanticTokensResponse(data: encodedTokens)
}
}
extension SyntaxClassifiedRange {
fileprivate func highlightingTokens(in snapshot: DocumentSnapshot) -> SyntaxHighlightingTokens {
guard let (kind, modifiers) = self.kind.highlightingKindAndModifiers else {
return SyntaxHighlightingTokens(tokens: [])
}
let multiLineRange = snapshot.positionOf(utf8Offset: self.offset)..<snapshot.positionOf(utf8Offset: self.endOffset)
let ranges = multiLineRange.splitToSingleLineRanges(in: snapshot)
let tokens = ranges.map {
SyntaxHighlightingToken(
range: $0,
kind: kind,
modifiers: modifiers
)
}
return SyntaxHighlightingTokens(tokens: tokens)
}
}
extension SyntaxClassification {
fileprivate var highlightingKindAndModifiers: (SemanticTokenTypes, SemanticTokenModifiers)? {
switch self {
case .none:
return nil
case .editorPlaceholder:
return nil
case .keyword:
return (.keyword, [])
case .identifier, .type, .dollarIdentifier:
return (.identifier, [])
case .operator:
return (.operator, [])
case .integerLiteral, .floatLiteral:
return (.number, [])
case .stringLiteral:
return (.string, [])
case .regexLiteral:
return (.regexp, [])
case .ifConfigDirective:
return (.macro, [])
case .attribute:
return (.modifier, [])
case .lineComment, .blockComment:
return (.comment, [])
case .docLineComment, .docBlockComment:
return (.comment, .documentation)
case .argumentLabel:
return (.function, [])
}
}
}
|