forked from intitni/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCodeiumChatTab.swift
More file actions
150 lines (125 loc) · 4.18 KB
/
CodeiumChatTab.swift
File metadata and controls
150 lines (125 loc) · 4.18 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
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
import AppKit
import ChatTab
import Combine
import ComposableArchitecture
import Logger
import Preferences
import SwiftUI
import WebKit
import XcodeInspector
public class CodeiumChatTab: ChatTab {
public static var name: String { "Codeium Chat" }
struct RestorableState: Codable {}
public struct EditorContent {
public var selectedText: String
public var language: String
public var fileContent: String
public init(selectedText: String, language: String, fileContent: String) {
self.selectedText = selectedText
self.language = language
self.fileContent = fileContent
}
public static var empty: EditorContent {
.init(selectedText: "", language: "", fileContent: "")
}
}
struct Builder: ChatTabBuilder {
var title: String
var buildable: Bool { true }
var afterBuild: (CodeiumChatTab) async -> Void = { _ in }
func build(store: StoreOf<ChatTabItem>) async -> (any ChatTab)? {
let tab = await CodeiumChatTab(chatTabStore: store)
await Task { @MainActor in
_ = tab.store.send(.loadCurrentWorkspace)
}.value
await afterBuild(tab)
return tab
}
}
let store: StoreOf<CodeiumChatBrowser>
let webView: WKWebView
let webViewDelegate: WKWebViewDelegate
var cancellable = Set<AnyCancellable>()
private var observer = NSObject()
@MainActor
public init(chatTabStore: StoreOf<ChatTabItem>) {
let webView = CodeiumWebView(getEditorContent: {
guard let content = await XcodeInspector.shared.getFocusedEditorContent()
else { return .empty }
return .init(
selectedText: content.selectedContent,
language: content.language.rawValue,
fileContent: content.editorContent?.content ?? ""
)
})
self.webView = webView
store = .init(
initialState: .init(),
reducer: { CodeiumChatBrowser(webView: webView) }
)
webViewDelegate = .init(store: store)
super.init(store: chatTabStore)
webView.navigationDelegate = webViewDelegate
webView.uiDelegate = webViewDelegate
webView.store = store
Task {
await CodeiumServiceLifeKeeper.shared.add(self)
}
}
public func start() {
observer = .init()
cancellable = []
chatTabStore.send(.updateTitle("Codeium Chat"))
store.send(.initialize)
do {
var previousURL: URL?
observer.observe { [weak self] in
guard let self else { return }
if store.url != previousURL {
previousURL = store.url
Task { @MainActor in
self.chatTabStore.send(.tabContentUpdated)
}
}
}
}
observer.observe { [weak self] in
guard let self, !store.title.isEmpty else { return }
let title = store.title
Task { @MainActor in
self.chatTabStore.send(.updateTitle(title))
}
}
}
public func buildView() -> any View {
BrowserView(store: store, webView: webView)
}
public func buildTabItem() -> any View {
CodeiumChatTabItem(store: store)
}
public func buildIcon() -> any View {
Image(systemName: "message")
}
public func buildMenu() -> any View {
EmptyView()
}
@MainActor
public func restorableState() -> Data {
let state = store.withState { _ in
RestorableState()
}
return (try? JSONEncoder().encode(state)) ?? Data()
}
public static func restore(from data: Data) throws -> any ChatTabBuilder {
let builder = Builder(title: "") { @MainActor chatTab in
chatTab.store.send(.loadCurrentWorkspace)
}
return builder
}
public static func chatBuilders() -> [ChatTabBuilder] {
[Builder(title: "Codeium Chat")]
}
public static func defaultChatBuilder() -> ChatTabBuilder {
Builder(title: "Codeium Chat")
}
}