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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- JSON and PHP tree values use the editor's syntax colors, matching the same value shown as text.
- Theme file format 3, where every color is optional. Themes written for earlier versions are not read and need to be recreated.
- Triggers and ClickHouse Parts in table structure shown as data grids, with multiple selection and Copy.
- Filter and maintenance SQL previews highlighted as SQL in the editor font.
- Details in Quick Switcher, autocomplete, Settings, query plans, tooltips and the result bar separated by commas or space instead of a dot.
- Filter and maintenance previews highlighted in the editor font, DynamoDB's as JSON.
- Settings > Editor grouped into Display, Gutter and Editing.
- Editor Font and Data Grid Font moved to Settings > Editor and Settings > Data, and kept per Mac.
Expand Down Expand Up @@ -80,6 +82,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Reopen Last Session forgetting its connections after an MCP client launched TablePro in the background.
- GitHub Copilot starting at launch when not in use, and running on after AI was turned off or Copilot was removed.
- Settings > AI reading sign-in tokens and running command line tools for providers that were never added.
- Database type icon and checkmark hard to see on the selected row in Choose a Database.
- Oracle connection timing out after 30 seconds when the password is about to expire. (#3401)
- Oracle session left open on the server when a connect was canceled during login.
- Only the first statement running on iPhone and iPad when the query holds several. (#3399)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ extension DynamoDBPluginDriver {
let units = formatter.string(from: NSNumber(value: stats.readUnits)) ?? "\(stats.readUnits)"
parts.append(String(format: String(localized: "%@ RCU"), units))
}
return (parts + extra).joined(separator: " · ")
// A note is a clause with commas of its own, so it follows the counts as a sentence.
return ([parts.joined(separator: ", ")] + extra).joined(separator: ". ")
}

static func queryResult(
Expand Down
3 changes: 2 additions & 1 deletion Plugins/DynamoDBDriverPlugin/DynamoDBTableDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ enum DynamoDBTableDefinition {
parts.append(String(format: String(localized: "Status: %@"), status))
}
parts.append(String(localized: "Item count is approximate, updated about every six hours"))
return parts.joined(separator: " · ")
// Capacity and the item count note carry commas, so a comma cannot separate the parts.
return parts.joined(separator: "; ")
}

private static func keySchema(_ keys: DynamoDBKeySchema) -> DynamoDBJSON {
Expand Down
8 changes: 1 addition & 7 deletions Plugins/MongoDBDriverPlugin/MongoScriptResultBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ enum MongoScriptResultBuilder {
outcome.documents.holdsStoredDocuments ? read.texts.map(MongoDocumentIdentity.locator(inDocument:)) : nil
)
guard !outcome.printedLines.isEmpty else { return grid }
return grid.withStatus(printedSummary(outcome.printedLines))
return grid.withStatus(MongoScriptText.printedSummary(outcome.printedLines))
}

if !outcome.printedLines.isEmpty {
Expand Down Expand Up @@ -69,10 +69,4 @@ enum MongoScriptResultBuilder {
private static func note(_ text: String, startTime: Date) -> PluginQueryResult {
values([text], column: MongoScriptText.outputColumn, startTime: startTime)
}

private static func printedSummary(_ lines: [String]) -> String {
let joined = lines.joined(separator: " · ")
guard joined.count > 400 else { return joined }
return String(joined.prefix(400)) + "…"
}
}
7 changes: 7 additions & 0 deletions Plugins/MongoDBDriverPlugin/MongoScriptText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ enum MongoScriptText {
String(format: String(localized: "%d line(s) printed"), count)
}

/// Printed lines can hold commas of their own, so a comma would not tell one line from the next.
static func printedSummary(_ lines: [String]) -> String {
let joined = lines.joined(separator: "; ")
guard joined.count > 400 else { return joined }
return String(joined.prefix(400)) + "…"
}

static var outputColumn: String {
String(localized: "output")
}
Expand Down
3 changes: 2 additions & 1 deletion Plugins/TypesenseDriverPlugin/TypesenseApiKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ enum TypesenseApiKeys {
} else {
parts.append(key.collections.joined(separator: ", "))
}
return parts.isEmpty ? nil : parts.joined(separator: " · ")
// The collection list is itself comma separated.
return parts.isEmpty ? nil : parts.joined(separator: "; ")
}

