diff --git a/CHANGELOG.md b/CHANGELOG.md index 87fe1d61e..f148a8ea5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - BigQuery datasets can be switched from the toolbar, the Cmd+K switcher, and the File menu, including creating and dropping datasets. (#509) +- `.psql` and `.pgsql` files now open in the SQL editor like `.sql`: Finder double-click, the open and save panels, and linked SQL folders all accept them. (#1641) ### Changed diff --git a/TablePro/Core/Services/Infrastructure/SQLFileService.swift b/TablePro/Core/Services/Infrastructure/SQLFileService.swift index 3580e4c90..837c444d3 100644 --- a/TablePro/Core/Services/Infrastructure/SQLFileService.swift +++ b/TablePro/Core/Services/Infrastructure/SQLFileService.swift @@ -13,6 +13,13 @@ import UniformTypeIdentifiers enum SQLFileService { private static let logger = Logger(subsystem: "com.TablePro", category: "SQLFileService") + static let supportedExtensions: Set = ["sql", "psql", "pgsql"] + + private static var allowedContentTypes: [UTType] { + let types = Set(supportedExtensions.compactMap { UTType(filenameExtension: $0) }) + return types.isEmpty ? [.plainText] : Array(types) + } + /// Reads a SQL file from disk. static func readFile(url: URL) async throws -> String { try await Task.detached { @@ -34,7 +41,7 @@ enum SQLFileService { @MainActor static func showOpenPanel() async -> [URL]? { let panel = NSOpenPanel() - panel.allowedContentTypes = [UTType(filenameExtension: "sql") ?? .plainText] + panel.allowedContentTypes = allowedContentTypes panel.allowsMultipleSelection = true panel.message = String(localized: "Select SQL files to open") let response = await panel.begin() @@ -46,7 +53,7 @@ enum SQLFileService { @MainActor static func showSavePanel(suggestedName: String = "query.sql") async -> URL? { let panel = NSSavePanel() - panel.allowedContentTypes = [UTType(filenameExtension: "sql") ?? .plainText] + panel.allowedContentTypes = allowedContentTypes panel.canCreateDirectories = true panel.nameFieldStringValue = suggestedName panel.message = String(localized: "Save SQL file") diff --git a/TablePro/Core/Services/Infrastructure/URLClassifier.swift b/TablePro/Core/Services/Infrastructure/URLClassifier.swift index 5f204a429..096c7b473 100644 --- a/TablePro/Core/Services/Infrastructure/URLClassifier.swift +++ b/TablePro/Core/Services/Infrastructure/URLClassifier.swift @@ -28,7 +28,7 @@ internal enum URLClassifier { if ext == "tablepro" { return .success(.openConnectionShare(url)) } - if ext == "sql" { + if SQLFileService.supportedExtensions.contains(ext) { return .success(.openSQLFile(url)) } if PluginManager.shared.allInspectorFileExtensions.contains(ext) { diff --git a/TablePro/Core/Services/SQL/SQLFolderWatcher.swift b/TablePro/Core/Services/SQL/SQLFolderWatcher.swift index b8197ea51..0f8498e9c 100644 --- a/TablePro/Core/Services/SQL/SQLFolderWatcher.swift +++ b/TablePro/Core/Services/SQL/SQLFolderWatcher.swift @@ -152,7 +152,7 @@ internal final class SQLFolderWatcher { var indexed: [LinkedSQLIndex.IndexedFile] = [] for case let url as URL in enumerator { - guard url.pathExtension.lowercased() == "sql" else { continue } + guard SQLFileService.supportedExtensions.contains(url.pathExtension.lowercased()) else { continue } let resourceValues = try? url.resourceValues(forKeys: [ .isRegularFileKey, .contentModificationDateKey, .fileSizeKey diff --git a/TablePro/Info.plist b/TablePro/Info.plist index 277bba8e2..2d097112e 100644 --- a/TablePro/Info.plist +++ b/TablePro/Info.plist @@ -14,6 +14,8 @@ CFBundleTypeExtensions sql + psql + pgsql CFBundleTypeIconSystemGenerated @@ -154,6 +156,8 @@ public.filename-extension sql + psql + pgsql diff --git a/TableProTests/Core/Services/Infrastructure/URLClassifierTests.swift b/TableProTests/Core/Services/Infrastructure/URLClassifierTests.swift index c8a310caa..4e70193dd 100644 --- a/TableProTests/Core/Services/Infrastructure/URLClassifierTests.swift +++ b/TableProTests/Core/Services/Infrastructure/URLClassifierTests.swift @@ -50,9 +50,9 @@ struct URLClassifierTests { #expect(intent == nil) } - @Test("SQL file routes to openSQLFile") - func routesSQLFile() { - let sqlURL = URL(fileURLWithPath: "/tmp/query.sql") + @Test("SQL file routes to openSQLFile", arguments: ["sql", "psql", "pgsql", "PSQL"]) + func routesSQLFile(ext: String) { + let sqlURL = URL(fileURLWithPath: "/tmp/query.\(ext)") let intent = URLClassifier.classify(sqlURL) guard case .some(.success(.openSQLFile(let routed))) = intent else { Issue.record("Expected .openSQLFile, got \(String(describing: intent))")