diff --git a/CHANGELOG.md b/CHANGELOG.md
index 413265fa2..addb9d6cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Clearing a query with the trash button now also clears its results, and a new Clear Results item on the results right-click menu clears results on their own. (#1256)
+- Inserting SQL from AI Chat opens it in a new query tab instead of appending to the current query. An empty editor is filled in place. (#1257)
### Fixed
diff --git a/TablePro/Views/Main/MainContentCoordinator.swift b/TablePro/Views/Main/MainContentCoordinator.swift
index 8d7ca362c..cc64a0bde 100644
--- a/TablePro/Views/Main/MainContentCoordinator.swift
+++ b/TablePro/Views/Main/MainContentCoordinator.swift
@@ -863,16 +863,15 @@ final class MainContentCoordinator {
}
}
+ var aiInsertReusesSelectedQueryTab: Bool {
+ guard let (tab, _) = tabManager.selectedTabAndIndex, tab.tabType == .query else { return false }
+ return tab.content.query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+ }
+
func insertQueryFromAI(_ query: String) {
- if let (tab, tabIndex) = tabManager.selectedTabAndIndex,
- tab.tabType == .query {
- let existingQuery = tab.content.query
+ if aiInsertReusesSelectedQueryTab, let (_, tabIndex) = tabManager.selectedTabAndIndex {
tabManager.mutate(at: tabIndex) { mutTab in
- if existingQuery.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
- mutTab.content.query = query
- } else {
- mutTab.content.query = existingQuery + "\n\n" + query
- }
+ mutTab.content.query = query
mutTab.hasUserInteraction = true
}
} else if tabManager.tabs.isEmpty {
diff --git a/TableProTests/Views/Main/AIChatInsertQueryTests.swift b/TableProTests/Views/Main/AIChatInsertQueryTests.swift
new file mode 100644
index 000000000..7ad8d680d
--- /dev/null
+++ b/TableProTests/Views/Main/AIChatInsertQueryTests.swift
@@ -0,0 +1,80 @@
+import Foundation
+import TableProPluginKit
+import Testing
+
+@testable import TablePro
+
+@Suite("AIChatInsertQuery")
+struct AIChatInsertQueryTests {
+ @Test("Reuses the selected query tab only when it is empty")
+ @MainActor
+ func reusesSelectedQueryTabWhenEmpty() {
+ let coordinator = Self.makeCoordinator()
+ defer { coordinator.teardown() }
+
+ coordinator.tabManager.addTab(databaseName: "db")
+
+ #expect(coordinator.aiInsertReusesSelectedQueryTab == true)
+ }
+
+ @Test("Does not reuse a query tab that already has content")
+ @MainActor
+ func doesNotReuseQueryTabWithContent() {
+ let coordinator = Self.makeCoordinator()
+ defer { coordinator.teardown() }
+
+ coordinator.tabManager.addTab(initialQuery: "SELECT 1", databaseName: "db")
+
+ #expect(coordinator.aiInsertReusesSelectedQueryTab == false)
+ }
+
+ @Test("Does not reuse a table tab")
+ @MainActor
+ func doesNotReuseTableTab() throws {
+ let coordinator = Self.makeCoordinator()
+ defer { coordinator.teardown() }
+
+ try coordinator.tabManager.addTableTab(tableName: "users", databaseType: .mysql, databaseName: "db")
+
+ #expect(coordinator.aiInsertReusesSelectedQueryTab == false)
+ }
+
+ @Test("Insert fills an empty query tab in place")
+ @MainActor
+ func insertFillsEmptyQueryTabInPlace() {
+ let coordinator = Self.makeCoordinator()
+ defer { coordinator.teardown() }
+
+ coordinator.tabManager.addTab(databaseName: "db")
+
+ coordinator.insertQueryFromAI("SELECT * FROM users")
+
+ #expect(coordinator.tabManager.tabs.count == 1)
+ #expect(coordinator.tabManager.selectedTab?.content.query == "SELECT * FROM users")
+ }
+
+ @Test("Insert with no open tabs creates a query tab with the SQL")
+ @MainActor
+ func insertWithNoTabsCreatesQueryTab() {
+ let coordinator = Self.makeCoordinator()
+ defer { coordinator.teardown() }
+
+ #expect(coordinator.tabManager.tabs.isEmpty)
+
+ coordinator.insertQueryFromAI("SELECT * FROM orders")
+
+ #expect(coordinator.tabManager.tabs.count == 1)
+ #expect(coordinator.tabManager.selectedTab?.tabType == .query)
+ #expect(coordinator.tabManager.selectedTab?.content.query == "SELECT * FROM orders")
+ }
+
+ @MainActor
+ private static func makeCoordinator() -> MainContentCoordinator {
+ MainContentCoordinator(
+ connection: TestFixtures.makeConnection(database: "db"),
+ tabManager: QueryTabManager(),
+ changeManager: DataChangeManager(),
+ toolbarState: ConnectionToolbarState()
+ )
+ }
+}
diff --git a/docs/features/ai-assistant.mdx b/docs/features/ai-assistant.mdx
index 3cef7db94..46a610040 100644
--- a/docs/features/ai-assistant.mdx
+++ b/docs/features/ai-assistant.mdx
@@ -62,7 +62,7 @@ Press `Cmd+Shift+L`, click the inspector toggle and pick **AI Chat**, or use **V
/>
-Type a question and press Return. Code blocks have **Copy** and **Insert to Editor** buttons. Token counts show below each response.
+Type a question and press Return. Code blocks have **Copy** and **Insert** buttons. Insert opens the SQL in a new query tab, or fills the current editor if it's empty. Token counts show below each response.
![]()