Skip to content

Commit ebce401

Browse files
committed
Unify implementation of app activation
1 parent 72bc2f2 commit ebce401

File tree

12 files changed

+114
-83
lines changed

12 files changed

+114
-83
lines changed

Core/Sources/Service/GUI/GraphicalUserInterfaceController.swift

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ActiveApplicationMonitor
2+
import AppActivator
23
import AppKit
34
import ChatGPTChatTab
45
import ChatTab
@@ -68,7 +69,8 @@ struct GUI: ReducerProtocol {
6869
#endif
6970
}
7071

71-
@Dependency(\.chatTabPool) var chatTabPool: ChatTabPool
72+
@Dependency(\.chatTabPool) var chatTabPool
73+
@Dependency(\.activateThisApp) var activateThisApp
7274

7375
public enum Debounce: Hashable {
7476
case updateChatTabOrder
@@ -137,6 +139,11 @@ struct GUI: ReducerProtocol {
137139
.chatPanel(.presentChatPanel(forceDetach: forceDetach))
138140
)
139141
)
142+
await send(.suggestionWidget(.updateKeyWindow(.chatPanel)))
143+
144+
if await !NSApplication.shared.isActive {
145+
activateThisApp()
146+
}
140147
}
141148

142149
case .createChatGPTChatTabIfNeeded:
@@ -195,17 +202,8 @@ struct GUI: ReducerProtocol {
195202
}
196203

197204
case .toggleWidgetsHotkeyPressed:
198-
let hasChat = state.chatTabGroup.selectedTabInfo != nil
199-
let hasPromptToCode = state.promptToCodeGroup.activePromptToCode != nil
200-
201205
return .run { send in
202206
await send(.suggestionWidget(.circularWidget(.widgetClicked)))
203-
204-
if hasPromptToCode {
205-
await send(.suggestionWidget(.updateKeyWindow(.sharedPanel)))
206-
} else if hasChat {
207-
await send(.suggestionWidget(.updateKeyWindow(.chatPanel)))
208-
}
209207
}
210208

211209
case let .suggestionWidget(.chatPanel(.chatTab(id, .tabContentUpdated))):
@@ -278,12 +276,8 @@ public final class GraphicalUserInterfaceController {
278276
Task {
279277
let handler = PseudoCommandHandler()
280278
await handler.acceptPromptToCode()
281-
if let app = ActiveApplicationMonitor.shared.previousApp,
282-
app.isXcode,
283-
!promptToCode.isContinuous
284-
{
285-
try await Task.sleep(nanoseconds: 200_000_000)
286-
app.activate()
279+
if !promptToCode.isContinuous {
280+
NSWorkspace.activatePreviousActiveXcode()
287281
}
288282
}
289283
}

Core/Sources/Service/GUI/WidgetDataSource.swift

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import ActiveApplicationMonitor
2+
import AppActivator
3+
import AppKit
24
import ChatService
35
import ComposableArchitecture
46
import Foundation
@@ -39,24 +41,14 @@ extension WidgetDataSource: SuggestionWidgetDataSource {
3941
Task {
4042
let handler = PseudoCommandHandler()
4143
await handler.rejectSuggestions()
42-
if let app = ActiveApplicationMonitor.shared.previousApp,
43-
app.isXcode
44-
{
45-
try await Task.sleep(nanoseconds: 200_000_000)
46-
app.activate()
47-
}
44+
NSWorkspace.activatePreviousActiveXcode()
4845
}
4946
},
5047
onAcceptSuggestionTapped: {
5148
Task {
5249
let handler = PseudoCommandHandler()
5350
await handler.acceptSuggestion()
54-
if let app = ActiveApplicationMonitor.shared.previousApp,
55-
app.isXcode
56-
{
57-
try await Task.sleep(nanoseconds: 200_000_000)
58-
app.activate()
59-
}
51+
NSWorkspace.activatePreviousActiveXcode()
6052
}
6153
}
6254
)

Core/Sources/Service/SuggestionCommandHandler/WindowBaseCommandHandler.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import AppKit
12
import ChatService
23
import Environment
34
import Foundation
@@ -409,7 +410,7 @@ extension WindowBaseCommandHandler {
409410
}() as (String, CursorRange)
410411

411412
let viewStore = Service.shared.guiController.viewStore
412-
413+
413414
let customCommandTemplateProcessor = CustomCommandTemplateProcessor()
414415
let newExtraSystemPrompt = extraSystemPrompt.map(customCommandTemplateProcessor.process)
415416
let newPrompt = prompt.map(customCommandTemplateProcessor.process)

