Repository navigation
Conversation
look_ahead expected a token after the current one, which doesn't exist at EOF. A document that ends in `with`, e.g. while typing a CTE at the end of a file, made the splitter panic.
Every change of a document split and lexed the whole text again and scanned every line for suppressions: 30 ms per keystroke on a file of 20,000 statements in a release build, 660 ms in a debug build. split_edited finds the changed part from the common prefix and suffix, splits the statements around it again, and keeps the rest of the previous split, shifted by the change in length. Two statements of context on each side have to come out as before, and changes that touch a quote, a comment or a psql command split the whole text, so the result is always the same as a full split. Documents without a suppression comment skip the suppressions parser, and line suppressions are looked up by binary search.
This was referenced Oct 24, 2026
This branch has not been deployed
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.
Stacked on #956. The first commit is the fix from #963 (a panic on a trailing
WITH), which the randomized test below found. It drops out when #963 is merged.Every
change_filesplit the whole document again (lexer, splitter, and the blank-line recovery pass) and scanned every line for suppression comments. That is linear in the file and runs on every keystroke:change_file, one keystroke in the middle of the fileMeasured with a throwaway example (not committed) that types and deletes a character in a table name in the middle of a migration-style file, 200 times, through
WorkspaceServer::change_file.Incremental split
pgls_statement_splitter::split_edited(old, previous, new)returns the same result assplit(new):Find the change. The common prefix and suffix of the old and new text give the changed part, compared in 64-byte blocks. Several edits at once become one changed part.
Re-split a region. The region runs from
EDIT_CONTEXT_STATEMENTS = 2statements before the change to as many after it, split on its own withsplit. The statements before the region are kept, and the ones after it are shifted by the change in length. Errors are kept and shifted the same way, lexer errors first and then splitter errors, assplitorders them.Check the edges. The two statements at each edge of the region have to come out exactly as in the previous split. That shows the region lines up with the statements around it:
look_back,look_ahead);The first and the last statement of the region, the ones that are split without their real neighbours (no
look_backat the start, the end of the region instead of the next statement), are taken from the previous split as well. Without that, an error that a statement reports at the end of the text (Expected FROM_KWondeletefollowed by EOF) leaked into the result.Fall back to a full split when the change, or one byte on either side of it, touches
$ ' " / * - \. Those can open or close a string, a dollar quote, a comment or a psql command, and so change how everything after them is lexed. A full split is also used when the edge statements don't match.The
Documentkeeps theSplitResultof its content and passes it tosplit_editedinupdate_content.SplitResultderivesClone/PartialEqand records how many of its errors come from the lexer.Suppressions
Suppressions::fromnow returns early when the text doesn't contain-ignore. Every suppression comment contains it, and the check is a single substring search. Without line suppressions the line index isn't needed, so it isn't built either. This took 2.5 ms of the remaining 3 ms at 20,000 statements. With suppressions in the file, the parser still runs over all lines as before.Two small fixes in the same code:
is_suppressedcomputed the lines of a diagnostic even without any line suppression;LineIndex::line_for_offsetwas a linear scan over all lines. It is a binary search now, which matters when a file with suppressions has many diagnostics.Tests
split_edited_is_split: 3,000 random edits over 10 documents built from statements the splitter treats differently:$$andBEGIN ATOMICfunctions, comments, quoted;, blank lines inside statements, statements without;, invalid SQL. The edits insert, delete or replace text, including fragments that cut or merge statements (;,\n\n,select,begin atomic,',$$,/*,--,\d), sometimes two at once. After each edit the result must equalsplit(new), ranges and errors included. Each edit starts from the previous incremental result, so errors would accumulate.SPLIT_SEEDandSPLIT_EDITSchange the run; locally, 12 seeds × 50,000 edits passed in release. About 43% of these edits are split incrementally; the rest touch a quote or comment and fall back. Normal typing (letters, spaces,;, parentheses) is incremental.updates_split_like_the_whole_document(workspace): 1,000 edits throughDocument::update_content. After each, the document's split and syntax diagnostics equal those of a full split.documents_without_suppressions_skip_the_lines: the fast path gives the same suppressions as parsing the lines.Not covered
update_contentstill hashes the text of every statement, about 0.3 ms at 20,000 statements in release. It could use the changed region too.