Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Minimum macOS is now 14.4.
- Minimum macOS lowered to 13.0 (Ventura).
- Toggle Filters on `Cmd+Shift+F`, leaving `Cmd+Option+F` to Find and Replace.
- The editor's find panel keeps the mode it was left in instead of reverting to Find each time it opens.
- Duplicate Connection shares a linked credential profile instead of copying its password.
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ These govern every decision about code, architecture, tooling and process:

## Project Overview

TablePro is a native macOS database client (SwiftUI + AppKit), a fast, lightweight alternative to TablePlus. macOS 14.4+, `SWIFT_VERSION = 6.0` (`Configs/Base.xcconfig`), Universal Binary (arm64 + x86_64).
TablePro is a native macOS database client (SwiftUI + AppKit), a fast, lightweight alternative to TablePlus. macOS 13.0+, `SWIFT_VERSION = 6.0` (`Configs/Base.xcconfig`), Universal Binary (arm64 + x86_64).

- **Source**: `TablePro/` holds `Core/` (business logic, services), `Views/` (UI), `Models/` (data structures), `ViewModels/`, `Extensions/` and `Theme/`
- **Plugins**: `Plugins/` holds the `.tableplugin` bundles plus the `TableProPluginKit` shared framework.
Expand Down
2 changes: 1 addition & 1 deletion Packages/TableProCore/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "TableProCore",
platforms: [
.macOS(.v14),
.macOS(.v13),
.iOS(.v17)
],
products: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ extension TextViewController {
textCoordinators.forEach { $0.val?.controllerDidDisappear(controller: self) }
}

