Skip to content

Commit c5f8f10

Browse files
committed
Move theme syncronization to Core
1 parent d80d931 commit c5f8f10

File tree

12 files changed

+882
-14
lines changed

12 files changed

+882
-14
lines changed

Core/Package.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ let package = Package(
102102
// https://github.com/sindresorhus/KeyboardShortcuts
103103
.package(url: "https://github.com/intitni/KeyboardShortcuts", branch: "main"),
104104
.package(url: "https://github.com/intitni/CGEventOverride", from: "1.2.1"),
105+
.package(url: "https://github.com/intitni/Highlightr", branch: "master"),
105106
].pro,
106107
targets: [
107108
// MARK: - Main
@@ -129,6 +130,7 @@ let package = Package(
129130
"ChatGPTChatTab",
130131
"PlusFeatureFlag",
131132
"KeyBindingManager",
133+
"XcodeThemeController",
132134
.product(name: "XPCShared", package: "Tool"),
133135
.product(name: "SuggestionProvider", package: "Tool"),
134136
.product(name: "Workspace", package: "Tool"),
@@ -397,6 +399,18 @@ let package = Package(
397399
name: "KeyBindingManagerTests",
398400
dependencies: ["KeyBindingManager"]
399401
),
402+
403+
// MARK: Theming
404+
405+
.target(
406+
name: "XcodeThemeController",
407+
dependencies: [
408+
.product(name: "Preferences", package: "Tool"),
409+
.product(name: "AppMonitoring", package: "Tool"),
410+
.product(name: "Highlightr", package: "Highlightr"),
411+
]
412+
),
413+
400414
]
401415
)
402416

Core/Sources/HostApp/FeatureSettings/Chat/ChatSettingsGeneralSectionView.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,12 +235,8 @@ struct ChatSettingsGeneralSectionView: View {
235235
Text("Wrap text in code block")
236236
}
237237

238-
#if canImport(ProHostApp)
239-
240238
CodeHighlightThemePicker(scenario: .chat)
241239

242-
#endif
243-
244240
Toggle(isOn: $settings.disableFloatOnTopWhenTheChatPanelIsDetached) {
245241
Text("Disable always-on-top when the chat panel is detached")
246242
}

Core/Sources/HostApp/FeatureSettings/PromptToCodeSettingsView.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,7 @@ struct PromptToCodeSettingsView: View {
9595
Text("Wrap code")
9696
}
9797

98-
#if canImport(ProHostApp)
99-
10098
CodeHighlightThemePicker(scenario: .promptToCode)
101-
102-
#endif
10399

