diff --git a/Core/Sources/SuggestionWidget/SuggestionPanelContent/ChatPanel.swift b/Core/Sources/SuggestionWidget/SuggestionPanelContent/ChatPanel.swift index ad68a818..60f1ac6a 100644 --- a/Core/Sources/SuggestionWidget/SuggestionPanelContent/ChatPanel.swift +++ b/Core/Sources/SuggestionWidget/SuggestionPanelContent/ChatPanel.swift @@ -55,7 +55,7 @@ struct ChatPanelMessages: View { } } } - + var body: some View { ScrollView { vstack { @@ -77,6 +77,7 @@ struct ChatPanelMessages: View { .buttonStyle(.plain) .xcodeStyleFrame() .matchedGeometryEffect(id: "input", in: inputAreaNamespace) + .scaleEffect(x: -1, y: 1, anchor: .center) } if chat.history.isEmpty { @@ -89,8 +90,9 @@ struct ChatPanelMessages: View { ) .xcodeStyleFrame() .rotationEffect(Angle(degrees: 180)) + .scaleEffect(x: -1, y: 1, anchor: .center) } - + ForEach(chat.history.reversed(), id: \.id) { message in let text = message.text.isEmpty && !message.isUser ? "..." : message .text @@ -112,11 +114,12 @@ struct ChatPanelMessages: View { ) .xcodeStyleFrame() .rotationEffect(Angle(degrees: 180)) - + .scaleEffect(x: -1, y: 1, anchor: .center) } } } .rotationEffect(Angle(degrees: 180)) + .scaleEffect(x: -1, y: 1, anchor: .center) } } @@ -124,6 +127,7 @@ struct ChatPanelInputArea: View { @ObservedObject var chat: ChatRoom var inputAreaNamespace: Namespace.ID @Binding var typedMessage: String + @FocusState var isInputAreaFocused: Bool var body: some View { HStack { @@ -131,12 +135,18 @@ struct ChatPanelInputArea: View { Button(action: { chat.clear() }) { - Image(systemName: "eraser.line.dashed.fill") - .padding(8) - .background( - .regularMaterial, - in: RoundedRectangle(cornerRadius: 8, style: .continuous) - ) + Group { + if #available(macOS 13.0, *) { + Image(systemName: "eraser.line.dashed.fill") + } else { + Image(systemName: "trash.fill") + } + } + .padding(8) + .background( + .regularMaterial, + in: RoundedRectangle(cornerRadius: 8, style: .continuous) + ) } .buttonStyle(.plain) .xcodeStyleFrame() @@ -145,9 +155,13 @@ struct ChatPanelInputArea: View { if #available(macOS 13.0, *) { TextField("Type a message", text: $typedMessage, axis: .vertical) } else { - TextField("Type a message", text: $typedMessage) + TextEditor(text: $typedMessage) + .frame(height: 42, alignment: .leading) + .font(.body) + .background(Color.clear) } } + .focused($isInputAreaFocused) .lineLimit(3) .multilineTextAlignment(.leading) .textFieldStyle(.plain) @@ -179,6 +193,9 @@ struct ChatPanelInputArea: View { .buttonStyle(.plain) .xcodeStyleFrame() } + .onAppear { + isInputAreaFocused = true + } } } @@ -246,7 +263,7 @@ struct ChatPanel_InputMultilineText_Preview: PreviewProvider { history: ChatPanel_Preview.history, isReceivingMessage: false ), - typedMessage: "Hello\nWorld" + typedMessage: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce turpis dolor, malesuada quis fringilla sit amet, placerat at nunc. Suspendisse orci tortor, tempor nec blandit a, malesuada vel tellus. Nunc sed leo ligula. Ut at ligula eget turpis pharetra tristique. Integer luctus leo non elit rhoncus fermentum." ) .padding(8) .background(Color.contentBackground) diff --git a/Core/Sources/SuggestionWidget/SuggestionWidgetController.swift b/Core/Sources/SuggestionWidget/SuggestionWidgetController.swift index e2bcfeee..4cadafb8 100644 --- a/Core/Sources/SuggestionWidget/SuggestionWidgetController.swift +++ b/Core/Sources/SuggestionWidget/SuggestionWidgetController.swift @@ -79,7 +79,7 @@ public final class SuggestionWidgetController { ) it.setIsVisible(true) it.canBecomeKeyChecker = { [suggestionPanelViewModel] in - if case .chat = suggestionPanelViewModel.activeTab { return false } + if case .chat = suggestionPanelViewModel.activeTab { return true } return false } return it @@ -219,19 +219,26 @@ public final class SuggestionWidgetController { } Task { @MainActor in - suggestionPanelViewModel.onActiveTabChanged = { [weak self] activeTab in + var switchTask: Task? + suggestionPanelViewModel.onActiveTabChanged = { activeTab in #warning(""" TODO: There should be a better way for that Currently, we have to make the app an accessory so that we can type things in the chat mode. But in other modes, we want to keep it prohibited so the helper app won't take over the focus. """) - if case .chat = activeTab, NSApp.activationPolicy() != .accessory { - NSApp.setActivationPolicy(.accessory) - } else if NSApp.activationPolicy() != .prohibited { - Task { + switch activeTab { + case .suggestion: + guard NSApp.activationPolicy() != .prohibited else { return } + switchTask?.cancel() + switchTask = Task { try await Environment.makeXcodeActive() + try Task.checkCancellation() NSApp.setActivationPolicy(.prohibited) } + case .chat: + guard NSApp.activationPolicy() != .accessory else { return } + switchTask?.cancel() + NSApp.setActivationPolicy(.accessory) } } } @@ -292,6 +299,11 @@ public extension SuggestionWidgetController { func presentChatRoom(_ chatRoom: ChatRoom, fileURL: URL) { if fileURL == currentFileURL || currentFileURL == nil { suggestionPanelViewModel.chat = chatRoom + Task { @MainActor in + // looks like we need a delay. + try await Task.sleep(nanoseconds: 100_000_000) + NSApplication.shared.activate(ignoringOtherApps: true) + } } widgetViewModel.isProcessing = false chatForFiles[fileURL] = chatRoom @@ -335,8 +347,11 @@ extension SuggestionWidgetController { observeEditorChangeIfNeeded(app) guard let fileURL = try? await Environment.fetchCurrentFileURL() else { - suggestionPanelViewModel.content = nil - suggestionPanelViewModel.chat = nil + // if it's switching to a ui component that is not a text area. + if ActiveApplicationMonitor.activeApplication?.isXcode ?? false { + suggestionPanelViewModel.content = nil + suggestionPanelViewModel.chat = nil + } continue } guard fileURL != currentFileURL else { continue } @@ -373,12 +388,14 @@ extension SuggestionWidgetController { scroll ) { guard let self else { return } + guard ActiveApplicationMonitor.activeXcode != nil else { return } try Task.checkCancellation() self.updateWindowLocation(animated: false) } } else { for await _ in merge(selectionRangeChange, scroll) { guard let self else { return } + guard ActiveApplicationMonitor.activeXcode != nil else { return } try Task.checkCancellation() let mode = UserDefaults.shared.value(for: \.suggestionWidgetPositionMode) if mode != .alignToTextCursor { break } diff --git a/Core/Sources/SuggestionWidget/TabView.swift b/Core/Sources/SuggestionWidget/TabView.swift index 03e6ba87..1e757488 100644 --- a/Core/Sources/SuggestionWidget/TabView.swift +++ b/Core/Sources/SuggestionWidget/TabView.swift @@ -38,6 +38,7 @@ struct TabView: View { } .opacity(panelViewModel.isPanelDisplayed ? 1 : 0) .preferredColorScheme(panelViewModel.colorScheme) + .frame(maxWidth: Style.widgetWidth, maxHeight: Style.widgetHeight) } }