mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
253 lines
8.1 KiB
Swift
253 lines
8.1 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2021 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
|
|
|
|
/// Parses tokens from sourcekitd response dictionaries.
|
|
struct SyntaxHighlightingTokenParser {
|
|
private let sourcekitd: SourceKitD
|
|
|
|
init(sourcekitd: SourceKitD) {
|
|
self.sourcekitd = sourcekitd
|
|
}
|
|
|
|
private func parseTokens(
|
|
_ response: SKDResponseDictionary,
|
|
in snapshot: DocumentSnapshot,
|
|
into tokens: inout [SyntaxHighlightingToken]
|
|
) {
|
|
let keys = sourcekitd.keys
|
|
|
|
if let offset: Int = response[keys.offset],
|
|
var length: Int = response[keys.length],
|
|
let start: Position = snapshot.positionOf(utf8Offset: offset),
|
|
let skKind: sourcekitd_uid_t = response[keys.kind],
|
|
case (let kind, var modifiers)? = parseKindAndModifiers(skKind)
|
|
{
|
|
|
|
// If the name is escaped in backticks, we need to add two characters to the
|
|
// length for the backticks.
|
|
if modifiers.contains(.declaration),
|
|
let index = snapshot.indexOf(utf8Offset: offset), snapshot.text[index] == "`"
|
|
{
|
|
length += 2
|
|
}
|
|
|
|
if let isSystem: Bool = response[keys.is_system], isSystem {
|
|
modifiers.insert(.defaultLibrary)
|
|
}
|
|
|
|
if let end: Position = snapshot.positionOf(utf8Offset: offset + length) {
|
|
let multiLineRange = start..<end
|
|
let ranges = multiLineRange.splitToSingleLineRanges(in: snapshot)
|
|
|
|
tokens += ranges.map {
|
|
SyntaxHighlightingToken(
|
|
range: $0,
|
|
kind: kind,
|
|
modifiers: modifiers
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
if let substructure: SKDResponseArray = response[keys.substructure] {
|
|
parseTokens(substructure, in: snapshot, into: &tokens)
|
|
}
|
|
}
|
|
|
|
private func parseTokens(
|
|
_ response: SKDResponseArray,
|
|
in snapshot: DocumentSnapshot,
|
|
into tokens: inout [SyntaxHighlightingToken]
|
|
) {
|
|
response.forEach { (_, value) in
|
|
parseTokens(value, in: snapshot, into: &tokens)
|
|
return true
|
|
}
|
|
}
|
|
|
|
func parseTokens(_ response: SKDResponseArray, in snapshot: DocumentSnapshot) -> [SyntaxHighlightingToken] {
|
|
var tokens: [SyntaxHighlightingToken] = []
|
|
parseTokens(response, in: snapshot, into: &tokens)
|
|
return tokens
|
|
}
|
|
|
|
private func parseKindAndModifiers(
|
|
_ uid: sourcekitd_uid_t
|
|
) -> (SyntaxHighlightingToken.Kind, SyntaxHighlightingToken.Modifiers)? {
|
|
let api = sourcekitd.api
|
|
let values = sourcekitd.values
|
|
switch uid {
|
|
case values.kind_keyword,
|
|
values.syntaxtype_keyword:
|
|
return (.keyword, [])
|
|
case values.syntaxtype_attribute_builtin:
|
|
return (.modifier, [])
|
|
case values.decl_module:
|
|
return (.namespace, [])
|
|
case values.decl_class:
|
|
return (.class, [.declaration])
|
|
case values.ref_class:
|
|
return (.class, [])
|
|
case values.decl_actor:
|
|
return (.actor, [.declaration])
|
|
case values.ref_actor:
|
|
return (.actor, [])
|
|
case values.decl_struct:
|
|
return (.struct, [.declaration])
|
|
case values.ref_struct:
|
|
return (.struct, [])
|
|
case values.decl_enum:
|
|
return (.enum, [.declaration])
|
|
case values.ref_enum:
|
|
return (.enum, [])
|
|
case values.decl_enumelement:
|
|
return (.enumMember, [.declaration])
|
|
case values.ref_enumelement:
|
|
return (.enumMember, [])
|
|
case values.decl_protocol:
|
|
return (.interface, [.declaration])
|
|
case values.ref_protocol:
|
|
return (.interface, [])
|
|
case values.decl_associatedtype,
|
|
values.decl_typealias,
|
|
values.decl_generic_type_param:
|
|
return (.typeParameter, [.declaration])
|
|
case values.ref_associatedtype,
|
|
values.ref_typealias,
|
|
values.ref_generic_type_param:
|
|
return (.typeParameter, [])
|
|
case values.decl_function_free:
|
|
return (.function, [.declaration])
|
|
case values.decl_function_method_static,
|
|
values.decl_function_method_class,
|
|
values.decl_function_constructor:
|
|
return (.method, [.declaration, .static])
|
|
case values.decl_function_method_instance,
|
|
values.decl_function_destructor,
|
|
values.decl_function_subscript:
|
|
return (.method, [.declaration])
|
|
case values.ref_function_free:
|
|
return (.function, [])
|
|
case values.ref_function_method_static,
|
|
values.ref_function_method_class,
|
|
values.ref_function_constructor:
|
|
return (.method, [.static])
|
|
case values.ref_function_method_instance,
|
|
values.ref_function_destructor,
|
|
values.ref_function_subscript:
|
|
return (.method, [])
|
|
case values.syntaxtype_operator:
|
|
return (.operator, [])
|
|
case values.decl_function_operator_prefix,
|
|
values.decl_function_operator_postfix,
|
|
values.decl_function_operator_infix:
|
|
return (.operator, [.declaration])
|
|
case values.ref_function_operator_prefix,
|
|
values.ref_function_operator_postfix,
|
|
values.ref_function_operator_infix:
|
|
return (.operator, [])
|
|
case values.decl_var_static,
|
|
values.decl_var_class,
|
|
values.decl_var_instance:
|
|
return (.property, [.declaration])
|
|
case values.decl_var_parameter:
|
|
// SourceKit seems to use these to refer to parameter labels,
|
|
// therefore we don't use .parameter here (which LSP clients like
|
|
// VSCode seem to interpret as variable identifiers, however
|
|
// causing a 'wrong highlighting' e.g. of `x` in `f(x y: Int) {}`)
|
|
return (.function, [.declaration])
|
|
case values.ref_var_static,
|
|
values.ref_var_class,
|
|
values.ref_var_instance:
|
|
return (.property, [])
|
|
case values.decl_var_local,
|
|
values.decl_var_global:
|
|
return (.variable, [.declaration])
|
|
case values.ref_var_local,
|
|
values.ref_var_global:
|
|
return (.variable, [])
|
|
case values.syntaxtype_comment,
|
|
values.syntaxtype_comment_marker,
|
|
values.syntaxtype_comment_url:
|
|
return (.comment, [])
|
|
case values.syntaxtype_doccomment,
|
|
values.syntaxtype_doccomment_field:
|
|
return (.comment, [.documentation])
|
|
case values.syntaxtype_type_identifier:
|
|
return (.type, [])
|
|
case values.syntaxtype_number:
|
|
return (.number, [])
|
|
case values.syntaxtype_string:
|
|
return (.string, [])
|
|
case values.syntaxtype_identifier:
|
|
return (.identifier, [])
|
|
default:
|
|
let ignoredKinds: Set<sourcekitd_uid_t> = [
|
|
values.syntaxtype_string_interpolation_anchor
|
|
]
|
|
if !ignoredKinds.contains(uid) {
|
|
let name = api.uid_get_string_ptr(uid).map(String.init(cString:))
|
|
logger.error("Unknown token kind: \(name ?? "?", privacy: .public)")
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Range where Bound == Position {
|
|
/// Splits a potentially multi-line range to multiple single-line ranges.
|
|
func splitToSingleLineRanges(in snapshot: DocumentSnapshot) -> [Self] {
|
|
if isEmpty {
|
|
return []
|
|
}
|
|
|
|
if lowerBound.line == upperBound.line {
|
|
return [self]
|
|
}
|
|
|
|
guard let startIndex = snapshot.index(of: lowerBound),
|
|
let endIndex = snapshot.index(of: upperBound)
|
|
else {
|
|
fatalError("Range \(self) reaches outside of the document")
|
|
}
|
|
|
|
let text = snapshot.text[startIndex..<endIndex]
|
|
let lines = text.split(separator: "\n", omittingEmptySubsequences: false)
|
|
|
|
return
|
|
lines
|
|
.enumerated()
|
|
.lazy
|
|
.map { (i, content) in
|
|
let start = Position(
|
|
line: lowerBound.line + i,
|
|
utf16index: i == 0 ? lowerBound.utf16index : 0
|
|
)
|
|
let end = Position(
|
|
line: start.line,
|
|
utf16index: start.utf16index + content.utf16.count
|
|
)
|
|
return start..<end
|
|
}
|
|
.filter { !$0.isEmpty }
|
|
}
|
|
|
|
/// **Public for testing**
|
|
public func _splitToSingleLineRanges(in snapshot: DocumentSnapshot) -> [Self] {
|
|
splitToSingleLineRanges(in: snapshot)
|
|
}
|
|
}
|