Skip to content

Commit 7d63c22

Browse files
committed
fix: address PR review feedback for plugin metadata lookups
1 parent cd9d6ff commit 7d63c22

12 files changed

Lines changed: 107 additions & 58 deletions

TablePro/AppDelegate+ConnectionHandler.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ enum QueuedURLEntry {
1616
case databaseURL(URL)
1717
case sqliteFile(URL)
1818
case duckdbFile(URL)
19+
case genericDatabaseFile(URL, DatabaseType)
1920
}
2021

2122
extension AppDelegate {
@@ -172,6 +173,51 @@ extension AppDelegate {
172173
}
173174
}
174175

176+
// MARK: - Generic Database File Handler
177+
178+
func handleGenericDatabaseFile(_ url: URL, type dbType: DatabaseType) {
179+
guard WindowOpener.shared.openWindow != nil else {
180+
queuedURLEntries.append(.genericDatabaseFile(url, dbType))
181+
scheduleQueuedURLProcessing()
182+
return
183+
}
184+
185+
let filePath = url.path(percentEncoded: false)
186+
let connectionName = url.deletingPathExtension().lastPathComponent
187+
188+
for (sessionId, session) in DatabaseManager.shared.activeSessions {
189+
if session.connection.type == dbType
190+
&& session.connection.database == filePath
191+
&& session.driver != nil {
192+
bringConnectionWindowToFront(sessionId)
193+
return
194+
}
195+
}
196+
197+
let connection = DatabaseConnection(
198+
name: connectionName,
199+
host: "",
200+
port: 0,
201+
database: filePath,
202+
username: "",
203+
type: dbType
204+
)
205+
206+
openNewConnectionWindow(for: connection)
207+
208+
Task { @MainActor in
209+
do {
210+
try await DatabaseManager.shared.connectToSession(connection)
211+
for window in NSApp.windows where self.isWelcomeWindow(window) {
212+
window.close()
213+
}
214+
} catch {
215+
connectionLogger.error("File open failed for '\(filePath, privacy: .public)' (\(dbType.rawValue)): \(error.localizedDescription)")
216+
await self.handleConnectionFailure(error)
217+
}
218+
}
219+
}
220+
175221
// MARK: - Unified Queue
176222

177223
func scheduleQueuedURLProcessing() {
@@ -203,6 +249,7 @@ extension AppDelegate {
203249
case .databaseURL(let url): self.handleDatabaseURL(url)
204250
case .sqliteFile(let url): self.handleSQLiteFile(url)
205251
case .duckdbFile(let url): self.handleDuckDBFile(url)
252+
case .genericDatabaseFile(let url, let dbType): self.handleGenericDatabaseFile(url, type: dbType)
206253
}
207254
}
208255
self.scheduleWelcomeWindowSuppression()

TablePro/AppDelegate+FileOpen.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ extension AppDelegate {
6969
case .duckdb:
7070
self.handleDuckDBFile(url)
7171
default:
72-
break
72+
self.handleGenericDatabaseFile(url, type: dbType)
7373
}
7474
}
7575
self.scheduleWelcomeWindowSuppression()

TablePro/ContentView.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ struct ContentView: View {
9393
AppState.shared.isConnected = true
9494
AppState.shared.safeModeLevel = session.connection.safeModeLevel
9595
AppState.shared.editorLanguage = PluginManager.shared.editorLanguage(for: session.connection.type)
96+
AppState.shared.currentDatabaseType = session.connection.type
9697
AppState.shared.supportsDatabaseSwitching = PluginManager.shared.supportsDatabaseSwitching(
9798
for: session.connection.type)
9899
}
@@ -119,6 +120,7 @@ struct ContentView: View {
119120
AppState.shared.isConnected = false
120121
AppState.shared.safeModeLevel = .silent
121122
AppState.shared.editorLanguage = .sql
123+
AppState.shared.currentDatabaseType = nil
122124
AppState.shared.supportsDatabaseSwitching = true
123125

124126
// Close all native tab windows for this connection and
@@ -150,6 +152,7 @@ struct ContentView: View {
150152
AppState.shared.isConnected = true
151153
AppState.shared.safeModeLevel = newSession.connection.safeModeLevel
152154
AppState.shared.editorLanguage = PluginManager.shared.editorLanguage(for: newSession.connection.type)
155+
AppState.shared.currentDatabaseType = newSession.connection.type
153156
AppState.shared.supportsDatabaseSwitching = PluginManager.shared.supportsDatabaseSwitching(
154157
for: newSession.connection.type)
155158
}
@@ -179,12 +182,14 @@ struct ContentView: View {
179182
AppState.shared.isConnected = true
180183
AppState.shared.safeModeLevel = session.connection.safeModeLevel
181184
AppState.shared.editorLanguage = PluginManager.shared.editorLanguage(for: session.connection.type)
185+
AppState.shared.currentDatabaseType = session.connection.type
182186
AppState.shared.supportsDatabaseSwitching = PluginManager.shared.supportsDatabaseSwitching(
183187
for: session.connection.type)
184188
} else {
185189
AppState.shared.isConnected = false
186190
AppState.shared.safeModeLevel = .silent
187191
AppState.shared.editorLanguage = .sql
192+
AppState.shared.currentDatabaseType = nil
188193
AppState.shared.supportsDatabaseSwitching = true
189194
}
190195
}

