Skip to content

Commit 5cd1b14

Browse files
committed
Support recovering Codeium chat tab
1 parent d620115 commit 5cd1b14

File tree

9 files changed

+102
-157
lines changed

9 files changed

+102
-157
lines changed

Core/Sources/ChatGPTChatTab/ChatGPTChatTab.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,7 @@ public class ChatGPTChatTab: ChatTab {
7676
return (try? JSONEncoder().encode(state)) ?? Data()
7777
}
7878

79-
public static func restore(
80-
from data: Data,
81-
externalDependency: Void
82-
) async throws -> any ChatTabBuilder {
79+
public static func restore(from data: Data) async throws -> any ChatTabBuilder {
8380
let state = try JSONDecoder().decode(RestorableState.self, from: data)
8481
let builder = Builder(title: "Chat") { @MainActor tab in
8582
tab.service.configuration.overriding = state.configuration
@@ -96,7 +93,7 @@ public class ChatGPTChatTab: ChatTab {
9693
return builder
9794
}
9895

99-
public static func chatBuilders(externalDependency: Void) -> [ChatTabBuilder] {
96+
public static func chatBuilders() -> [ChatTabBuilder] {
10097
let customCommands = UserDefaults.shared.value(for: \.customCommands).compactMap {
10198
command in
10299
if case .customChat = command.feature {

Core/Sources/Service/GUI/ChatTabFactory.swift

Lines changed: 64 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,10 @@ import ProChatTabs
1515
enum ChatTabFactory {
1616
static func chatTabBuilderCollection() -> [ChatTabBuilderCollection] {
1717
#if canImport(ProChatTabs)
18+
_ = lazyLoadDependency
1819
let collection = [
1920
folderIfNeeded(ChatGPTChatTab.chatBuilders(), title: ChatGPTChatTab.name),
20-
folderIfNeeded(
21-
BrowserChatTab.chatBuilders(
22-
externalDependency: externalDependenciesForBrowserChatTab()
23-
),
24-
title: BrowserChatTab.name
25-
),
21+
folderIfNeeded(BrowserChatTab.chatBuilders(), title: BrowserChatTab.name),
2622
folderIfNeeded(TerminalChatTab.chatBuilders(), title: TerminalChatTab.name),
2723
]
2824
#else
@@ -47,83 +43,68 @@ enum ChatTabFactory {
4743

4844
static func chatTabsFromExtensions() -> [ChatTabBuilderCollection] {
4945
let extensions = BuiltinExtensionManager.shared.extensions
50-
let chatBuilders = extensions.flatMap { $0.chatBuilders }
51-
return chatBuilders.compactMap { folderIfNeeded($0.value, title: $0.key) }
46+
let chatTabTypes = extensions.flatMap(\.chatTabTypes)
47+
return chatTabTypes.compactMap { folderIfNeeded($0.chatBuilders(), title: $0.name) }
5248
}
53-
54-
#if canImport(ProChatTabs)
55-
static func externalDependenciesForBrowserChatTab() -> BrowserChatTab.ExternalDependency {
56-
.init(
57-
getEditorContent: {
58-
guard let editor = XcodeInspector.shared.focusedEditor else {
59-
return .init(selectedText: "", language: "", fileContent: "")
60-
}
61-
let content = editor.getContent()
62-
return .init(
63-
selectedText: content.selectedContent,
64-
language: (
65-
XcodeInspector.shared.activeDocumentURL
66-
.map(languageIdentifierFromFileURL) ?? .plaintext
67-
).rawValue,
68-
fileContent: content.content
69-
)
70-
},
71-
handleCustomCommand: { command, prompt in
72-
switch command.feature {
73-
case let .chatWithSelection(extraSystemPrompt, _, useExtraSystemPrompt):
74-
let service = ChatService()
75-
return try await service.processMessage(
76-
systemPrompt: nil,
77-
extraSystemPrompt: (useExtraSystemPrompt ?? false) ? extraSystemPrompt :
78-
nil,
79-
prompt: prompt
80-
)
81-
case let .customChat(systemPrompt, _):
82-
let service = ChatService()
83-
return try await service.processMessage(
84-
systemPrompt: systemPrompt,
85-
extraSystemPrompt: nil,
86-
prompt: prompt
87-
)
88-
case let .singleRoundDialog(
89-
systemPrompt,
90-
overwriteSystemPrompt,
91-
_,
92-
_
93-
):
94-
let service = ChatService()
95-
return try await service.handleSingleRoundDialogCommand(
96-
systemPrompt: systemPrompt,
97-
overwriteSystemPrompt: overwriteSystemPrompt ?? false,
98-
prompt: prompt
99-
)
100-
case let .promptToCode(extraSystemPrompt, instruction, _, _):
101-
let service = OpenAIPromptToCodeService()
102-
103-
let result = try await service.modifyCode(
104-
code: prompt,
105-
requirement: instruction ?? "Modify content.",
106-
source: .init(
107-
language: .plaintext,
108-
documentURL: .init(fileURLWithPath: "/"),
109-
projectRootURL: .init(fileURLWithPath: "/"),
110-
content: prompt,
111-
lines: prompt.breakLines(),
112-
range: .outOfScope
113-
),
114-
isDetached: true,
115-
extraSystemPrompt: extraSystemPrompt,
116-
generateDescriptionRequirement: false
117-
)
118-
var code = ""
119-
for try await (newCode, _) in result {
120-
code = newCode
121-
}
122-
return code
123-
}
124-
}
125-
)
126-
}
127-
#endif
12849
}
12950

51+
#if canImport(ProChatTabs)
52+
let lazyLoadDependency: () = {
53+
BrowserChatTab.externalDependency = .init(
54+
handleCustomCommand: { command, prompt in
55+
switch command.feature {
56+
case let .chatWithSelection(extraSystemPrompt, _, useExtraSystemPrompt):
57+
let service = ChatService()
58+
return try await service.processMessage(
59+
systemPrompt: nil,
60+
extraSystemPrompt: (useExtraSystemPrompt ?? false) ? extraSystemPrompt :
61+
nil,
62+
prompt: prompt
63+
)
64+
case let .customChat(systemPrompt, _):
65+
let service = ChatService()
66+
return try await service.processMessage(
67+
systemPrompt: systemPrompt,
68+
extraSystemPrompt: nil,
69+
prompt: prompt
70+
)
71+
case let .singleRoundDialog(
72+
systemPrompt,
73+
overwriteSystemPrompt,
74+
_,
75+
_
76+
):
77+
let service = ChatService()
78+
return try await service.handleSingleRoundDialogCommand(
79+
systemPrompt: systemPrompt,
80+
overwriteSystemPrompt: overwriteSystemPrompt ?? false,
81+
prompt: prompt
82+
)
83+
case let .promptToCode(extraSystemPrompt, instruction, _, _):
84+
let service = OpenAIPromptToCodeService()
85+
86+
let result = try await service.modifyCode(
87+
code: prompt,
88+
requirement: instruction ?? "Modify content.",
89+
source: .init(
90+
language: .plaintext,
91+
documentURL: .init(fileURLWithPath: "/"),
92+
projectRootURL: .init(fileURLWithPath: "/"),
93+
content: prompt,
94+
lines: prompt.breakLines(),
95+
range: .outOfScope
96+
),
97+
isDetached: true,
98+
extraSystemPrompt: extraSystemPrompt,
99+
generateDescriptionRequirement: false
100+
)
101+
var code = ""
102+
for try await (newCode, _) in result {
103+
code = newCode
104+
}
105+
return code
106+
}
107+
}
108+
)
109+
}()
110+
#endif

Core/Sources/Service/GUI/GraphicalUserInterfaceController.swift

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ActiveApplicationMonitor
22
import AppActivator
33
import AppKit
4+
import BuiltinExtension
45
import ChatGPTChatTab
56
import ChatTab
67
import ComposableArchitecture
@@ -405,38 +406,25 @@ extension ChatTabPool {
405406
) async -> (any ChatTab, ChatTabInfo)? {
406407
switch data.name {
407408
case ChatGPTChatTab.name:
408-
guard let builder = try? await ChatGPTChatTab.restore(
409-
from: data.data,
410-
externalDependency: ()
411-
) else { break }
412-
return await createTab(id: data.id, from: builder)
413-
case EmptyChatTab.name:
414-
guard let builder = try? await EmptyChatTab.restore(
415-
from: data.data,
416-
externalDependency: ()
417-
) else { break }
409+
guard let builder = try? await ChatGPTChatTab.restore(from: data.data) else { break }
418410
return await createTab(id: data.id, from: builder)
419411
case BrowserChatTab.name:
420-
guard let builder = try? BrowserChatTab.restore(
421-
from: data.data,
422-
externalDependency: ChatTabFactory.externalDependenciesForBrowserChatTab()
423-
) else { break }
412+
guard let builder = try? BrowserChatTab.restore(from: data.data) else { break }
424413
return await createTab(id: data.id, from: builder)
425414
case TerminalChatTab.name:
426-
guard let builder = try? await TerminalChatTab.restore(
427-
from: data.data,
428-
externalDependency: ()
429-
) else { break }
415+
guard let builder = try? await TerminalChatTab.restore(from: data.data) else { break }
430416
return await createTab(id: data.id, from: builder)
431417
default:
432-
break
418+
let chatTabTypes = BuiltinExtensionManager.shared.extensions.flatMap(\.chatTabTypes)
419+
for type in chatTabTypes {
420+
if type.name == data.name {
421+
guard let builder = try? await type.restore(from: data.data) else { break }
422+
return await createTab(id: data.id, from: builder)
423+
}
424+
}
433425
}
434426

435-
guard let builder = try? await EmptyChatTab.restore(
436-
from: data.data, externalDependency: ()
437-
) else {
438-
return nil
439-
}
427+
guard let builder = try? await EmptyChatTab.restore(from: data.data) else { return nil }
440428
return await createTab(id: data.id, from: builder)
441429
}
442430
#endif

Core/Sources/Service/SuggestionCommandHandler/PseudoCommandHandler.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,7 @@ struct PseudoCommandHandler {
362362
match(tab.url) else { return false }
363363
return true
364364
},
365-
kind: .init(BrowserChatTab.urlChatBuilder(
366-
url: url,
367-
externalDependency: ChatTabFactory
368-
.externalDependenciesForBrowserChatTab()
369-
))
365+
kind: .init(BrowserChatTab.urlChatBuilder(url: url))
370366
)).finish()
371367
store.send(.openChatPanel(forceDetach: forceDetach))
372368
}

Pro

Submodule Pro updated from 228405c to 9790545

Tool/Sources/BuiltinExtension/BuiltinExtension.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public protocol BuiltinExtension: CopilotForXcodeExtensionCapability {
88
var suggestionServiceId: BuiltInSuggestionFeatureProvider { get }
99

1010
/// All chat builders provided by this extension.
11-
var chatBuilders: [String: [ChatTabBuilder]] { get }
11+
var chatTabTypes: [any ChatTab.Type] { get }
1212

1313
/// It's usually called when the app is about to quit,
1414
/// you should clean up all the resources here.
@@ -18,6 +18,6 @@ public protocol BuiltinExtension: CopilotForXcodeExtensionCapability {
1818
// MARK: - Default Implementation
1919

2020
public extension BuiltinExtension {
21-
var chatBuilders: [String: [ChatTabBuilder]] { [:] }
21+
var chatTabTypes: [any ChatTab.Type] { [] }
2222
}
2323

Tool/Sources/ChatTab/ChatTab.swift

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ public typealias ChatTab = BaseChatTab & ChatTabType
2020

2121
/// Defines a bunch of things a chat tab should implement.
2222
public protocol ChatTabType {
23-
/// The type of the external dependency required by this chat tab.
24-
associatedtype ExternalDependency
2523
/// Build the view for this chat tab.
2624
@ViewBuilder
2725
func buildView() -> any View
@@ -38,14 +36,11 @@ public protocol ChatTabType {
3836
static var name: String { get }
3937
/// Available builders for this chat tab.
4038
/// It's used to generate a list of tab types for user to create.
41-
static func chatBuilders(externalDependency: ExternalDependency) -> [ChatTabBuilder]
39+
static func chatBuilders() -> [ChatTabBuilder]
4240
/// Restorable state
4341
func restorableState() async -> Data
4442
/// Restore state
45-
static func restore(
46-
from data: Data,
47-
externalDependency: ExternalDependency
48-
) async throws -> any ChatTabBuilder
43+
static func restore(from data: Data) async throws -> any ChatTabBuilder
4944
/// Whenever the body or menu is accessed, this method will be called.
5045
/// It will be called only once so long as you don't call it yourself.
5146
/// It will be called from MainActor.
@@ -66,7 +61,7 @@ open class BaseChatTab {
6661
public var title: String = ""
6762
/// The store for chat tab info. You should only access it after `start` is called.
6863
public let chatTabStore: StoreOf<ChatTabItem>
69-
64+
7065
private var didStart = false
7166
private let storeObserver = NSObject()
7267

@@ -109,7 +104,7 @@ open class BaseChatTab {
109104
EmptyView().id(id)
110105
}
111106
}
112-
107+
113108
/// The icon for this chat tab.
114109
@ViewBuilder
115110
public var icon: some View {
@@ -120,7 +115,7 @@ open class BaseChatTab {
120115
EmptyView().id(id)
121116
}
122117
}
123-
118+
124119
/// The tab item for this chat tab.
125120
@ViewBuilder
126121
public var menu: some View {
@@ -171,14 +166,6 @@ public extension ChatTabType {
171166
var name: String { Self.name }
172167
}
173168

174-
public extension ChatTabType where ExternalDependency == Void {
175-
/// Available builders for this chat tab.
176-
/// It's used to generate a list of tab types for user to create.
177-
static func chatBuilders() -> [ChatTabBuilder] {
178-
chatBuilders(externalDependency: ())
179-
}
180-
}
181-
182169
/// A chat tab that does nothing.
183170
public class EmptyChatTab: ChatTab {
184171
public static var name: String { "Empty" }
@@ -190,7 +177,7 @@ public class EmptyChatTab: ChatTab {
190177
}
191178
}
192179

193-
public static func chatBuilders(externalDependency: Void) -> [ChatTabBuilder] {
180+
public static func chatBuilders() -> [ChatTabBuilder] {
194181
[Builder(title: "Empty")]
195182
}
196183

@@ -204,11 +191,11 @@ public class EmptyChatTab: ChatTab {
204191
public func buildTabItem() -> any View {
205192
Text("Empty-\(id)")
206193
}
207-
194+
208195
public func buildIcon() -> any View {
209196
Image(systemName: "square")
210197
}
211-
198+
212199
public func buildMenu() -> any View {
213200
Text("Empty-\(id)")
214201
}
@@ -217,10 +204,7 @@ public class EmptyChatTab: ChatTab {
217204
return Data()
218205
}
219206

220-
public static func restore(
221-
from data: Data,
222-
externalDependency: Void
223-
) async throws -> any ChatTabBuilder {
207+
public static func restore(from data: Data) async throws -> any ChatTabBuilder {
224208
return Builder(title: "Empty")
225209
}
226210

0 commit comments

Comments
 (0)