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.