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
2 changes: 1 addition & 1 deletion apple/.swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ custom_rules:
# Tests (listed in TableProTests/.swiftlint.yml and Plugins/.swiftlint.yml)
suite_without_trait:
name: "Suite Without A Trait"
included: ['.*/(TableProTests|Plugins/[A-Za-z]+Tests|Packages/TableProMacCore/Tests)/']
included: ['.*/(TableProTests|Plugins/[A-Za-z]+Tests|Packages/TableProMacCore/Tests|Packages/TableProCore/Tests/TableProAutocompleteTests)/']
regex: '(?:^#if\b[^\n]*\n[ \t]*|^)(?:@\w+[ \t]+)*@(?:Testing\.)?Suite(?:[ \t]*\(\s*(?:"(?:[^"\\\n]|\\.)*"|#+"[^\n]*?"#+)?\s*\))?(?![ \t]*\()(?=\s)'
excluded_match_kinds: [comment, doccomment]
message: "Every top-level @Suite expansion walks every other one, so compile time grows with their square. Keep @Suite only to carry a trait such as .serialized."
Expand Down
2 changes: 1 addition & 1 deletion apple/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ The architecture is in the area rules under `.claude/rules/`, and its invariants
| TableProEditor | `Packages/`, vendored from CodeEditSourceEditor and CodeEditTextView | The SQL editor |
| TableProGrammars | `Packages/` | Tree-sitter grammars |
| TableProCore | `Packages/` | Modules the macOS and iOS apps share |
| TableProMacCore | `Packages/` | macOS-only modules the app links: utilities, autocomplete, SSH config, MCP |
| TableProMacCore | `Packages/` | macOS-only modules the app links: utilities, SSH config, MCP |
| TableProOracle | `Packages/`, wraps a pinned fork of OracleNIO | Oracle wire protocol |
| Sparkle | SPM | Updates, EdDSA-signed |
| Yams | SPM | YAML for project folder import |
Expand Down
14 changes: 14 additions & 0 deletions apple/Packages/TableProCore/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ let package = Package(
.library(name: "TableProR2SQLCore", targets: ["TableProR2SQLCore"]),
.library(name: "TableProConnectionLibrary", targets: ["TableProConnectionLibrary"]),
.library(name: "TableProSQLGrammar", targets: ["TableProSQLGrammar"]),
.library(name: "TableProAutocomplete", targets: ["TableProAutocomplete"]),
.library(name: "TableProSSHTransport", targets: ["TableProSSHTransport"]),
.library(name: "CSQLite", targets: ["CSQLite"]),
.library(name: "TableProSQLiteCore", targets: ["TableProSQLiteCore"]),
Expand Down Expand Up @@ -200,6 +201,13 @@ let package = Package(
dependencies: [],
path: "Sources/TableProSQLGrammar"
),
// The macOS app links it, so never TableProPluginKit.
.target(
name: "TableProAutocomplete",
dependencies: ["TableProSQLGrammar"],
path: "Sources/TableProAutocomplete",
swiftSettings: pluginCoreSettings
),
.target(
name: "TableProSSHTransport",
dependencies: [],
Expand Down Expand Up @@ -329,6 +337,12 @@ let package = Package(
dependencies: ["TableProSQLGrammar"],
path: "Tests/TableProSQLGrammarTests"
),
.testTarget(
name: "TableProAutocompleteTests",
dependencies: ["TableProAutocomplete", "TableProSQLGrammar"],
path: "Tests/TableProAutocompleteTests",
swiftSettings: pluginCoreSettings
),
.testTarget(
name: "TableProAnalyticsTests",
dependencies: ["TableProAnalytics"],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

// Each app answers from its own schema cache, so both rank through one provider.
public protocol SQLCompletionCatalog: Sendable {
func resolveAlias(_ aliasOrName: String, in references: [TableReference]) async -> String?
func columnCompletionItems(for tableName: String, schema: String?) async -> [SQLCompletionItem]
func tableCompletionItems() async -> [SQLCompletionItem]
func tableCompletionItems(inSchema schema: String) async -> [SQLCompletionItem]
func schemaCompletionItems() async -> [SQLCompletionItem]
func namespaceCompletionItems() async -> [SQLCompletionItem]
func isKnownSchema(_ name: String) async -> Bool
func isKnownDatabase(_ name: String) async -> Bool
func allowedValues(forColumn column: String, in references: [TableReference]) async -> [String]
func allColumnsInScope(for references: [TableReference]) async -> [SQLCompletionItem]
func allColumnsFromCachedTables() async -> [SQLCompletionItem]
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Foundation

public enum SQLCompletionTriggerPolicy {
// A clause with a full column list behind it stays shut until the user types or asks; the listed ones answer with
// a short, closed list. An opening quote counts as typed input: it sits in `prefixRange` but not in `prefix`.
public static func suppressesEmptyPrefix(_ context: SQLContext, isManualTrigger: Bool) -> Bool {
guard !isManualTrigger,
context.prefix.isEmpty,
context.prefixRange.isEmpty,
context.dotPrefix == nil else { return false }

switch context.clauseType {
case .from, .join, .into, .set, .insertColumns, .on,
.alterTableColumn, .returning, .using, .dropObject, .createIndex, .castTarget:
return false
case .select where !context.isAfterComma:
return false
default:
return true
}
}

public static func suppressesAfterStatementBreak(in text: NSString, at offset: Int) -> Bool {
guard offset > 0, offset - 1 < text.length else { return false }

let previous = text.character(at: offset - 1)
let semicolon = UInt16(UnicodeScalar(";").value)
let newline = UInt16(UnicodeScalar("\n").value)
guard previous == semicolon || previous == newline else { return false }
guard offset < text.length else { return true }

return text.substring(from: offset).trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Foundation

public struct SQLCompletionVocabulary: Sendable {
public struct Operator: Sendable, Hashable {
public let symbol: String
public let summary: String
public let appliesToTypes: [String]

public init(symbol: String, summary: String, appliesToTypes: [String] = []) {
self.symbol = symbol
self.summary = summary
self.appliesToTypes = appliesToTypes
}
}

public struct StatementCompletion: Sendable, Hashable {
public let label: String
public let insertText: String

public init(label: String, insertText: String) {
self.label = label
self.insertText = insertText
}
}

public let functions: Set<String>
public let functionNamesAreCaseInsensitive: Bool
public let operators: [Operator]
public let dataTypes: Set<String>
// nil offers the generic options; an empty list offers none.
public let tableOptions: [String]?
public let statementCompletions: [StatementCompletion]

public init(
functions: Set<String> = [],
functionNamesAreCaseInsensitive: Bool = false,
operators: [Operator] = [],
dataTypes: Set<String> = [],
tableOptions: [String]? = nil,
statementCompletions: [StatementCompletion] = []
) {
self.functions = functions
self.functionNamesAreCaseInsensitive = functionNamesAreCaseInsensitive
self.operators = operators
self.dataTypes = dataTypes
self.tableOptions = tableOptions
self.statementCompletions = statementCompletions
}

public static let generic = SQLCompletionVocabulary()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

// The analyzer reads only the text around the caret, so a long script costs a keystroke no more than a short one.
public enum SQLCompletionWindow {
public static let radius = 5_000

public static func cut(_ text: NSString, around offset: Int) -> (text: String, start: Int) {
let caret = min(max(offset, 0), text.length)
let start = max(0, caret - radius)
let end = min(text.length, caret + radius)
return (text.substring(with: NSRange(location: start, length: end - start)), start)
}
}
Loading
Loading