104100
FontPicker(font: $settings.font) {
105101
Text("Font")

Core/Sources/HostApp/FeatureSettings/Suggestion/SuggestionSettingsGeneralSectionView.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,8 @@ struct SuggestionSettingsGeneralSectionView: View {
245245
Text("Hide common preceding spaces")
246246
}
247247

248-
#if canImport(ProHostApp)
249-
250248
CodeHighlightThemePicker(scenario: .suggestion)
251249

252-
#endif
253-
254250
FontPicker(font: $settings.font) {
255251
Text("Font")
256252
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import Foundation
2+
import Preferences
3+
import SwiftUI
4+
5+
public struct CodeHighlightThemePicker: View {
6+
public enum Scenario {
7+
case suggestion
8+
case promptToCode
9+
case chat
10+
}
11+
12+
let scenario: Scenario
13+
14+
public init(scenario: Scenario) {
15+
self.scenario = scenario
16+
}
17+
18+
public var body: some View {
19+
switch scenario {
20+
case .suggestion:
21+
SuggestionThemePicker()
22+
case .promptToCode:
23+
PromptToCodeThemePicker()
24+
case .chat:
25+
ChatThemePicker()
26+
}
27+
}
28+
29+
struct SuggestionThemePicker: View {
30+
@AppStorage(\.syncSuggestionHighlightTheme) var sync: Bool
31+
var body: some View {
32+
SyncToggle(sync: $sync)
33+
}
34+
}
35+
36+
struct PromptToCodeThemePicker: View {
37+
@AppStorage(\.syncPromptToCodeHighlightTheme) var sync: Bool
38+
var body: some View {
39+
SyncToggle(sync: $sync)
40+
}
41+
}
42+
43+
struct ChatThemePicker: View {
44+
@AppStorage(\.syncChatCodeHighlightTheme) var sync: Bool
45+
var body: some View {
46+
SyncToggle(sync: $sync)
47+
}
48+
}
49+
50+
struct SyncToggle: View {
51+
@Binding var sync: Bool
52+
53+
var body: some View {
54+
VStack(alignment: .leading) {
55+
Toggle(isOn: $sync) {
56+
Text("Sync color scheme with Xcode")
57+
}
58+
59+
Text("To refresh the theme, you must activate the extension service app once.")
60+
.font(.footnote)
61+
.foregroundColor(.secondary)
62+
}
63+
}
64+
}
65+
}
66+
67+
#Preview {
68+
@State var sync = false
69+
return CodeHighlightThemePicker.SyncToggle(sync: $sync)
70+
}
71+

Core/Sources/Service/Service.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import Combine
44
import Dependencies
55
import Foundation
66
import GitHubCopilotService
7+
import KeyBindingManager
78
import Logger
89
import SuggestionService
910
import Toast
1011
import Workspace
1112
import WorkspaceSuggestionService
1213
import XcodeInspector
14+
import XcodeThemeController
1315
import XPCShared
14-
import KeyBindingManager
1516
#if canImport(ProService)
1617
import ProService
1718
#endif
@@ -33,6 +34,7 @@ public final class Service {
3334
public let scheduledCleaner: ScheduledCleaner
3435
let globalShortcutManager: GlobalShortcutManager
3536
let keyBindingManager: KeyBindingManager
37+
let xcodeThemeController: XcodeThemeController = .init()
3638

3739
#if canImport(ProService)
3840
let proService: ProService
@@ -85,6 +87,7 @@ public final class Service {
8587
scheduledCleaner.start()
8688
realtimeSuggestionController.start()
8789
guiController.start()
90+
xcodeThemeController.start()
8891
#if canImport(ProService)
8992
proService.start()
9093
#endif
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import Foundation
2+
3+
func buildHighlightJSTheme(_ theme: XcodeTheme) -> String {
4+
/// The source value is an `r g b a` string, for example: `0.5 0.5 0.2 1`
5+
6+
return """
7+
.hljs {
8+
display: block;
9+
overflow-x: auto;
10+
padding: 0.5em;
11+
background: \(theme.backgroundColor.hexString);
12+
color: \(theme.plainTextColor.hexString);
13+
}
14+
.xml .hljs-meta {
15+
color: \(theme.marksColor.hexString);
16+
}
17+
.hljs-comment,
18+
.hljs-quote {
19+
color: \(theme.commentColor.hexString);
20+
}
21+
.hljs-tag,
22+
.hljs-keyword,
23+
.hljs-selector-tag,
24+
.hljs-literal,
25+
.hljs-name {
26+
color: \(theme.keywordsColor.hexString);
27+
}
28+
.hljs-attribute {
29+
color: \(theme.attributesColor.hexString);
30+
}
31+
.hljs-variable,
32+
.hljs-template-variable {
33+
color: \(theme.otherPropertiesAndGlobalsColor.hexString);
34+
}
35+
.hljs-code,
36+
.hljs-string,
37+
.hljs-meta-string {
38+
color: \(theme.stringsColor.hexString);
39+
}
40+
.hljs-regexp {
41+
color: \(theme.regexLiteralsColor.hexString);
42+
}
43+
.hljs-link {
44+
color: \(theme.urlsColor.hexString);
45+
}
46+
.hljs-title {
47+
color: \(theme.headingColor.hexString);
48+
}
49+
.hljs-symbol,
50+
.hljs-bullet {
51+
color: \(theme.attributesColor.hexString);
52+
}
53+
.hljs-number {
54+
color: \(theme.numbersColor.hexString);
55+
}
56+
.hljs-section {
57+
color: \(theme.marksColor.hexString);
58+
}
59+
.hljs-meta {
60+
color: \(theme.keywordsColor.hexString);
61+
}
62+
.hljs-type,
63+
.hljs-built_in,
64+
.hljs-builtin-name {
65+
color: \(theme.otherTypeNamesColor.hexString);
66+
}
67+
.hljs-class .hljs-title,
68+
.hljs-title .class_ {
69+
color: \(theme.typeDeclarationsColor.hexString);
70+
}
71+
.hljs-function .hljs-title,
72+
.hljs-title .function_ {
73+
color: \(theme.otherDeclarationsColor.hexString);
74+
}
75+
.hljs-params {
76+
color: \(theme.otherDeclarationsColor.hexString);
77+
}
78+
.hljs-attr {
79+
color: \(theme.attributesColor.hexString);
80+
}
81+
.hljs-subst {
82+
color: \(theme.plainTextColor.hexString);
83+
}
84+
.hljs-formula {
85+
background-color: \(theme.selectionColor.hexString);
86+
font-style: italic;
87+
}
88+
.hljs-addition {
89+
background-color: #baeeba;
90+
}
91+
.hljs-deletion {
92+
background-color: #ffc8bd;
93+
}
94+
.hljs-selector-id,
95+
.hljs-selector-class {
96+
color: \(theme.plainTextColor.hexString);
97+
}
98+
.hljs-doctag,
99+
.hljs-strong {
100+
font-weight: bold;
101+
}
102+
.hljs-emphasis {
103+
font-style: italic;
104+
}
105+
"""
106+
}
107+
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import Foundation
2+
import Highlightr
3+
import Preferences
4+
5+
public class HighlightrThemeManager: ThemeManager {
6+
let defaultManager: ThemeManager
7+
8+
weak var controller: XcodeThemeController?
9+
10+
public init(defaultManager: ThemeManager, controller: XcodeThemeController) {
11+
self.defaultManager = defaultManager
12+
self.controller = controller
13+
}
14+
15+
public func theme(for name: String) -> Theme? {
16+
let syncSuggestionTheme = UserDefaults.shared.value(for: \.syncSuggestionHighlightTheme)
17+
let syncPromptToCodeTheme = UserDefaults.shared.value(for: \.syncPromptToCodeHighlightTheme)
18+
let syncChatTheme = UserDefaults.shared.value(for: \.syncChatCodeHighlightTheme)
19+
20+
lazy var defaultLight = Theme(themeString: defaultLightTheme)
21+
lazy var defaultDark = Theme(themeString: defaultDarkTheme)
22+
23+
switch name {
24+
case "suggestion-light":
25+
guard syncSuggestionTheme, let theme = theme(lightMode: true) else {
26+
return defaultLight
27+
}
28+
return theme
29+
case "suggestion-dark":
30+
guard syncSuggestionTheme, let theme = theme(lightMode: false) else {
31+
return defaultDark
32+
}
33+
return theme
34+
case "promptToCode-light":
35+
guard syncPromptToCodeTheme, let theme = theme(lightMode: true) else {
36+
return defaultLight
37+
}
38+
return theme
39+
case "promptToCode-dark":
40+
guard syncPromptToCodeTheme, let theme = theme(lightMode: false) else {
41+
return defaultDark
42+
}
43+
return theme
44+
case "chat-light":
45+
guard syncChatTheme, let theme = theme(lightMode: true) else {
46+
return defaultLight
47+
}
48+
return theme
49+
case "chat-dark":
50+
guard syncChatTheme, let theme = theme(lightMode: false) else {
51+
return defaultDark
52+
}
53+
return theme
54+
case "light":
55+
return defaultLight
56+
case "dark":
57+
return defaultDark
58+
default:
59+
return defaultLight
60+
}
61+
}
62+
63+
func theme(lightMode: Bool) -> Theme? {
64+
guard let controller else { return nil }
65+
guard let directories = controller.createSupportDirectoriesIfNeeded() else { return nil }
66+
67+
let themeURL: URL = if lightMode {
68+
directories.themeDirectory.appendingPathComponent("highlightjs-light")
69+
} else {
70+
directories.themeDirectory.appendingPathComponent("highlightjs-dark")
71+
}
72+
73+
if let themeString = try? String(contentsOf: themeURL) {
74+
return Theme(themeString: themeString)
75+
}
76+
77+
controller.syncXcodeThemeIfNeeded()
78+
79+
if let themeString = try? String(contentsOf: themeURL) {
80+
return Theme(themeString: themeString)
81+
}
82+
83+
return nil
84+
}
85+
}
86+
87+
let defaultLightTheme = ".hljs{display:block;overflow-x:auto;padding:0.5em;background:#FFFFFFFF;color:#000000D8}.xml .hljs-meta{color:#495460FF}.hljs-comment,.hljs-quote{color:#5D6B79FF}.hljs-tag,.hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-name{color:#9A2393FF}.hljs-attribute{color:#805E03FF}.hljs-variable,.hljs-template-variable{color:#6B36A9FF}.hljs-code,.hljs-string,.hljs-meta-string{color:#C31A15FF}.hljs-regexp{color:#000000D8}.hljs-link{color:#0E0EFFFF}.hljs-title{color:#000000FF}.hljs-symbol,.hljs-bullet{color:#805E03FF}.hljs-number{color:#1C00CFFF}.hljs-section{color:#495460FF}.hljs-meta{color:#9A2393FF}.hljs-type,.hljs-built_in,.hljs-builtin-name{color:#3900A0FF}.hljs-class .hljs-title,.hljs-title .class_{color:#0B4F79FF}.hljs-function .hljs-title,.hljs-title .function_{color:#0E67A0FF}.hljs-params{color:#0E67A0FF}.hljs-attr{color:#805E03FF}.hljs-subst{color:#000000D8}.hljs-formula{background-color:#A3CCFEFF;font-style:italic}.hljs-addition{background-color:#baeeba}.hljs-deletion{background-color:#ffc8bd}.hljs-selector-id,.hljs-selector-class{color:#000000D8}.hljs-doctag,.hljs-strong{font-weight:bold}.hljs-emphasis{font-style:italic}"
88+
89+
let defaultDarkTheme = ".hljs{display:block;overflow-x:auto;padding:0.5em;background:#1F1F23FF;color:#FFFFFFD8}.xml .hljs-meta{color:#91A1B1FF}.hljs-comment,.hljs-quote{color:#6B7985FF}.hljs-tag,.hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-name{color:#FC5FA2FF}.hljs-attribute{color:#BF8554FF}.hljs-variable,.hljs-template-variable{color:#A166E5FF}.hljs-code,.hljs-string,.hljs-meta-string{color:#FC695DFF}.hljs-regexp{color:#FFFFFFD8}.hljs-link{color:#5482FEFF}.hljs-title{color:#FFFFFFFF}.hljs-symbol,.hljs-bullet{color:#BF8554FF}.hljs-number{color:#CFBF69FF}.hljs-section{color:#91A1B1FF}.hljs-meta{color:#FC5FA2FF}.hljs-type,.hljs-built_in,.hljs-builtin-name{color:#D0A7FEFF}.hljs-class .hljs-title,.hljs-title .class_{color:#5CD7FEFF}.hljs-function .hljs-title,.hljs-title .function_{color:#41A1BFFF}.hljs-params{color:#41A1BFFF}.hljs-attr{color:#BF8554FF}.hljs-subst{color:#FFFFFFD8}.hljs-formula{background-color:#505A6FFF;font-style:italic}.hljs-addition{background-color:#baeeba}.hljs-deletion{background-color:#ffc8bd}.hljs-selector-id,.hljs-selector-class{color:#FFFFFFD8}.hljs-doctag,.hljs-strong{font-weight:bold}.hljs-emphasis{font-style:italic}"

0 commit comments

Comments
 (0)