Skip to content

Commit 221a034

Browse files
committed
Merge tag '0.27.1' into develop
# Conflicts: # Pro
2 parents ba79efd + 45a78f4 commit 221a034

29 files changed

+430
-183
lines changed

Core/Sources/ChatGPTChatTab/Chat.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ struct Chat: ReducerProtocol {
3232
var history: [ChatMessage] = []
3333
@BindingState var isReceivingMessage = false
3434
var chatMenu = ChatMenu.State()
35+
@BindingState var focusedField: Field?
36+
37+
enum Field: String, Hashable {
38+
case textField
39+
}
3540
}
3641

3742
enum Action: Equatable, BindableAction {
@@ -45,6 +50,7 @@ struct Chat: ReducerProtocol {
4550
case deleteMessageButtonTapped(MessageID)
4651
case resendMessageButtonTapped(MessageID)
4752
case setAsExtraPromptButtonTapped(MessageID)
53+
case focusOnTextField
4854

4955
case observeChatService
5056
case observeHistoryChange
@@ -89,6 +95,7 @@ struct Chat: ReducerProtocol {
8995
await send(.isReceivingMessageChanged)
9096
await send(.systemPromptChanged)
9197
await send(.extraSystemPromptChanged)
98+
await send(.focusOnTextField)
9299
}
93100

94101
case .sendButtonTapped:
@@ -127,6 +134,10 @@ struct Chat: ReducerProtocol {
127134
return .run { _ in
128135
await service.setMessageAsExtraPrompt(id: id)
129136
}
137+
138+
case .focusOnTextField:
139+
state.focusedField = .textField
140+
return .none
130141

131142
case .observeChatService:
132143
return .run { send in

Core/Sources/ChatGPTChatTab/ChatGPTChatTab.swift

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,19 @@ public class ChatGPTChatTab: ChatTab {
116116
public func start() {
117117
chatTabViewStore.send(.updateTitle("Chat"))
118118

119-
service.$systemPrompt.removeDuplicates().sink { _ in
119+
chatTabViewStore.publisher.focusTrigger.removeDuplicates().sink { [weak self] _ in
120+
Task { @MainActor [weak self] in
121+
self?.viewStore.send(.focusOnTextField)
122+
}
123+
}.store(in: &cancellable)
124+
125+
service.$systemPrompt.removeDuplicates().sink { [weak self] _ in
120126
Task { @MainActor [weak self] in
121127
self?.chatTabViewStore.send(.tabContentUpdated)
122128
}
123129
}.store(in: &cancellable)
124130

125-
service.$extraSystemPrompt.removeDuplicates().sink { _ in
131+
service.$extraSystemPrompt.removeDuplicates().sink { [weak self] _ in
126132
Task { @MainActor [weak self] in
127133
self?.chatTabViewStore.send(.tabContentUpdated)
128134
}
@@ -134,12 +140,11 @@ public class ChatGPTChatTab: ChatTab {
134140
}
135141
}.store(in: &cancellable)
136142

137-
viewStore.publisher.removeDuplicates()
138-
.sink { [weak self] _ in
139-
Task { @MainActor [weak self] in
140-
self?.chatTabViewStore.send(.tabContentUpdated)
141-
}
142-
}.store(in: &cancellable)
143+
viewStore.publisher.removeDuplicates().sink { [weak self] _ in
144+
Task { @MainActor [weak self] in
145+
self?.chatTabViewStore.send(.tabContentUpdated)
146+
}
147+
}.store(in: &cancellable)
143148
}
144149
}
145150

Core/Sources/ChatGPTChatTab/ChatPanel.swift

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -498,16 +498,13 @@ struct FunctionMessage: View {
498498

499499
struct ChatPanelInputArea: View {
500500
let chat: StoreOf<Chat>
501-
@FocusState var isInputAreaFocused: Bool
501+
@FocusState var focusedField: Chat.State.Field?
502502

503503
var body: some View {
504504
HStack {
505505
clearButton
506506
textEditor
507507
}
508-
.onAppear {
509-
isInputAreaFocused = true
510-
}
511508
.padding(8)
512509
.background(.ultraThickMaterial)
513510
}
@@ -538,8 +535,11 @@ struct ChatPanelInputArea: View {
538535
@MainActor
539536
var textEditor: some View {
540537
HStack(spacing: 0) {
541-
WithViewStore(chat, removeDuplicates: { $0.typedMessage == $1.typedMessage }) {
542-
viewStore in
538+
WithViewStore(
539+
chat,
540+
removeDuplicates: {
541+
$0.typedMessage == $1.typedMessage && $0.focusedField == $1.focusedField
542+
}) { viewStore in
543543
ZStack(alignment: .center) {
544544
// a hack to support dynamic height of TextEditor
545545
Text(
@@ -560,7 +560,8 @@ struct ChatPanelInputArea: View {
560560
.padding(.top, 1)
561561
.padding(.bottom, -1)
562562
}
563-
.focused($isInputAreaFocused)
563+
.focused($focusedField, equals: .textField)
564+
.bind(viewStore.$focusedField, to: $focusedField)
564565
.padding(8)
565566
.fixedSize(horizontal: false, vertical: true)
566567
}
@@ -595,7 +596,7 @@ struct ChatPanelInputArea: View {
595596
.keyboardShortcut(KeyEquivalent.return, modifiers: [.shift])
596597

597598
Button(action: {
598-
isInputAreaFocused = true
599+
focusedField = .textField
599600
}) {
600601
EmptyView()
601602
}

Core/Sources/ChatService/ChatService.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ public final class ChatService: ObservableObject {
4242
let configuration = UserPreferenceChatGPTConfiguration().overriding()
4343
/// Used by context collector
4444
let extraConfiguration = configuration.overriding()
45+
extraConfiguration.textWindowTerminator = {
46+
guard let last = $0.last else { return false }
47+
return last.isNewline || last.isPunctuation
48+
}
4549
let memory = ContextAwareAutoManagedChatGPTMemory(
4650
configuration: extraConfiguration,
4751
functionProvider: ChatFunctionProvider()

Core/Sources/Service/GUI/GraphicalUserInterfaceController.swift

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import ActiveApplicationMonitor
2+
import AppActivator
23
import AppKit
34
import ChatGPTChatTab
45
import ChatTab
56
import ComposableArchitecture
67
import Dependencies
78
import Environment
89
import Preferences
10+
import SuggestionModel
911
import SuggestionWidget
1012

1113
#if canImport(ProChatTabs)
@@ -54,6 +56,7 @@ struct GUI: ReducerProtocol {
5456
case openChatPanel(forceDetach: Bool)
5557
case createChatGPTChatTabIfNeeded
5658
case sendCustomCommandToActiveChat(CustomCommand)
59+
case toggleWidgetsHotkeyPressed
5760

5861
case suggestionWidget(WidgetFeature.Action)
5962

@@ -66,7 +69,8 @@ struct GUI: ReducerProtocol {
6669
#endif
6770
}
6871

69-
@Dependency(\.chatTabPool) var chatTabPool: ChatTabPool
72+
@Dependency(\.chatTabPool) var chatTabPool
73+
@Dependency(\.activateThisApp) var activateThisApp
7074

7175
public enum Debounce: Hashable {
7276
case updateChatTabOrder
@@ -135,6 +139,9 @@ struct GUI: ReducerProtocol {
135139
.chatPanel(.presentChatPanel(forceDetach: forceDetach))
136140
)
137141
)
142+
await send(.suggestionWidget(.updateKeyWindow(.chatPanel)))
143+
144+
activateThisApp()
138145
}
139146

140147
case .createChatGPTChatTabIfNeeded:
@@ -192,6 +199,11 @@ struct GUI: ReducerProtocol {
192199
}
193200
}
194201

202+
case .toggleWidgetsHotkeyPressed:
203+
return .run { send in
204+
await send(.suggestionWidget(.circularWidget(.widgetClicked)))
205+
}
206+
195207
case let .suggestionWidget(.chatPanel(.chatTab(id, .tabContentUpdated))):
196208
#if canImport(ChatTabPersistent)
197209
// when a tab is updated, persist it.
@@ -262,12 +274,10 @@ public final class GraphicalUserInterfaceController {
262274
Task {
263275
let handler = PseudoCommandHandler()
264276
await handler.acceptPromptToCode()
265-
if let app = ActiveApplicationMonitor.shared.previousApp,
266-
app.isXcode,
267-
!promptToCode.isContinuous
268-
{
269-
try await Task.sleep(nanoseconds: 200_000_000)
270-
app.activate()
277+
if !promptToCode.isContinuous {
278+
NSWorkspace.activatePreviousActiveXcode()
279+
} else {
280+
NSWorkspace.activateThisApp()
271281
}
272282
}
273283
}

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
)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import AppKit
2+
import Combine
3+
import Foundation
4+
import KeyboardShortcuts
5+
import XcodeInspector
6+
7+
extension KeyboardShortcuts.Name {
8+
static let showHideWidget = Self("ShowHideWidget")
9+
}
10+
11+
@MainActor
12+
final class GlobalShortcutManager {
13+
let guiController: GraphicalUserInterfaceController
14+
private var cancellable = Set<AnyCancellable>()
15+
16+
nonisolated init(guiController: GraphicalUserInterfaceController) {
17+
self.guiController = guiController
18+
}
19+
20+
func start() {
21+
KeyboardShortcuts.userDefaults = .shared
22+
setupShortcutIfNeeded()
23+
24+
KeyboardShortcuts.onKeyUp(for: .showHideWidget) { [guiController] in
25+
let isXCodeActive = XcodeInspector.shared.activeXcode != nil
26+
27+
if !isXCodeActive,
28+
!guiController.viewStore.state.suggestionWidgetState.chatPanelState.isPanelDisplayed,
29+
UserDefaults.shared.value(for: \.showHideWidgetShortcutGlobally)
30+
{
31+
guiController.viewStore.send(.openChatPanel(forceDetach: true))
32+
} else {
33+
guiController.viewStore.send(.toggleWidgetsHotkeyPressed)
34+
}
35+
}
36+
37+
XcodeInspector.shared.$activeApplication.sink { app in
38+
if !UserDefaults.shared.value(for: \.showHideWidgetShortcutGlobally) {
39+
let shouldBeEnabled = if let app, app.isXcode || app.isExtensionService {
40+
true
41+
} else {
42+
false
43+
}
44+
if shouldBeEnabled {
45+
self.setupShortcutIfNeeded()
46+
} else {
47+
self.removeShortcutIfNeeded()
48+
}
49+
} else {
50+
self.setupShortcutIfNeeded()
51+
}
52+
}.store(in: &cancellable)
53+
}
54+
55+
func setupShortcutIfNeeded() {
56+
KeyboardShortcuts.enable(.showHideWidget)
57+
}
58+
59+
func removeShortcutIfNeeded() {
60+
KeyboardShortcuts.disable(.showHideWidget)
61+
}
62+
}
63+

Core/Sources/Service/Service.swift

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import Combine
21
import Dependencies
32
import Foundation
4-
import KeyboardShortcuts
53
import Workspace
64
import WorkspaceSuggestionService
75
import XcodeInspector
@@ -15,10 +13,6 @@ import ProService
1513
public static let shared = TheActor()
1614
}
1715

18-
extension KeyboardShortcuts.Name {
19-
static let showHideWidget = Self("ShowHideWidget")
20-
}
21-
2216
/// The running extension service.
2317
public final class Service {
2418
public static let shared = Service()
@@ -69,54 +63,3 @@ public final class Service {
6963
}
7064
}
7165

72-
@MainActor
73-
final class GlobalShortcutManager {
74-
let guiController: GraphicalUserInterfaceController
75-
private var cancellable = Set<AnyCancellable>()
76-
77-
nonisolated init(guiController: GraphicalUserInterfaceController) {
78-
self.guiController = guiController
79-
}
80-
81-
func start() {
82-
KeyboardShortcuts.userDefaults = .shared
83-
setupShortcutIfNeeded()
84-
85-
KeyboardShortcuts.onKeyUp(for: .showHideWidget) { [guiController] in
86-
if XcodeInspector.shared.activeXcode == nil,
87-
!guiController.viewStore.state.suggestionWidgetState.chatPanelState.isPanelDisplayed,
88-
UserDefaults.shared.value(for: \.showHideWidgetShortcutGlobally)
89-
{
90-
guiController.viewStore.send(.openChatPanel(forceDetach: true))
91-
} else {
92-
guiController.viewStore.send(.suggestionWidget(.circularWidget(.widgetClicked)))
93-
}
94-
}
95-
96-
XcodeInspector.shared.$activeApplication.sink { app in
97-
if !UserDefaults.shared.value(for: \.showHideWidgetShortcutGlobally) {
98-
let shouldBeEnabled = if let app, app.isXcode || app.isExtensionService {
99-
true
100-
} else {
101-
false
102-
}
103-
if shouldBeEnabled {
104-
self.setupShortcutIfNeeded()
105-
} else {
106-
self.removeShortcutIfNeeded()
107-
}
108-
} else {
109-
self.setupShortcutIfNeeded()
110-
}
111-
}.store(in: &cancellable)
112-
}
113-
114-
func setupShortcutIfNeeded() {
115-
KeyboardShortcuts.enable(.showHideWidget)
116-
}
117-
118-
func removeShortcutIfNeeded() {
119-
KeyboardShortcuts.disable(.showHideWidget)
120-
}
121-
}
122-

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)

0 commit comments

Comments
 (0)