Skip to content

Commit 20662cd

Browse files
authored
Merge pull request swiftlang#107 from Trzyipolkostkicukru/color-literal
Implement color presentation
2 parents a304c54 + c5264a6 commit 20662cd

11 files changed

+454
-28
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 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+
/// The color presentation request is sent from the client to the server to obtain
14+
/// a list of presentations for a color value at a given location. Clients can
15+
/// use the result to modify a color reference, or show in a color picker
16+
/// and let users pick one of the presentations
17+
///
18+
/// - Parameters:
19+
/// - textDocument: The document to request presentations for.
20+
/// - color: The color information to request presentations for.
21+
/// - range: The range where the color would be inserted. Serves as a context.
22+
///
23+
/// - Returns: A list of color presentations for the given document.
24+
public struct ColorPresentationRequest: TextDocumentRequest, Hashable {
25+
public static let method: String = "textDocument/colorPresentation"
26+
public typealias Response = [ColorPresentation]?
27+
28+
/// The document to request presentations for.
29+
public var textDocument: TextDocumentIdentifier
30+
31+
/// The color information to request presentations for.
32+
public var color: Color
33+
34+
/// The range where the color would be inserted. Serves as a context.
35+
public var range: PositionRange
36+
37+
public init(
38+
textDocument: TextDocumentIdentifier,
39+
color: Color,
40+
range: Range<Position>)
41+
{
42+
self.textDocument = textDocument
43+
self.color = color
44+
self.range = PositionRange(range)
45+
}
46+
}
47+
48+
public struct ColorPresentation: ResponseType, Hashable {
49+
/// The label of this color presentation. It will be shown on the color
50+
/// picker header. By default this is also the text that is inserted when
51+
/// selecting this color presentation.
52+
public var label: String
53+
54+
/// An edit which is applied to a document when selecting this
55+
/// presentation for the color. When `falsy` the label is used.
56+
public var textEdit: TextEdit?
57+
58+
/// An optional array of additional text edits that are applied when
59+
/// selecting this color presentation. Edits must not overlap with
60+
/// the main edit nor with themselves.
61+
public var additionalTextEdits: [TextEdit]?
62+
63+
public init(label: String, textEdit: TextEdit?, additionalTextEdits: [TextEdit]?) {
64+
self.label = label
65+
self.textEdit = textEdit
66+
self.additionalTextEdits = additionalTextEdits
67+
}
68+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 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+
/// The document color request is sent from the client to the server to list
14+
/// all color references found in a given text document. Along with the range,
15+
/// a color value in RGB is returned.
16+
/// Clients can use the result to decorate color references in an editor.
17+
///
18+
/// - Parameters:
19+
/// - textDocument: The document to search for color references.
20+
///
21+
/// - Returns: A list of color references for the given document.
22+
public struct DocumentColorRequest: TextDocumentRequest, Hashable {
23+
public static let method: String = "textDocument/documentColor"
24+
public typealias Response = [ColorInformation]?
25+
26+
/// The document in which to search for color references.
27+
public var textDocument: TextDocumentIdentifier
28+
29+
public init(textDocument: TextDocumentIdentifier) {
30+
self.textDocument = textDocument
31+
}
32+
}
33+
34+
public struct ColorInformation: ResponseType, Hashable {
35+
/// The range in the document where this color appears.
36+
public var range: PositionRange
37+
38+
/// The actual color value for this color range.
39+
public var color: Color
40+
41+
public init(range: Range<Position>, color: Color) {
42+
self.range = PositionRange(range)
43+
self.color = color
44+
}
45+
}
46+
47+
/// Represents a color in RGBA space.
48+
public struct Color: Hashable, Codable {
49+
/// The red component of this color in the range [0-1].
50+
public var red: Double
51+
/// The green component of this color in the range [0-1].
52+
public var green: Double
53+
/// The blue component of this color in the range [0-1].
54+
public var blue: Double
55+
/// The alpha component of this color in the range [0-1].
56+
public var alpha: Double
57+
58+
public init(red: Double, green: Double, blue: Double, alpha: Double) {
59+
self.red = red
60+
self.green = green
61+
self.blue = blue
62+
self.alpha = alpha
63+
}
64+
}

Sources/LanguageServerProtocol/Messages.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public let builtinRequests: [_RequestType.Type] = [
3131
DocumentOnTypeFormatting.self,
3232
FoldingRangeRequest.self,
3333
DocumentSymbolRequest.self,
34+
DocumentColorRequest.self,
35+
ColorPresentationRequest.self,
3436

3537
// MARK: LSP Extension Requests
3638

Sources/LanguageServerProtocol/ServerCapabilities.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public struct ServerCapabilities: Codable, Hashable {
4545
/// Whether the server provides "textDocument/documentSymbol"
4646
public var documentSymbolProvider: Bool?
4747

48+
/// Whether the server provides "textDocument/documentColor" and "textDocument/colorPresentation".
49+
public var colorProvider: Bool?
50+
4851
// TODO: fill-in the rest.
4952

5053
public init(
@@ -58,7 +61,8 @@ public struct ServerCapabilities: Codable, Hashable {
5861
documentRangeFormattingProvider: Bool? = nil,
5962
documentOnTypeFormattingProvider: DocumentOnTypeFormattingOptions? = nil,
6063
foldingRangeProvider: Bool? = nil,
61-
documentSymbolProvider: Bool? = nil
64+
documentSymbolProvider: Bool? = nil,
65+
colorProvider: Bool? = nil
6266
)
6367
{
6468
self.textDocumentSync = textDocumentSync
@@ -72,6 +76,7 @@ public struct ServerCapabilities: Codable, Hashable {
7276
self.documentOnTypeFormattingProvider = documentOnTypeFormattingProvider
7377
self.foldingRangeProvider = foldingRangeProvider
7478
self.documentSymbolProvider = documentSymbolProvider
79+
self.colorProvider = colorProvider
7580
}
7681

7782
public init(from decoder: Decoder) throws {
@@ -81,6 +86,7 @@ public struct ServerCapabilities: Codable, Hashable {
8186
self.definitionProvider = try container.decodeIfPresent(Bool.self, forKey: .definitionProvider)
8287
self.foldingRangeProvider = try container.decodeIfPresent(Bool.self, forKey: .foldingRangeProvider)
8388
self.documentSymbolProvider = try container.decodeIfPresent(Bool.self, forKey: .documentSymbolProvider)
89+
self.colorProvider = try container.decodeIfPresent(Bool.self, forKey: .colorProvider)
8490

8591
if let textDocumentSync = try? container.decode(TextDocumentSyncOptions.self, forKey: .textDocumentSync) {
8692
self.textDocumentSync = textDocumentSync

Sources/SourceKit/SourceKitServer.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public final class SourceKitServer: LanguageServer {
7777
registerWorkspaceRequest(SourceKitServer.foldingRange)
7878
registerWorkspaceRequest(SourceKitServer.symbolInfo)
7979
registerWorkspaceRequest(SourceKitServer.documentSymbol)
80+
registerWorkspaceRequest(SourceKitServer.documentColor)
81+
registerWorkspaceRequest(SourceKitServer.colorPresentation)
8082
}
8183

8284
func registerWorkspaceRequest<R>(
@@ -260,7 +262,8 @@ extension SourceKitServer {
260262
referencesProvider: true,
261263
documentHighlightProvider: true,
262264
foldingRangeProvider: true,
263-
documentSymbolProvider: true
265+
documentSymbolProvider: true,
266+
colorProvider: true
264267
)))
265268
}
266269

@@ -346,6 +349,14 @@ extension SourceKitServer {
346349
toolchainTextDocumentRequest(req, workspace: workspace, fallback: nil)
347350
}
348351

352+
func documentColor(_ req: Request<DocumentColorRequest>, workspace: Workspace) {
353+
toolchainTextDocumentRequest(req, workspace: workspace, fallback: nil)
354+
}
355+
356+
func colorPresentation(_ req: Request<ColorPresentationRequest>, workspace: Workspace) {
357+
toolchainTextDocumentRequest(req, workspace: workspace, fallback: nil)
358+
}
359+
349360
func definition(_ req: Request<DefinitionRequest>, workspace: Workspace) {
350361
// FIXME: sending yourself a request isn't very convenient
351362

Sources/SourceKit/sourcekitd/SwiftLanguageServer.swift

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ public final class SwiftLanguageServer: LanguageServer {
6464
_register(SwiftLanguageServer.foldingRange)
6565
_register(SwiftLanguageServer.symbolInfo)
6666
_register(SwiftLanguageServer.documentSymbol)
67+
_register(SwiftLanguageServer.documentColor)
68+
_register(SwiftLanguageServer.colorPresentation)
6769
}
6870

6971
func getDiagnostic(_ diag: SKResponseDictionary, for snapshot: DocumentSnapshot) -> Diagnostic? {
@@ -193,7 +195,8 @@ extension SwiftLanguageServer {
193195
referencesProvider: nil,
194196
documentHighlightProvider: true,
195197
foldingRangeProvider: true,
196-
documentSymbolProvider: true
198+
documentSymbolProvider: true,
199+
colorProvider: true
197200
)))
198201
}
199202

@@ -553,6 +556,107 @@ extension SwiftLanguageServer {
553556
return
554557
}
555558

559+
func documentColor(_ req: Request<DocumentColorRequest>) {
560+
guard let snapshot = documentManager.latestSnapshot(req.params.textDocument.url) else {
561+
log("failed to find snapshot for url \(req.params.textDocument.url)")
562+
req.reply(nil)
563+
return
564+
}
565+
566+
let skreq = SKRequestDictionary(sourcekitd: sourcekitd)
567+
skreq[keys.request] = requests.editor_open
568+
skreq[keys.name] = "DocumentColor:" + snapshot.document.url.path
569+
skreq[keys.sourcetext] = snapshot.text
570+
skreq[keys.syntactic_only] = 1
571+
572+
let handle = sourcekitd.send(skreq) { [weak self] result in
573+
guard let self = self else { return }
574+
guard let dict = result.success else {
575+
req.reply(.failure(result.failure!))
576+
return
577+
}
578+
579+
guard let results: SKResponseArray = dict[self.keys.substructure] else {
580+
return req.reply([])
581+
}
582+
583+
func colorInformation(dict: SKResponseDictionary) -> ColorInformation? {
584+
guard let kind: sourcekitd_uid_t = dict[self.keys.kind],
585+
kind == self.values.expr_object_literal,
586+
let name: String = dict[self.keys.name],
587+
name == "colorLiteral",
588+
let offset: Int = dict[self.keys.offset],
589+
let start: Position = snapshot.positionOf(utf8Offset: offset),
590+
let length: Int = dict[self.keys.length],
591+
let end: Position = snapshot.positionOf(utf8Offset: offset + length),
592+
let substructure: SKResponseArray = dict[self.keys.substructure] else {
593+
return nil
594+
}
595+
var red, green, blue, alpha: Double?
596+
substructure.forEach{ (i: Int, value: SKResponseDictionary) in
597+
guard let name: String = value[self.keys.name],
598+
let bodyoffset: Int = value[self.keys.bodyoffset],
599+
let bodylength: Int = value[self.keys.bodylength] else {
600+
return true
601+
}
602+
let view = snapshot.text.utf8
603+
let bodyStart = view.index(view.startIndex, offsetBy: bodyoffset)
604+
let bodyEnd = view.index(view.startIndex, offsetBy: bodyoffset+bodylength)
605+
let value = String(view[bodyStart..<bodyEnd]).flatMap(Double.init)
606+
switch name {
607+
case "red":
608+
red = value
609+
case "green":
610+
green = value
611+
case "blue":
612+
blue = value
613+
case "alpha":
614+
alpha = value
615+
default:
616+
break
617+
}
618+
return true
619+
}
620+
if let red = red,
621+
let green = green,
622+
let blue = blue,
623+
let alpha = alpha {
624+
let color = Color(red: red, green: green, blue: blue, alpha: alpha)
625+
return ColorInformation(range: start..<end, color: color)
626+
} else {
627+
return nil
628+
}
629+
}
630+
631+
func colorInformation(array: SKResponseArray) -> [ColorInformation] {
632+
var result: [ColorInformation] = []
633+
array.forEach { (i: Int, value: SKResponseDictionary) in
634+
if let documentSymbol = colorInformation(dict: value) {
635+
result.append(documentSymbol)
636+
} else if let substructure: SKResponseArray = value[self.keys.substructure] {
637+
result += colorInformation(array: substructure)
638+
}
639+
return true
640+
}
641+
return result
642+
}
643+
644+
req.reply(colorInformation(array: results))
645+
}
646+
// FIXME: cancellation
647+
_ = handle
648+
}
649+
650+
func colorPresentation(_ req: Request<ColorPresentationRequest>) {
651+
let color = req.params.color
652+
// Empty string as a label breaks VSCode color picker
653+
let label = "Color Literal"
654+
let newText = "#colorLiteral(red: \(color.red), green: \(color.green), blue: \(color.blue), alpha: \(color.alpha))"
655+
let textEdit = TextEdit(range: req.params.range.asRange, newText: newText)
656+
let presentation = ColorPresentation(label: label, textEdit: textEdit, additionalTextEdits: nil)
657+
req.reply([presentation])
658+
}
659+
556660
func documentSymbolHighlight(_ req: Request<DocumentHighlightRequest>) {
557661

558662
guard let snapshot = documentManager.latestSnapshot(req.params.textDocument.url) else {

Sources/SourceKit/sourcekitd/SwiftSourceKitFramework.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ struct sourcekitd_values {
359359
let syntaxtype_comment_url: sourcekitd_uid_t
360360
let syntaxtype_doccomment: sourcekitd_uid_t
361361
let syntaxtype_doccomment_field: sourcekitd_uid_t
362+
let expr_object_literal: sourcekitd_uid_t
362363

363364
let kind_keyword: sourcekitd_uid_t
364365

@@ -448,6 +449,7 @@ struct sourcekitd_values {
448449
syntaxtype_comment_url = api.uid_get_from_cstr("source.lang.swift.syntaxtype.comment.url")!
449450
syntaxtype_doccomment = api.uid_get_from_cstr("source.lang.swift.syntaxtype.doccomment")!
450451
syntaxtype_doccomment_field = api.uid_get_from_cstr("source.lang.swift.syntaxtype.doccomment.field")!
452+
expr_object_literal = api.uid_get_from_cstr("source.lang.swift.expr.object_literal")!
451453

452454
kind_keyword = api.uid_get_from_cstr("source.lang.swift.keyword")!
453455
}

0 commit comments

Comments
 (0)