Core/Sources/SuggestionWidget/FeatureReducers/ChatPanelFeature.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ public struct ChatPanelFeature: ReducerProtocol {
7777

7878
@Dependency(\.suggestionWidgetControllerDependency) var suggestionWidgetControllerDependency
7979
@Dependency(\.xcodeInspector) var xcodeInspector
80-
@Dependency(\.activatePreviouslyActiveXcode) var activatePreviouslyActiveXcode
81-
@Dependency(\.activateExtensionService) var activateExtensionService
80+
@Dependency(\.activatePreviousActiveXcode) var activatePreviouslyActiveXcode
81+
@Dependency(\.activateThisApp) var activateExtensionService
8282
@Dependency(\.chatTabBuilderCollection) var chatTabBuilderCollection
8383

8484
public var body: some ReducerProtocol<State, Action> {
@@ -88,7 +88,7 @@ public struct ChatPanelFeature: ReducerProtocol {
8888
state.isPanelDisplayed = false
8989

9090
return .run { _ in
91-
await activatePreviouslyActiveXcode()
91+
activatePreviouslyActiveXcode()
9292
}
9393

9494
case .closeActiveTabClicked:
@@ -119,7 +119,7 @@ public struct ChatPanelFeature: ReducerProtocol {
119119
}
120120
state.isPanelDisplayed = true
121121
return .run { send in
122-
await activateExtensionService()
122+
activateExtensionService()
123123
await send(.focusActiveChatTab)
124124
}
125125

Core/Sources/SuggestionWidget/FeatureReducers/PanelFeature.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public struct PanelFeature: ReducerProtocol {
3737

3838
@Dependency(\.suggestionWidgetControllerDependency) var suggestionWidgetControllerDependency
3939
@Dependency(\.xcodeInspector) var xcodeInspector
40+
@Dependency(\.activateThisApp) var activateThisApp
4041
var windows: WidgetWindows { suggestionWidgetControllerDependency.windows }
4142

4243
public var body: some ReducerProtocol<State, Action> {
@@ -121,9 +122,7 @@ public struct PanelFeature: ReducerProtocol {
121122
await send(.displayPanelContent)
122123

123124
if hasPromptToCode {
124-
// looks like we need a delay.
125-
try await Task.sleep(nanoseconds: 150_000_000)
126-
await NSApplication.shared.activate(ignoringOtherApps: true)
125+
activateThisApp()
127126
await windows.sharedPanelWindow.makeKey()
128127
}
129128
}.animation(.easeInOut(duration: 0.2))

Core/Sources/SuggestionWidget/FeatureReducers/PromptToCodeGroup.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public struct PromptToCodeGroup: ReducerProtocol {
8080
}
8181

8282
@Dependency(\.promptToCodeServiceFactory) var promptToCodeServiceFactory
83+
@Dependency(\.activatePreviousActiveXcode) var activatePreviousActiveXcode
8384

8485
public var body: some ReducerProtocol<State, Action> {
8586
Reduce { state, action in
@@ -143,7 +144,7 @@ public struct PromptToCodeGroup: ReducerProtocol {
143144
case .cancelButtonTapped:
144145
state.promptToCodes.remove(id: id)
145146
return .run { _ in
146-
try await Environment.makeXcodeActive()
147+
activatePreviousActiveXcode()
147148
}
148149
default:
149150
return .none

Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ActiveApplicationMonitor
2+
import AppActivator
23
import AsyncAlgorithms
34
import AXNotificationStream
45
import ComposableArchitecture
@@ -133,6 +134,8 @@ public struct WidgetFeature: ReducerProtocol {
133134
@Dependency(\.activeApplicationMonitor) var activeApplicationMonitor
134135
@Dependency(\.xcodeInspector) var xcodeInspector
135136
@Dependency(\.mainQueue) var mainQueue
137+
@Dependency(\.activateThisApp) var activateThisApp
138+
@Dependency(\.activatePreviousActiveApp) var activatePreviousActiveApp
136139

137140
public enum DebounceKey: Hashable {
138141
case updateWindowOpacity
@@ -163,20 +166,26 @@ public struct WidgetFeature: ReducerProtocol {
163166
state.panelState.suggestionPanelState.isPanelDisplayed = true
164167
state.chatPanelState.isPanelDisplayed = true
165168
}
169+
166170
let isDisplayingContent = state._circularWidgetState.isDisplayingContent
171+
let hasChat = state.chatPanelState.chatTabGroup.selectedTabInfo != nil
172+
let hasPromptToCode = state.panelState.sharedPanelState.content
173+
.promptToCodeGroup.activePromptToCode != nil
174+
167175
return .run { send in
168176
if isDisplayingContent {
177+
if hasPromptToCode {
178+
await send(.updateKeyWindow(.sharedPanel))
179+
} else if hasChat {
180+
await send(.updateKeyWindow(.chatPanel))
181+
}
169182
await send(.chatPanel(.focusActiveChatTab))
170183
}
171-
184+
172185
if isDisplayingContent, !(await NSApplication.shared.isActive) {
173-
try await Task.sleep(nanoseconds: 50_000_000)
174-
await NSApplication.shared.activate(ignoringOtherApps: true)
175-
} else if !isDisplayingContent,
176-
let app = xcodeInspector.previousActiveApplication
177-
{
178-
try await Task.sleep(nanoseconds: 20_000_000)
179-
app.runningApplication.activate()
186+
activateThisApp()
187+
} else if !isDisplayingContent {
188+
activatePreviousActiveApp()
180189
}
181190
}
182191

@@ -599,7 +608,7 @@ public struct WidgetFeature: ReducerProtocol {
599608
return .none
600609

601610
case let .updateKeyWindow(window):
602-
return .run { _ in
611+
return .run { send in
603612
switch window {
604613
case .chatPanel:
605614
await windows.chatPanelWindow.makeKeyAndOrderFront(nil)

Core/Sources/SuggestionWidget/ModuleDependency.swift

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -78,23 +78,6 @@ struct ChatTabBuilderCollectionKey: DependencyKey {
7878
static let liveValue: () -> [ChatTabBuilderCollection] = { [] }
7979
}
8080

81-
struct ActivatePreviouslyActiveXcodeKey: DependencyKey {
82-
static let liveValue = { @MainActor in
83-
@Dependency(\.activeApplicationMonitor) var activeApplicationMonitor
84-
if let app = activeApplicationMonitor.previousApp, app.isXcode {
85-
try? await Task.sleep(nanoseconds: 200_000_000)
86-
app.activate()
87-
}
88-
}
89-
}
90-
91-
struct ActivateExtensionServiceKey: DependencyKey {
92-
static let liveValue = { @MainActor in
93-
try? await Task.sleep(nanoseconds: 150_000_000)
94-
NSApplication.shared.activate(ignoringOtherApps: true)
95-
}
96-
}
97-
9881
public extension DependencyValues {
9982
var suggestionWidgetControllerDependency: SuggestionWidgetControllerDependency {
10083
get { self[SuggestionWidgetControllerDependencyKey.self] }
@@ -122,15 +105,5 @@ extension DependencyValues {
122105
get { self[ActiveApplicationMonitorKey.self] }
123106
set { self[ActiveApplicationMonitorKey.self] = newValue }
124107
}
125-
126-
var activatePreviouslyActiveXcode: () async -> Void {
127-
get { self[ActivatePreviouslyActiveXcodeKey.self] }
128-
set { self[ActivatePreviouslyActiveXcodeKey.self] = newValue }
129-
}
130-
131-
var activateExtensionService: () async -> Void {
132-
get { self[ActivateExtensionServiceKey.self] }
133-
set { self[ActivateExtensionServiceKey.self] = newValue }
134-
}
135108
}
136109

Tool/Package.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ let package = Package(
3939
"ActiveApplicationMonitor",
4040
"AXExtension",
4141
"AXNotificationStream",
42+
"AppActivator",
4243
]
4344
),
4445
],
@@ -111,6 +112,14 @@ let package = Package(
111112
]
112113
),
113114

115+
.target(
116+
name: "AppActivator",
117+
dependencies: [
118+
"XcodeInspector",
119+
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
120+
]
121+
),
122+
114123
.target(name: "ActiveApplicationMonitor"),
115124

116125
.target(name: "USearchIndex", dependencies: [
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import AppKit
2+
import Dependencies
3+
import XcodeInspector
4+
5+
public extension NSWorkspace {
6+
static func activateThisApp(delay: TimeInterval = 0.5) {
7+
Task { @MainActor in
8+
try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
9+
// NSApp.activate may fail.
10+
NSRunningApplication(
11+
processIdentifier: ProcessInfo.processInfo.processIdentifier
12+
)?.activate()
13+
}
14+
}
15+
16+
static func activatePreviousActiveApp(delay: TimeInterval = 0.2) {
17+
Task { @MainActor in
18+
guard let app = XcodeInspector.shared.previousActiveApplication else { return }
19+
try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
20+
app.runningApplication.activate()
21+
}
22+
}
23+
24+
static func activatePreviousActiveXcode(delay: TimeInterval = 0.2) {
25+
Task { @MainActor in
26+
guard let app = XcodeInspector.shared.latestActiveXcode else { return }
27+
try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
28+
app.runningApplication.activate()
29+
}
30+
}
31+
}
32+
33+
struct ActivateThisAppDependencyKey: DependencyKey {
34+
static var liveValue: () -> Void = { NSWorkspace.activateThisApp() }
35+
}
36+
37+
struct ActivatePreviousActiveAppDependencyKey: DependencyKey {
38+
static var liveValue: () -> Void = { NSWorkspace.activatePreviousActiveApp() }
39+
}
40+
41+
struct ActivatePreviousActiveXcodeDependencyKey: DependencyKey {
42+
static var liveValue: () -> Void = { NSWorkspace.activatePreviousActiveXcode() }
43+
}
44+
45+
public extension DependencyValues {
46+
var activateThisApp: () -> Void {
47+
get { self[ActivateThisAppDependencyKey.self] }
48+
set { self[ActivateThisAppDependencyKey.self] = newValue }
49+
}
50+
51+
var activatePreviousActiveApp: () -> Void {
52+
get { self[ActivatePreviousActiveAppDependencyKey.self] }
53+
set { self[ActivatePreviousActiveAppDependencyKey.self] = newValue }
54+
}
55+
56+
var activatePreviousActiveXcode: () -> Void {
57+
get { self[ActivatePreviousActiveXcodeDependencyKey.self] }
58+
set { self[ActivatePreviousActiveXcodeDependencyKey.self] = newValue }
59+
}
60+
}
61+

0 commit comments

Comments
 (0)