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 .claude/skills/fix-issue/references/verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ The `DEVELOPER_DIR` export above is required; without it SwiftLint aborts with `

Two things to know:

- **SwiftLint's `included:` scope is `TablePro` only.** `Plugins/`, `LocalPackages/`, and the test targets are never linted by a bare `swiftlint lint`. Pass explicit paths to lint a change outside the app target.
- **SwiftLint's `included:` scope is `TablePro` and `Packages`.** The rest of `Plugins/` and the app's test targets are never linted by a bare `swiftlint lint`. Pass explicit paths to lint a change outside that scope.
- **Local `swiftformat` is a version behind the repo `.swiftformat`** and rejects `--ifdefindent`, so it cannot run here. Rely on SwiftLint plus reading the diff.
- **Never remove a `swiftlint:disable force_unwrapping` comment** to satisfy a local run. There are five of them, inline in four files, not in the config: `.swiftlint.yml` enables the rule as an opt-in and sets it to `warning`. The CI toolchain differs from this one, so a disable that looks unnecessary here is load-bearing there.

Expand Down
17 changes: 6 additions & 11 deletions .github/workflows/macos-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ on:
- "TablePro/**"
- "Plugins/**"
- "Packages/**"
- "LocalPackages/**"
- "TableProTests/**"
- "TableProUITests/**"
- "Native/**"
Expand Down Expand Up @@ -95,7 +94,7 @@ jobs:
--base "$BASE_SHA" \
--ref "$REF" \
--skip-release-commit \
'TablePro/' 'Plugins/' 'Packages/' 'LocalPackages/' \
'TablePro/' 'Plugins/' 'Packages/' \
'TableProTests/' 'TableProUITests/' 'Native/' 'Configs/' 'Libs/' 'scripts/' \
'\.github/actions/' '\.github/macos-(ui-)?test-quarantine\.txt$' \
'TablePro\.xcodeproj/project\.xcworkspace/xcshareddata/swiftpm/Package\.resolved$' \
Expand Down Expand Up @@ -145,16 +144,12 @@ jobs:
if: needs.changes.outputs.oracle == 'true'
run: swift test --package-path Packages/TableProOracle --force-resolved-versions

- name: Run CodeEditTextView package tests
run: swift test --package-path LocalPackages/CodeEditTextView --force-resolved-versions

# The editor's own suites, 111 XCTest cases and 102 Swift Testing cases, roughly half of them
# TablePro's. Two XCTest cases skip themselves on a headless runner (no screen, no key window),
# so the expected result is 111 executed with 2 skipped. Pinned to the tracked Package.resolved
# like its siblings; note that this package and CodeEditTextView are path-linked, so a
# SwiftTreeSitter bump has to land in both lockfiles or this step fails rather than floating.
- name: Run CodeEditSourceEditor package tests
run: swift test --package-path LocalPackages/CodeEditSourceEditor --force-resolved-versions
# The editor, both targets. Two XCTest cases skip themselves on a headless runner (no screen,
# no key window), so the expected result is 112 executed with 2 skipped, plus 102 Swift Testing
# cases. Pinned to the tracked Package.resolved like its siblings.
- name: Run TableProEditor package tests
run: swift test --package-path Packages/TableProEditor --force-resolved-versions

# Compiles the app, both test bundles and all 31 plugins once, then hands the products to the
# test jobs. That build used to happen inside the same job that ran the tests, which meant the
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ playground.xcworkspace
.swiftpm/

.build/
LocalPackages/*/Example/**/Package.resolved

# Coverage profile output
*.profraw
Expand Down
14 changes: 7 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ TablePro is a native macOS database client (SwiftUI + AppKit), a fast, lightweig
- **Registry-only** (the other 23): MongoDB, Oracle, DuckDB, MSSQL, Cassandra, Etcd, CloudflareD1, CloudflareR2SQL, DynamoDB, BigQuery, Spanner, LibSQL, Snowflake, Elasticsearch, Typesense, Beancount, SurrealDB, Teradata, Trino, Dameng, Kafka, Weaviate, Parquet export. Parquet is registry-only because it links its own copy of DuckDB, which does the encoding, and that is too large to ship in the app for one format. Distributed via [TableProApp/plugins](https://github.com/TableProApp/plugins) `plugins.json`, installed into the user plugins directory.
- **C bridges**: Each plugin contains its own C bridge module (e.g., `Plugins/MySQLDriverPlugin/CMariaDB/`, `Plugins/PostgreSQLDriverPlugin/CLibPQ/`)
- **Static libs**: `Libs/` holds pre-built `.a` files and `Libs/ios/` holds the iOS xcframeworks. Both are downloaded by `scripts/download-libs.sh` and are not in git.
- **SPM deps**: declared in `project.yml`. Vendored forks under `LocalPackages/` (CodeEditSourceEditor, CodeEditTextView) and TablePro's own packages under `Packages/` (TableProCore, TableProOracle, TableProGrammars); remote packages are Sparkle, swift-certificates and Yams. Revisions are pinned by the tracked `Package.resolved` inside each generated `.xcodeproj`.
- **SPM deps**: declared in `project.yml`. Every local package lives under `Packages/` (TableProCore, TableProOracle, TableProEditor, TableProGrammars); remote packages are Sparkle, swift-certificates and Yams. Revisions are pinned by the tracked `Package.resolved` inside each generated `.xcodeproj`.
- `Packages/TableProGrammars` is first-party code, not a fork. It holds TablePro's `CodeLanguage`, `GrammarID` and `HighlightQueries` plus the four tree-sitter grammars the app compiles (bash, javascript, json, sql), each with its own upstream MIT licence beside its sources and a record in `licenses.yml`. Every `GrammarID` case but `plainText` resolves to a parser, and a test asserts it: the enum is the list of grammars that ship, not a list of languages tree-sitter supports.
- `SWIFT_VERSION` in `Configs/Base.xcconfig` sets the language mode for the Xcode-native targets only. A SwiftPM package takes its mode from its own manifest, so `Packages/TableProCore` and `Packages/TableProOracle` carry `swift-tools-version: 6.0` of their own. The vendored `LocalPackages/` forks stay on 5.9 so they can still take upstream changes, and a remote dependency keeps whatever its own manifest says. Never pass `SWIFT_VERSION=` on an `xcodebuild` command line to test a language-mode change: the override reaches the package targets too and reports their errors as yours.
- `SWIFT_VERSION` in `Configs/Base.xcconfig` sets the language mode for the Xcode-native targets only. A SwiftPM package takes its mode from its own manifest, so every package under `Packages/` carries `swift-tools-version: 6.0` of its own, and a remote dependency keeps whatever its own manifest says. `TableProEditor` sets `.swiftLanguageMode(.v5)` on its targets: the manifest is current but the code has not been through the Swift 6 concurrency migration yet, and that is a change with its own tests rather than a side effect of a move. Never pass `SWIFT_VERSION=` on an `xcodebuild` command line to test a language-mode change: the override reaches the package targets too and reports their errors as yours.

## Build & Development Commands

Expand Down Expand Up @@ -156,14 +156,14 @@ When adding a new method to the driver protocol: add to `PluginDatabaseDriver` (
- Unknown types (from future plugins) are valid, they round-trip through Codable
- Use `DatabaseType.allKnownTypes` (not `allCases`) for the canonical list

### Editor Architecture (CodeEditSourceEditor)
### Editor Architecture (TableProEditor)

- **`ThemeEngine`**: the `@Observable` singleton that owns the active theme, and the single source of truth for editor colors and fonts
- **Two font domains, and a control never names a third**: `editorFonts` is the Editor Font setting and belongs to a view that also wears the editor's background, text color and syntax palette (the SQL editor, `JSONCodeEditor`, the fold and import previews). Every control that shows or edits a stored *value* takes `ThemeEngine.valueFont` / `valueFontSwiftUI`, which is the Data Grid Font setting, so one value reads the same in the grid cell, its inline editor, the row inspector, a cell popover and a pop-out window. Naming a system text style instead is the defect: it looks right only while the user leaves both settings equal, which is the default, so it ships invisibly (#2393). The inspector sets it once for the whole editor subtree in `InspectorFieldRow`, past the field label and the value menu, so a new field editor inherits it. The switch that decides it is exhaustive over `FieldEditorKind` on purpose: two `default`-armed switches are what let `.typePicker` escape the domain and render a structure row's Name and Type in two different fonts. AppKit will not honour it on a `Picker(.menu)`'s items, which `NSMenu` draws itself; that is the one place the rule stops.
- **`TableProEditorTheme`**: adapter to CodeEdit's `EditorTheme` protocol; `ThemeEngine.makeEditorTheme()` builds it
- **`CompletionEngine`**: framework-agnostic; **`QueryCompletionAdapter`** bridges to CodeEdit's `CodeSuggestionDelegate`
- **`TableProEditorTheme`**: adapter to `TableProEditorKit`'s `EditorTheme` protocol; `ThemeEngine.makeEditorTheme()` builds it
- **`CompletionEngine`**: framework-agnostic; **`QueryCompletionAdapter`** bridges to `TableProEditorKit`'s `CodeSuggestionDelegate`
- Editor tabs are drawn by `EditorTabStrip`, not by native window tabs. A window belongs to exactly one `NSWindow` tab group and that group's bar shows every window in it, so a window hosting several connections could only ever show all of their tabs interleaved. Window tabbing itself stays on AppKit's terms: `TabWindowController` leaves `tabbingMode` at `.automatic`, which is the user's own System Settings preference, and never forces `.preferred`.
- Cursor model: `cursorPositions: [CursorPosition]` (multi-cursor via CodeEditSourceEditor)
- Cursor model: `cursorPositions: [CursorPosition]` (multi-cursor via `TableProEditorKit`)

### Change Tracking Flow

Expand Down Expand Up @@ -339,7 +339,7 @@ These are **non-negotiable**, never skip them:

4. **Tests**: Every change with testable behavior must include or update unit/function tests. UI and user-flow changes should add or update `TableProUITests` UI automation where the flow runs deterministically; if it can't, note why in the PR description. When tests fail, fix the source code, never adjust tests to match incorrect output. Tests define expected behavior.

5. **Lint after changes**: Run `swiftlint lint --strict` to verify compliance. `.swiftlint.yml` sets `included: [TablePro, Packages]`, so a bare run never sees `Plugins/`, `TableProTests/` or `TableProUITests/`.
5. **Lint after changes**: Run `swiftlint lint --strict` to verify compliance. `.swiftlint.yml` sets `included: [TablePro, Packages]`, so a bare run covers the app and every local package, including `TableProPluginKit` through the symlink in `TableProCore`. It still does not see the rest of `Plugins/`, `TableProTests/` or `TableProUITests/`.

**Reaching those takes file paths, not directory paths.** SwiftLint applies `included:` to a directory argument and not to a file argument, so `swiftlint lint --strict TableProTests` reports zero violations having linted nothing at all: measured, that command finds 0 and `TableProTests/**/*.swift` finds 1,227. Glob to files for a change outside `included:`, e.g. `swiftlint lint --strict $(git show --name-only --format= HEAD | grep '\.swift$')`. `verify.sh lint` names any directory it was handed and then dropped, so a clean result is not read as coverage it never had.

Expand Down
47 changes: 0 additions & 47 deletions LocalPackages/CodeEditSourceEditor/Package.swift

This file was deleted.

32 changes: 0 additions & 32 deletions LocalPackages/CodeEditTextView/Package.resolved

This file was deleted.

53 changes: 0 additions & 53 deletions LocalPackages/CodeEditTextView/Package.swift

This file was deleted.

This file was deleted.

This file was deleted.

52 changes: 52 additions & 0 deletions Packages/TableProEditor/ORIGIN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# TableProEditor

The SQL editor: the text engine, its layout and selection, syntax highlighting, folding, the gutter
and the completion window. It began as two packages from the
[CodeEdit](https://github.com/CodeEditApp) project and TablePro has diverged from both far enough
that they are no longer upstreamable as a whole.

| Target | Vendored from | Licence |
| --- | --- | --- |
| `TableProTextEngine`, `TableProTextEngineObjC` | https://github.com/CodeEditApp/CodeEditTextView at `d7ac3f11f` (2025-07-30) | MIT, `LICENSE-CodeEditTextView.md` |
| `TableProEditorKit` | https://github.com/CodeEditApp/CodeEditSourceEditor at `1fa4d3c3f` (2025-12-31) | MIT, `LICENSE-CodeEditSourceEditor.md` |

Both were vendored in 2026-03 at what was then upstream's tip, and upstream has landed nothing since.

## Why it is one package and not two

Upstream split the engine from the editor because they are two products. Here they are one: 32% of
the commits that touched either touched both, and the strongest co-change pair in the repository is
`TableProEditorKit`'s controller against `TableProTextEngine`'s text view, which the old boundary put
on opposite sides. The targets keep the layering the split was for, and SwiftPM rejects a cycle
between them just as it rejected one between the packages.

## What TablePro changed

Roughly 7,000 lines of source and 7,400 lines of tests, in three groups.

**Correctness and performance in the engine.** Clip-bounded CoreText drawing, per-line widths in the
red-black tree so `maxLineWidth` falls when the widest line is deleted, a typesetting fix where a
line break was used as a length rather than an offset, IME and `NSTextInputClient` range handling,
pasteboard coercion, invisible-character rendering, and range-safety helpers. Most of this is
general and upstream would plausibly want it.

**Product features.** Per-statement run controls in the gutter, and the fold placeholder preview.
These carry no SQL knowledge: `StatementRun` holds an `NSRange` and nothing else, and every SQL
scanner lives in `TablePro/Core/Utilities/SQL/`.

**Replacements.** The completion window was rewritten from `NSTableView` to SwiftUI, and the fold
ribbon from upstream's hover-marker strip to macOS disclosure chevrons. Six upstream files were
deleted outright.

Four subsystems upstream ships were removed because TablePro never used them and two of them were
running anyway: the minimap, jump to definition, the reformatting guide and HTML tag completion
(#2877).

## Taking a change from upstream

Do not expect a rebase to work. 42 of 65 files TablePro touched in the engine, and 41 of 70 in the
editor, collide with what upstream changed in its own last 60 commits. Port a specific fix by reading
it and writing it here, the way any other bug report is handled.

The reverse direction is open: the correctness and performance work is written against upstream's own
shapes and roughly 1,060 lines of it lift out cleanly if anyone wants to send it back.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading