feat(editor): add code folding to the SQL editor - #2232
Open
datlechin wants to merge 12 commits into
Open
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…eat/sql-code-folding # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Folds SQL in the query editor: statements, table bodies, CTEs, subqueries,
BEGINblocks and multi-line block comments. A collapsed region is replaced by a chip showing the start of what it hides and how many lines are hidden.The folding engine already existed in the vendored
CodeEditSourceEditorpackage but was switched off at all seven call sites by #1654, because recalculating folds walks the whole document on every keystroke and that froze the editor on a large paste. Most of this PR is removing the reasons it had to stay off.What was blocking it
Fold depth reset every 50 lines.
ChunkedLineIterator.next()declared a localpreviousDepththat shadowed the instance property and was never written back, so the running depth dropped to zero at every chunk boundary. Any document longer than 50 lines produced wrong folds.LineFoldChunkBoundaryTestscovers it; reintroducing the shadow makes it fail at lines 50 and 100 and pass at 49.No way to supply a fold provider.
TextViewController.foldProvideris internal and the publicSourceEditornever forwarded it, so every consumer was stuck with the indentation-based default. Indentation is the wrong signal for SQL.SourceEditornow takesfoldProvider:, defaulting tonilso existing callers are unaffected.The performance cliff. Fold calculation now stops above
Peripherals.foldingSizeLimit, defaulting toEditorHighlighting.maxHighlightableCharacters(2,000,000), the same threshold that already gates highlighting, inline suggestions and diagnostics. The text-changed stream also switched to.bufferingNewest(1), so a burst of keystrokes coalesces into one recalculation instead of queueing one per keystroke.The placeholder had no text. It drew three dots at a fixed width. It now measures and draws a label. The label string is built by the fold provider rather than the package, because the package has no strings catalog and no
defaultLocalization, so this is the only seam where the app's localized strings can reach it.Folding required visible line numbers. The ribbon is a subview of the gutter, so three editors that hide the gutter could not show it.
Peripherals.showLineNumbersnow separates "draw the gutter" from "draw the numbers in it". This also fixes a coupling in the main editor, where turning off "Show line numbers" would have taken code folding with it.The scanner
SQLFoldScannerwalks the document once and builds a depth stack of frames. Strings, line comments, block comments, MySQL#comments and PostgreSQL dollar-quoted bodies are skipped, so a(inside a string never opens a fold. A region that opens and closes on the same line is discarded.Two rules are worth calling out because both were bugs first:
;after an inner statement inside aBEGINblock closed the enclosing statement and destroyed the block.CREATE TABLE users (,SELECT foo(.END IF,END LOOP,END WHILEandEND FORclose constructs the scanner does not open, so they are skipped rather than popping aBEGINorCASEframe. A closer that does not match the top of the stack is ignored instead of reaching past unrelated open frames, which matters while a block is half-typed.Sharing the lexical rules
The app already had six SQL scanners duplicating string and comment handling.
SqlLexernow holds the character constants and the skip routines the scanners agree on, and bothSQLFoldScannerandSQLStatementScanneruse it.They are not fully merged, and that is deliberate.
SQLStatementScannertreats a backslash as an escape in every dialect;SqlLexer.skipQuotedStringgates it on the dialect, which is what PostgreSQL requires. Unifying that would change how scripts are split for execution, andSQLStatementScannerTestspins the current behaviour. The divergence is documented onSqlLexerand pinned bySqlLexerTests.backslashEscapeIsDialectGatedso it is not "fixed" by accident later.Fold state across tabs
Query tabs share one editor view identity, and
LineFoldStoragecarries collapse state across a recalculation by(depth, start offset). Two unrelated documents hitting the same pair meant one tab's collapsed regions appeared in another. Switching tabs now drops every fold before replaying the incoming tab's own ranges, and the outbound value is guarded against a document that no longer matches the binding, the same way cursor positions already were.Collapsed regions persist through a tab close and reopen. Ranges that no longer fit the query are dropped rather than replayed, mirroring the existing cursor-offset clamping.
Scope
Enabled in all seven editors: SQL editor, DDL view, trigger editor, SQL import preview, AI review sheet, AI chat code blocks and the JSON cell viewer. JSON and JavaScript keep the package's indentation provider, which suits them.
Fold All, Unfold All and Toggle Fold are in the Query menu with rebindable shortcuts (
Cmd+Option+Shift+Left,Cmd+Option+Shift+Right,Cmd+Option+Left; the arrow combinations are unclaimed, unlikeCmd+Option+[). Fold and Unfold are on the editor's right-click menu. Fold All collapses top-level regions only, because the layout manager ignores an attachment that overlaps an earlier one.The Settings toggle defaults to on.
Testing
117 unit tests and 3 UI tests, all passing. The three bugs above were each verified by reintroducing the defect and confirming the test fails.
SQLFoldScannerTests,SQLFoldEventOrderingTests: region shapes, nesting, dialect handling, malformed input, event orderingSQLFoldPerformanceGuardTests: the size gate stops the provider, a 170k-character script scans in under a second, and a single 2,000,000-character line does not stallSqlLexerTests: including the dialect-gated backslash divergenceQueryTabFoldPersistenceTests: round trip, out-of-bounds and malformed ranges, tab files written before folding existedLineFoldChunkBoundaryTests: the depth regressionQueryCodeFoldingUITests: Fold All and Unfold All, Toggle Fold, and the menu items, asserting the document text never changesLeft out
TablePro/Resources/Localizable.xcstringsis not in this PR. It is shared, was already dirty from other work in this checkout before this change started, and the new keys fall back to their English text until a build regenerates it.