override public func loadView() { // swiftlint:disable:this prohibited_super_call
super.loadView()
override public func loadView() {
/// Not `super.loadView()`. With a nil `nibName`, macOS 13 looks for a nib named after the
/// class and raises when there is none; macOS 14 quietly makes an empty view instead.
/// This controller has no nib on either, so it makes the view itself.
view = NSView()

scrollView = SourceEditorScrollView()
scrollView.documentView = textView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ final class FindViewController: NSViewController {
fatalError("init(coder:) has not been implemented")
}

override func loadView() { // swiftlint:disable:this prohibited_super_call
super.loadView()
override func loadView() {
/// See `TextViewController.loadView()`: `super` looks for a nib on macOS 13 and raises.
view = NSView()

// Set up the `childView` as a subview of our view. Constrained to all edges, except the top is constrained to
// the find panel's bottom
Expand Down
2 changes: 1 addition & 1 deletion Packages/TableProOracle/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "TableProOracle",
platforms: [
.macOS(.v14),
.macOS(.v13),
.iOS(.v17)
],
products: [
Expand Down
2 changes: 1 addition & 1 deletion Plugins/CSVExportPlugin/CSVExportOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import SwiftUI
import TableProPluginKit

struct CSVExportOptionsView: View {
@Bindable var plugin: CSVExportPlugin
@ObservedObject var plugin: CSVExportPlugin

var body: some View {
VStack(alignment: .leading, spacing: 10) {
Expand Down
6 changes: 3 additions & 3 deletions Plugins/CSVExportPlugin/CSVExportPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
// CSVExportPlugin
//

import Combine
import Foundation
import SwiftUI
import TableProPluginKit

@Observable
final class CSVExportPlugin: ExportFormatPlugin, SettablePlugin, @unchecked Sendable {
final class CSVExportPlugin: ObservableObject, ExportFormatPlugin, SettablePlugin, @unchecked Sendable {
static let pluginName = "CSV Export"
static let pluginVersion = "1.0.0"
static let pluginDescription = "Export data to CSV format"
Expand All @@ -23,7 +23,7 @@ final class CSVExportPlugin: ExportFormatPlugin, SettablePlugin, @unchecked Send
typealias Settings = CSVExportOptions
static let settingsStorageId = "csv"

var settings = CSVExportOptions() {
@Published var settings = CSVExportOptions() {
didSet { saveSettings() }
}

Expand Down
22 changes: 11 additions & 11 deletions Plugins/CSVImportPlugin/CSVImportOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import SwiftUI
import TableProPluginKit

struct CSVImportOptionsView: View {
let plugin: CSVImportPlugin
@ObservedObject var plugin: CSVImportPlugin

var body: some View {
HStack(alignment: .top, spacing: 32) {
Grid(alignment: .leading, horizontalSpacing: 8, verticalSpacing: 10) {
GridRow {
Text("Delimiter:")
.gridColumnAlignment(.trailing)
Picker(String(localized: "Delimiter", bundle: .main), selection: Bindable(plugin).settings.delimiter) {
Picker(String(localized: "Delimiter", bundle: .main), selection: $plugin.settings.delimiter) {
Text("Auto-detect").tag(CSVImportOptions.Delimiter.auto)
Text("Comma (,)").tag(CSVImportOptions.Delimiter.comma)
Text("Semicolon (;)").tag(CSVImportOptions.Delimiter.semicolon)
Expand All @@ -29,7 +29,7 @@ struct CSVImportOptionsView: View {

GridRow {
Text("Quote character:")
Picker(String(localized: "Quote character", bundle: .main), selection: Bindable(plugin).settings.quoteCharacter) {
Picker(String(localized: "Quote character", bundle: .main), selection: $plugin.settings.quoteCharacter) {
Text("Double quote (\")").tag(CSVImportOptions.QuoteCharacter.doubleQuote)
Text("Single quote (')").tag(CSVImportOptions.QuoteCharacter.singleQuote)
}
Expand All @@ -40,7 +40,7 @@ struct CSVImportOptionsView: View {

GridRow {
Text("Encoding:")
Picker(String(localized: "Encoding", bundle: .main), selection: Bindable(plugin).settings.encoding) {
Picker(String(localized: "Encoding", bundle: .main), selection: $plugin.settings.encoding) {
Text("Auto-detect").tag(CSVImportOptions.TextEncoding.auto)
Text("UTF-8").tag(CSVImportOptions.TextEncoding.utf8)
Text("ISO Latin 1").tag(CSVImportOptions.TextEncoding.isoLatin1)
Expand All @@ -53,7 +53,7 @@ struct CSVImportOptionsView: View {

GridRow {
Text("On error:")
Picker(String(localized: "On error", bundle: .main), selection: Bindable(plugin).settings.errorHandling) {
Picker(String(localized: "On error", bundle: .main), selection: $plugin.settings.errorHandling) {
Text("Stop and Rollback").tag(ImportErrorHandling.stopAndRollback)
Text("Stop and Commit").tag(ImportErrorHandling.stopAndCommit)
Text("Skip and Continue").tag(ImportErrorHandling.skipAndContinue)
Expand All @@ -65,29 +65,29 @@ struct CSVImportOptionsView: View {

GridRow {
Text("NULL text:")
TextField("", text: Bindable(plugin).settings.nullString, prompt: Text(verbatim: "\\N"))
TextField("", text: $plugin.settings.nullString, prompt: Text(verbatim: "\\N"))
.textFieldStyle(.roundedBorder)
.frame(width: 170)
.help("An extra value that should be imported as NULL, for example \\N.")
}
}

VStack(alignment: .leading, spacing: 10) {
Toggle("First row is a header", isOn: Bindable(plugin).settings.hasHeaderRow)
Toggle("First row is a header", isOn: $plugin.settings.hasHeaderRow)
.help("Use the first row as column names. Turn off to import every row as data.")

Toggle("Trim leading and trailing spaces", isOn: Bindable(plugin).settings.trimWhitespace)
Toggle("Trim leading and trailing spaces", isOn: $plugin.settings.trimWhitespace)

Toggle("Treat empty values as NULL", isOn: Bindable(plugin).settings.emptyAsNull)
Toggle("Treat empty values as NULL", isOn: $plugin.settings.emptyAsNull)
.help("Insert NULL for empty fields instead of an empty string.")

Toggle("Wrap in transaction (BEGIN/COMMIT)", isOn: Bindable(plugin).settings.wrapInTransaction)
Toggle("Wrap in transaction (BEGIN/COMMIT)", isOn: $plugin.settings.wrapInTransaction)
.disabled(plugin.settings.errorHandling == .skipAndContinue)
.help(plugin.settings.errorHandling == .skipAndContinue
? String(localized: "Not available in skip-and-continue mode")
: String(localized: "Insert all rows in a single transaction. If any row fails, all changes are rolled back."))

Toggle("Delete existing rows before import", isOn: Bindable(plugin).settings.deleteExistingRows)
Toggle("Delete existing rows before import", isOn: $plugin.settings.deleteExistingRows)
.help("Remove every row from the target table before inserting the imported rows.")
}
}
Expand Down
6 changes: 3 additions & 3 deletions Plugins/CSVImportPlugin/CSVImportPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
// CSVImportPlugin
//

import Combine
import Foundation
import SwiftUI
import TableProPluginKit

@Observable
final class CSVImportPlugin: ImportFormatPlugin, SettablePlugin, @unchecked Sendable {
final class CSVImportPlugin: ObservableObject, ImportFormatPlugin, SettablePlugin, @unchecked Sendable {
static let pluginName = "CSV Import"
static let pluginVersion = "1.0.0"
static let pluginDescription = "Import data from CSV and TSV files"
Expand All @@ -21,7 +21,7 @@ final class CSVImportPlugin: ImportFormatPlugin, SettablePlugin, @unchecked Send
typealias Settings = CSVImportOptions
static let settingsStorageId = "csv-import"

var settings = CSVImportOptions() {
@Published var settings = CSVImportOptions() {
didSet { saveSettings() }
}

Expand Down
2 changes: 1 addition & 1 deletion Plugins/HTMLExportPlugin/HTMLExportOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import SwiftUI

struct HTMLExportOptionsView: View {
@Bindable var plugin: HTMLExportPlugin
@ObservedObject var plugin: HTMLExportPlugin

var body: some View {
VStack(alignment: .leading, spacing: 8) {
Expand Down
6 changes: 3 additions & 3 deletions Plugins/HTMLExportPlugin/HTMLExportPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
// HTMLExportPlugin
//

import Combine
import Foundation
import os
import SwiftUI
import TableProPluginKit

@Observable
final class HTMLExportPlugin: ExportFormatPlugin, SettablePlugin, @unchecked Sendable {
final class HTMLExportPlugin: ObservableObject, ExportFormatPlugin, SettablePlugin, @unchecked Sendable {
static let pluginName = "HTML Export"
static let pluginVersion = "1.0.0"
static let pluginDescription = "Export data to an HTML table"
Expand All @@ -21,7 +21,7 @@ final class HTMLExportPlugin: ExportFormatPlugin, SettablePlugin, @unchecked Sen
typealias Settings = HTMLExportOptions
static let settingsStorageId = "html"

var settings = HTMLExportOptions() {
@Published var settings = HTMLExportOptions() {
didSet { saveSettings() }
}

Expand Down
2 changes: 1 addition & 1 deletion Plugins/JSONExportPlugin/JSONExportOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import SwiftUI

struct JSONExportOptionsView: View {
@Bindable var plugin: JSONExportPlugin
@ObservedObject var plugin: JSONExportPlugin

var body: some View {
VStack(alignment: .leading, spacing: 8) {
Expand Down
6 changes: 3 additions & 3 deletions Plugins/JSONExportPlugin/JSONExportPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
// JSONExportPlugin
//

import Combine
import Foundation
import SwiftUI
import TableProPluginKit

@Observable
final class JSONExportPlugin: ExportFormatPlugin, SettablePlugin, @unchecked Sendable {
final class JSONExportPlugin: ObservableObject, ExportFormatPlugin, SettablePlugin, @unchecked Sendable {
static let pluginName = "JSON Export"
static let pluginVersion = "1.0.0"
static let pluginDescription = "Export data to JSON format"
Expand All @@ -20,7 +20,7 @@ final class JSONExportPlugin: ExportFormatPlugin, SettablePlugin, @unchecked Sen
typealias Settings = JSONExportOptions
static let settingsStorageId = "json"

var settings = JSONExportOptions() {
@Published var settings = JSONExportOptions() {
didSet { saveSettings() }
}

Expand Down
8 changes: 4 additions & 4 deletions Plugins/JSONImportPlugin/JSONImportOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@ import SwiftUI
import TableProPluginKit

struct JSONImportOptionsView: View {
let plugin: JSONImportPlugin
@ObservedObject var plugin: JSONImportPlugin

var body: some View {
VStack(alignment: .leading, spacing: 12) {
Picker("On error:", selection: Bindable(plugin).settings.errorHandling) {
Picker("On error:", selection: $plugin.settings.errorHandling) {
Text("Stop and Rollback").tag(ImportErrorHandling.stopAndRollback)
Text("Stop and Commit").tag(ImportErrorHandling.stopAndCommit)
Text("Skip and Continue").tag(ImportErrorHandling.skipAndContinue)
}
.pickerStyle(.menu)
.font(.system(size: 13))

Toggle("Wrap in transaction (BEGIN/COMMIT)", isOn: Bindable(plugin).settings.wrapInTransaction)
Toggle("Wrap in transaction (BEGIN/COMMIT)", isOn: $plugin.settings.wrapInTransaction)
.font(.system(size: 13))
.disabled(plugin.settings.errorHandling == .skipAndContinue)
.help(plugin.settings.errorHandling == .skipAndContinue
? String(localized: "Not available in skip-and-continue mode")
: String(localized: "Insert all rows in a single transaction. If any row fails, all changes are rolled back."))

Toggle("Delete existing rows before import", isOn: Bindable(plugin).settings.deleteExistingRows)
Toggle("Delete existing rows before import", isOn: $plugin.settings.deleteExistingRows)
.font(.system(size: 13))
.help("Remove every row from the target table before inserting the imported rows.")
}
Expand Down
6 changes: 3 additions & 3 deletions Plugins/JSONImportPlugin/JSONImportPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
// JSONImportPlugin
//

import Combine
import Foundation
import SwiftUI
import TableProPluginKit

@Observable
final class JSONImportPlugin: ImportFormatPlugin, SettablePlugin, @unchecked Sendable {
final class JSONImportPlugin: ObservableObject, ImportFormatPlugin, SettablePlugin, @unchecked Sendable {
static let pluginName = "JSON Import"
static let pluginVersion = "1.0.0"
static let pluginDescription = "Import data from JSON files"
Expand All @@ -21,7 +21,7 @@ final class JSONImportPlugin: ImportFormatPlugin, SettablePlugin, @unchecked Sen
typealias Settings = JSONImportOptions
static let settingsStorageId = "json-import"

var settings = JSONImportOptions() {
@Published var settings = JSONImportOptions() {
didSet { saveSettings() }
}

Expand Down
2 changes: 1 addition & 1 deletion Plugins/MQLExportPlugin/MQLExportOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import SwiftUI

struct MQLExportOptionsView: View {
@Bindable var plugin: MQLExportPlugin
@ObservedObject var plugin: MQLExportPlugin

private static let batchSizeOptions = [100, 500, 1_000, 5_000]

Expand Down
6 changes: 3 additions & 3 deletions Plugins/MQLExportPlugin/MQLExportPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
// MQLExportPlugin
//

import Combine
import Foundation
import SwiftUI
import TableProPluginKit

@Observable
final class MQLExportPlugin: ExportFormatPlugin, SettablePlugin, @unchecked Sendable {
final class MQLExportPlugin: ObservableObject, ExportFormatPlugin, SettablePlugin, @unchecked Sendable {
static let pluginName = "MQL Export"
static let pluginVersion = "1.0.0"
static let pluginDescription = "Export data to MongoDB Query Language format"
Expand All @@ -27,7 +27,7 @@ final class MQLExportPlugin: ExportFormatPlugin, SettablePlugin, @unchecked Send
typealias Settings = MQLExportOptions
static let settingsStorageId = "mql"

var settings = MQLExportOptions() {
@Published var settings = MQLExportOptions() {
didSet { saveSettings() }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import SwiftUI

struct MarkdownExportOptionsView: View {
@Bindable var plugin: MarkdownExportPlugin
@ObservedObject var plugin: MarkdownExportPlugin

var body: some View {
VStack(alignment: .leading, spacing: 8) {
Expand Down
6 changes: 3 additions & 3 deletions Plugins/MarkdownExportPlugin/MarkdownExportPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
// MarkdownExportPlugin
//

import Combine
import Foundation
import os
import SwiftUI
import TableProPluginKit

@Observable
final class MarkdownExportPlugin: ExportFormatPlugin, SettablePlugin, @unchecked Sendable {
final class MarkdownExportPlugin: ObservableObject, ExportFormatPlugin, SettablePlugin, @unchecked Sendable {
static let pluginName = "Markdown Export"
static let pluginVersion = "1.0.0"
static let pluginDescription = "Export data to Markdown tables"
Expand All @@ -25,7 +25,7 @@ final class MarkdownExportPlugin: ExportFormatPlugin, SettablePlugin, @unchecked
/// would mean holding the whole table in memory to decide a column width.
private static let alignmentSampleRows = 200

var settings = MarkdownExportOptions() {
@Published var settings = MarkdownExportOptions() {
didSet { saveSettings() }
}

Expand Down
2 changes: 1 addition & 1 deletion Plugins/ParquetExportPlugin/ParquetExportOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import SwiftUI

struct ParquetExportOptionsView: View {
@Bindable var plugin: ParquetExportPlugin
@ObservedObject var plugin: ParquetExportPlugin

private static let rowGroupSizes = [10_000, 50_000, 122_880, 500_000, 1_000_000]

Expand Down
Loading
Loading