forked from github/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCodeBlockHighlighter.swift
More file actions
120 lines (110 loc) · 4.23 KB
/
CodeBlockHighlighter.swift
File metadata and controls
120 lines (110 loc) · 4.23 KB
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
import Combine
import ComposableArchitecture
import DebounceFunction
import Foundation
import MarkdownUI
import Perception
import SharedUIComponents
import SwiftUI
/// Use this instead of the built in ``CodeBlockView`` to highlight code blocks asynchronously,
/// so that the UI doesn't freeze when rendering large code blocks.
struct AsyncCodeBlockView: View {
@Perceptible
class Storage {
static let queue = DispatchQueue(
label: "chat-code-block-highlight",
qos: .userInteractive,
attributes: .concurrent
)
var highlighted: AttributedString?
@PerceptionIgnored var debounceFunction: DebounceFunction<AsyncCodeBlockView>?
@PerceptionIgnored private var highlightTask: Task<Void, Error>?
init() {
debounceFunction = .init(duration: 0.5, block: { [weak self] view in
self?.highlight(for: view)
})
}
func highlight(debounce: Bool, for view: AsyncCodeBlockView) {
if debounce {
Task { await debounceFunction?(view) }
} else {
highlight(for: view)
}
}
func highlight(for view: AsyncCodeBlockView) {
highlightTask?.cancel()
let content = view.content
let language = view.fenceInfo ?? ""
let brightMode = view.colorScheme != .dark
let font = view.font
highlightTask = Task {
let string = await withUnsafeContinuation { continuation in
Self.queue.async {
let content = CodeHighlighting.highlightedCodeBlock(
code: content,
language: language,
scenario: "chat",
brightMode: brightMode,
font: font
)
continuation.resume(returning: AttributedString(content))
}
}
try Task.checkCancellation()
await MainActor.run {
self.highlighted = string
}
}
}
}
let fenceInfo: String?
let content: String
let font: NSFont
@Environment(\.colorScheme) var colorScheme
@State var storage = Storage()
@AppStorage(\.syncChatCodeHighlightTheme) var syncCodeHighlightTheme
@AppStorage(\.codeForegroundColorLight) var codeForegroundColorLight
@AppStorage(\.codeBackgroundColorLight) var codeBackgroundColorLight
@AppStorage(\.codeForegroundColorDark) var codeForegroundColorDark
@AppStorage(\.codeBackgroundColorDark) var codeBackgroundColorDark
init(fenceInfo: String?, content: String, font: NSFont) {
self.fenceInfo = fenceInfo
self.content = content.hasSuffix("\n") ? String(content.dropLast()) : content
self.font = font
}
var body: some View {
WithPerceptionTracking {
Group {
if let highlighted = storage.highlighted {
Text(highlighted)
.frame(maxWidth: .infinity, alignment: .leading)
} else {
Text(content).font(.init(font))
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.frame(maxWidth: .infinity)
.onAppear {
storage.highlight(debounce: false, for: self)
}
.onChange(of: colorScheme) { _ in
storage.highlight(debounce: false, for: self)
}
.onChange(of: syncCodeHighlightTheme) { _ in
storage.highlight(debounce: true, for: self)
}
.onChange(of: codeForegroundColorLight) { _ in
storage.highlight(debounce: true, for: self)
}
.onChange(of: codeBackgroundColorLight) { _ in
storage.highlight(debounce: true, for: self)
}
.onChange(of: codeForegroundColorDark) { _ in
storage.highlight(debounce: true, for: self)
}
.onChange(of: codeBackgroundColorDark) { _ in
storage.highlight(debounce: true, for: self)
}
}
}
}