TablePro/Core/Plugins/PluginManager.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,18 @@ final class PluginManager {
461461
var allRegisteredFileExtensions: [String: DatabaseType] {
462462
loadPendingPlugins()
463463
var result: [String: DatabaseType] = [:]
464-
for (typeId, plugin) in driverPlugins {
464+
for typeId in driverPlugins.keys.sorted() {
465+
guard let plugin = driverPlugins[typeId] else { continue }
465466
let dbType = DatabaseType(rawValue: typeId)
466467
for ext in Swift.type(of: plugin).fileExtensions {
467-
result[ext.lowercased()] = dbType
468+
let key = ext.lowercased()
469+
if let existing = result[key], existing != dbType {
470+
Self.logger.warning(
471+
"File extension '\(key)' is registered by multiple plugins; keeping '\(existing.rawValue)', ignoring '\(dbType.rawValue)'"
472+
)
473+
continue
474+
}
475+
result[key] = dbType
468476
}
469477
}
470478
return result

TablePro/Extensions/Color+Hex.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
import SwiftUI
77

88
extension Color {
9-
/// Creates a Color from a hex string like "#FF8800" or "FF8800".
109
init(hex: String) {
11-
let hex = hex.trimmingCharacters(in: CharacterSet(charactersIn: "#"))
12-
let scanner = Scanner(string: hex)
13-
var rgbValue: UInt64 = 0
14-
scanner.scanHexInt64(&rgbValue)
10+
let cleaned = hex
11+
.trimmingCharacters(in: .whitespacesAndNewlines)
12+
.trimmingCharacters(in: CharacterSet(charactersIn: "#"))
13+
14+
guard cleaned.count == 6, let rgbValue = UInt64(cleaned, radix: 16) else {
15+
self = .gray
16+
return
17+
}
1518

1619
let red = Double((rgbValue >> 16) & 0xFF) / 255.0
1720
let green = Double((rgbValue >> 8) & 0xFF) / 255.0

TablePro/Extensions/EditorLanguage+TreeSitter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ extension EditorLanguage {
1212
case .sql: return .sql
1313
case .javascript: return .javascript
1414
case .bash: return .bash
15-
case .custom: return .sql
15+
case .custom: return .default
1616
}
1717
}
1818

TablePro/TableProApp.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ final class AppState {
2121
var safeModeLevel: SafeModeLevel = .silent
2222
var isReadOnly: Bool { safeModeLevel.blocksAllWrites }
2323
var editorLanguage: EditorLanguage = .sql
24+
var currentDatabaseType: DatabaseType?
2425
var supportsDatabaseSwitching: Bool = true
2526
var isCurrentTabEditable: Bool = false // True when current tab is an editable table
2627
var hasRowSelection: Bool = false // True when rows are selected in data grid
@@ -194,10 +195,14 @@ struct AppMenuCommands: Commands {
194195
.optionalKeyboardShortcut(shortcut(for: .saveChanges))
195196
.disabled(!appState.isConnected || appState.isReadOnly)
196197

197-
Button(appState.editorLanguage == .javascript ? "Preview MQL"
198-
: appState.editorLanguage == .bash ? "Preview Commands"
199-
: "Preview SQL") {
198+
Button {
200199
actions?.previewSQL()
200+
} label: {
201+
if let dbType = appState.currentDatabaseType {
202+
Text("Preview \(PluginManager.shared.queryLanguageName(for: dbType))")
203+
} else {
204+
Text("Preview SQL")
205+
}
201206
}
202207
.optionalKeyboardShortcut(shortcut(for: .previewSQL))
203208
.disabled(!appState.isConnected)
@@ -233,7 +238,7 @@ struct AppMenuCommands: Commands {
233238
.optionalKeyboardShortcut(shortcut(for: .export))
234239
.disabled(!appState.isConnected)
235240

236-
if appState.editorLanguage == .sql {
241+
if appState.currentDatabaseType.map({ PluginManager.shared.supportsImport(for: $0) }) ?? true {
237242
Button("Import...") {
238243
actions?.importTables()
239244
}

TablePro/Views/Components/SQLReviewPopover.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ struct SQLReviewPopover: View {
100100

101101
private var headerView: some View {
102102
HStack {
103-
Text(String(localized: "\(PluginManager.shared.queryLanguageName(for: databaseType)) Preview"))
103+
Text("\(PluginManager.shared.queryLanguageName(for: databaseType)) Preview")
104104
.font(.system(size: DesignConstants.FontSize.body, weight: .semibold))
105105
if !statements.isEmpty {
106106
Text(

TablePro/Views/Connection/ConnectionFormView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ struct ConnectionFormView: View {
753753
private var filePathPrompt: String {
754754
let extensions = PluginManager.shared.driverPlugin(for: type)
755755
.map { Swift.type(of: $0).fileExtensions } ?? []
756-
let ext = extensions.first ?? type.rawValue.lowercased()
756+
let ext = extensions.first ?? "db"
757757
return "/path/to/database.\(ext)"
758758
}
759759

TablePro/Views/Sidebar/SidebarView.swift

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,9 @@ struct SidebarView: View {
151151
}
152152

153153
private var emptyState: some View {
154-
let entityName = PluginManager.shared.queryLanguageName(for: viewModel.databaseType)
155-
let noItemsLabel = entityName == "MQL" ? "No Collections"
156-
: entityName == "Redis CLI" ? "No Databases"
157-
: "No Tables"
158-
let noItemsDetail = entityName == "MQL" ? "This database has no collections yet."
159-
: entityName == "Redis CLI" ? "All databases are empty."
160-
: "This database has no tables yet."
154+
let entityName = PluginManager.shared.tableEntityName(for: viewModel.databaseType)
155+
let noItemsLabel = String(localized: "No \(entityName)")
156+
let noItemsDetail = String(localized: "This database has no \(entityName.lowercased()) yet.")
161157
return VStack(spacing: 6) {
162158
Image(systemName: "tablecells")
163159
.font(.system(size: 28, weight: .thin))
@@ -177,17 +173,10 @@ struct SidebarView: View {
177173
// MARK: - Table List
178174

179175
private var tableList: some View {
180-
let langName = PluginManager.shared.queryLanguageName(for: viewModel.databaseType)
181-
let entityLabel = langName == "MQL" ? "Collections" : langName == "Redis CLI" ? "Databases" : "Tables"
182-
let noMatchLabel = langName == "MQL" ? "No matching collections"
183-
: langName == "Redis CLI" ? "No matching databases"
184-
: "No matching tables"
185-
let helpLabel = langName == "MQL" ? "Right-click to show all collections"
186-
: langName == "Redis CLI" ? "Right-click to show all databases"
187-
: "Right-click to show all tables"
188-
let showAllLabel = langName == "MQL" ? String(localized: "Show All Collections")
189-
: langName == "Redis CLI" ? String(localized: "Show All Databases")
190-
: String(localized: "Show All Tables")
176+
let entityLabel = PluginManager.shared.tableEntityName(for: viewModel.databaseType)
177+
let noMatchLabel = String(localized: "No matching \(entityLabel.lowercased())")
178+
let helpLabel = String(localized: "Right-click to show all \(entityLabel.lowercased())")
179+
let showAllLabel = String(localized: "Show All \(entityLabel)")
191180
return List(selection: selectedTablesBinding) {
192181
if filteredTables.isEmpty {
193182
ContentUnavailableView(

0 commit comments

Comments
 (0)