@@ -92,8 +92,29 @@ public final class SuggestionWidgetController {
9292 return it
9393 } ( )
9494
95+ private lazy var chatWindow = {
96+ let it = CanBecomeKeyWindow (
97+ contentRect: . zero,
98+ styleMask: [ . borderless, . resizable] ,
99+ backing: . buffered,
100+ defer: false
101+ )
102+ it. isReleasedWhenClosed = false
103+ it. isOpaque = false
104+ it. backgroundColor = . clear
105+ it. level = . floating
106+ it. hasShadow = true
107+ it. contentView = NSHostingView (
108+ rootView: ChatWindowView ( viewModel: chatWindowViewModel)
109+ )
110+ it. setIsVisible ( true )
111+ it. canBecomeKeyChecker = { true }
112+ return it
113+ } ( )
114+
95115 let widgetViewModel = WidgetViewModel ( )
96116 let suggestionPanelViewModel = SuggestionPanelViewModel ( )
117+ let chatWindowViewModel = ChatWindowViewModel ( )
97118
98119 private var presentationModeChangeObserver = UserDefaultsObserver ( )
99120 private var colorSchemeChangeObserver = UserDefaultsObserver ( )
@@ -102,7 +123,7 @@ public final class SuggestionWidgetController {
102123 private var sourceEditorMonitorTask : Task < Void , Error > ?
103124 private var currentFileURL : URL ?
104125 private var colorScheme : ColorScheme = . light
105-
126+
106127 public var onOpenChatClicked : ( ) -> Void = { }
107128 public var onCustomCommandClicked : ( CustomCommand ) -> Void = { _ in }
108129 public var dataSource : SuggestionWidgetDataSource ?
@@ -198,30 +219,32 @@ public final class SuggestionWidgetController {
198219 )
199220 }
200221
201- Task { @MainActor in
202- var switchTask : Task < Void , Error > ?
203- suggestionPanelViewModel. requestApplicationPolicyUpdate = { viewModel in
204- #warning("""
205- TODO: There should be a better way for that
206- Currently, we have to make the app an accessory so that we can type thin gs in the chat mode.
207- But in other modes, we want to keep it prohibited so the helper app won't take over the focus.
208- """ )
209- switch (viewModel.activeTab, viewModel.content) {
210- case (.chat, _), (.suggestion, .promptToCode):
211- guard NSApp.activationPolicy() != .accessory else { return }
212- switchTask?.cancel()
213- NSApp.setActivationPolicy(.accessory)
214- case (.suggestion, _):
215- guard NSApp.activationPolicy() != .prohibited else { return }
216- switchTask?.cancel()
217- switchTask = Task {
218- try await Environment.makeXcodeActive()
219- try Task.checkCancellation()
220- NSApp.setActivationPolicy(.prohibited)
221- }
222- }
223- }
224- }
222+ // Task { @MainActor in
223+ // var switchTask: Task<Void, Error>?
224+ // suggestionPanelViewModel.requestApplicationPolicyUpdate = { viewModel in
225+ // #warning("""
226+ // TODO: There should be a better way for that
227+ // Currently, we have to make the app an accessory so that we can type things in the
228+ // chat mode.
229+ // But in other modes, we want to keep it prohibited so the helper app won't take
230+ // over the focus.
231+ // """)
232+ // switch (viewModel.activeTab, viewModel.content) {
233+ // case (.chat, _), (.suggestion, .promptToCode):
234+ // guard NSApp.activationPolicy() != .accessory else { return }
235+ // switchTask?.cancel()
236+ // NSApp.setActivationPolicy(.accessory)
237+ // case (.suggestion, _):
238+ // guard NSApp.activationPolicy() != .prohibited else { return }
239+ // switchTask?.cancel()
240+ // switchTask = Task {
241+ // try await Environment.makeXcodeActive()
242+ // try Task.checkCancellation()
243+ // NSApp.setActivationPolicy(.prohibited)
244+ // }
245+ // }
246+ // }
247+ // }
225248 }
226249}
227250
@@ -259,9 +282,14 @@ public extension SuggestionWidgetController {
259282 widgetViewModel. isProcessing = false
260283 Task {
261284 if let chat = await dataSource? . chatForFile ( at: fileURL) {
285+ chatWindowViewModel. chat = chat
262286 suggestionPanelViewModel. chat = chat
263287 suggestionPanelViewModel. isPanelDisplayed = true
264288
289+ if UserDefaults . shared. value ( for: \. chatPanelInASeparateWindow) {
290+ self . updateWindowLocation ( )
291+ }
292+
265293 Task { @MainActor in
266294 // looks like we need a delay.
267295 try await Task . sleep ( nanoseconds: 150_000_000 )
@@ -277,14 +305,14 @@ public extension SuggestionWidgetController {
277305 await updateContentForActiveEditor ( fileURL: fileURL)
278306 }
279307 }
280-
308+
281309 func presentPromptToCode( fileURL: URL ) {
282310 widgetViewModel. isProcessing = false
283311 Task {
284312 if let provider = await dataSource? . promptToCodeForFile ( at: fileURL) {
285313 suggestionPanelViewModel. content = . promptToCode( provider)
286314 suggestionPanelViewModel. isPanelDisplayed = true
287-
315+
288316 Task { @MainActor in
289317 // looks like we need a delay.
290318 try await Task . sleep ( nanoseconds: 150_000_000 )
@@ -293,7 +321,7 @@ public extension SuggestionWidgetController {
293321 }
294322 }
295323 }
296-
324+
297325 func discardPromptToCode( fileURL: URL ) {
298326 widgetViewModel. isProcessing = false
299327 Task {
@@ -427,6 +455,14 @@ extension SuggestionWidgetController {
427455 hide ( )
428456 return
429457 }
458+
459+ let detachChat = UserDefaults . shared. value ( for: \. chatPanelInASeparateWindow)
460+
461+ if detachChat {
462+ chatWindow. alphaValue = chatWindowViewModel. chat != nil ? 1 : 0
463+ } else {
464+ chatWindow. alphaValue = 0
465+ }
430466
431467 if let xcode = ActiveApplicationMonitor . activeXcode {
432468 let application = AXUIElementCreateApplication ( xcode. processIdentifier)
@@ -461,6 +497,10 @@ extension SuggestionWidgetController {
461497 tabWindow. setFrame ( result. tabFrame, display: false , animate: animated)
462498 suggestionPanelViewModel. alignTopToAnchor = result. alignPanelTopToAnchor
463499 }
500+
501+ if chatWindow. alphaValue == 0 {
502+ chatWindow. setFrame ( panelWindow. frame, display: false , animate: false )
503+ }
464504
465505 if panelWindow. alphaValue != 1 {
466506 panelWindow. alphaValue = 1
@@ -484,6 +524,7 @@ extension SuggestionWidgetController {
484524 return try ? await Environment . fetchCurrentFileURL ( )
485525 } ( ) else {
486526 suggestionPanelViewModel. content = nil
527+ chatWindowViewModel. chat = nil
487528 suggestionPanelViewModel. chat = nil
488529 return
489530 }
@@ -492,10 +533,14 @@ extension SuggestionWidgetController {
492533 if suggestionPanelViewModel. chat? . id != chat. id {
493534 suggestionPanelViewModel. chat = chat
494535 }
536+ if chatWindowViewModel. chat? . id != chat. id {
537+ chatWindowViewModel. chat = chat
538+ }
495539 } else {
496540 suggestionPanelViewModel. chat = nil
541+ chatWindowViewModel. chat = nil
497542 }
498-
543+
499544 if let provider = await dataSource? . promptToCodeForFile ( at: fileURL) {
500545 suggestionPanelViewModel. content = . promptToCode( provider)
501546 } else if let suggestion = await dataSource? . suggestionForFile ( at: fileURL) {
0 commit comments