/// A Typesense key carries actions and a collection list, and the pair is the grant: every
Expand Down
4 changes: 1 addition & 3 deletions TablePro/Core/Autocomplete/SQLCompletionItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,6 @@ extension SQLCompletionItem {
)
}

/// Create a column completion item
static func column(
_ name: String,
dataType: String?,
Expand All @@ -279,12 +278,11 @@ extension SQLCompletionItem {
defaultValue: String? = nil,
comment: String? = nil
) -> SQLCompletionItem {
// Build detail string: "PK · NOT NULL · INT"
var detailParts: [String] = []
if isPrimaryKey { detailParts.append("PK") }
if !isNullable { detailParts.append("NOT NULL") }
if let dataType { detailParts.append(dataType) }
let detail = detailParts.isEmpty ? nil : detailParts.joined(separator: " · ")
let detail = detailParts.isEmpty ? nil : detailParts.joined(separator: ", ")

var docParts: [String] = []
if let tableName { docParts.append("Column from \(tableName)") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ internal extension MainSplitViewController {
}

private static func recentTabDetail(container: String?, connectionName: String?) -> String {
[container, connectionName].compactMap { $0 }.joined(separator: " \u{00B7} ")
[container, connectionName].compactMap { $0 }.joined(separator: ", ")
}

private static func recentTabSymbol(for tab: QueryTab) -> String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ internal final class WorkspaceRailCellView: NSTableCellView {
if let identity = connection.identityColor {
parts.append(String(format: String(localized: "Color: %@"), identity.displayName))
}
return parts.joined(separator: " · ")
return parts.joined(separator: ", ")
}

internal static func voiceOverLabel(for entry: WorkspaceRailEntry) -> String {
Expand Down
7 changes: 2 additions & 5 deletions TablePro/Core/Services/Licensing/LicensePresentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,7 @@ internal enum LicensePresentation {
String(format: String(localized: "%1$lld of %2$lld members"), used, limit)
}

/// What a licence is, in one line under the licensee's email.
///
/// A one-time licence has no expiry and says so once. Its billing cycle is the server's word for
/// the same thing, "lifetime", so taking both spelled the plan "Team · Lifetime · Lifetime".
/// A one-time licence's "lifetime" cycle repeats "One-time", so it is dropped.
static func planDescription(tier: String, billingCycle: String?, expiry: String?) -> String {
var parts = [LicenseTier(rawValue: tier).displayName]

Expand All @@ -214,7 +211,7 @@ internal enum LicensePresentation {
parts.append(String(localized: "One-time"))
}

return parts.joined(separator: " · ")
return parts.joined(separator: ", ")
}

/// The first group of a license key, with the rest withheld.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ struct ScannedConnectionCandidate: Identifiable {
self.sourceKey = sourceKey
self.kind = kind
self.tier = tier
self.warnings = warnings
var seen = Set<String>()
self.warnings = warnings.filter { seen.insert($0).inserted }
self.placeholderSuspected = PlaceholderCredentialDetector.isPlaceholder(
username: parsedURL.username,
password: parsedURL.password,
Expand Down
9 changes: 7 additions & 2 deletions TablePro/Models/Query/QueryPlanDiff.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@ struct QueryPlanNodeChange: Identifiable, Hashable, Sendable {

var id: String { "\(kind.rawValue):\(path)" }

var relationName: String? {
guard let relation, !relation.isEmpty else { return nil }
return relation
}

var title: String {
guard let relation, !relation.isEmpty else { return operation }
return "\(operation) · \(relation)"
guard let relationName else { return operation }
return "\(operation), \(relationName)"
}
}

Expand Down
Loading
Loading