Repository navigation
[ty] Emit folding ranges from the language server for multi-line block headers. - #24978
Merged
Merged
Conversation
lerebear
force-pushed
the
lerebear/push-pwlqvypsrllr
branch
from
May 4, 2026 04:00
b63c191 to
24deab6
Compare
lerebear
force-pushed
the
lerebear/push-ouwoxyvtlyks
branch
from
May 4, 2026 21:50
9929fe5 to
fc48147
Compare
lerebear
force-pushed
the
lerebear/push-pwlqvypsrllr
branch
from
May 4, 2026 21:50
24deab6 to
a471d29
Compare
lerebear
force-pushed
the
lerebear/push-ouwoxyvtlyks
branch
from
May 4, 2026 21:54
fc48147 to
196607d
Compare
lerebear
force-pushed
the
lerebear/push-pwlqvypsrllr
branch
2 times, most recently
from
May 5, 2026 02:57
22e1e3a to
e7c4370
Compare
Merging this PR will not alter performance
Comparing Footnotes
|
lerebear
force-pushed
the
lerebear/push-ouwoxyvtlyks
branch
from
May 5, 2026 03:11
196607d to
aa1041d
Compare
lerebear
force-pushed
the
lerebear/push-pwlqvypsrllr
branch
from
May 5, 2026 03:11
e7c4370 to
5c640b9
Compare
lerebear
marked this pull request as ready for review
May 5, 2026 03:33
lerebear
requested review from
AlexWaygood,
BurntSushi,
Gankra,
MichaReiser,
carljm,
dcreager and
sharkdp
as code owners
May 5, 2026 03:33
MichaReiser
approved these changes
May 6, 2026
MichaReiser
left a comment
Member
There was a problem hiding this comment.
Nice. I left a few questions where I'm not sure how folding ranges is intended to behave.
lerebear
force-pushed
the
lerebear/push-pwlqvypsrllr
branch
from
May 7, 2026 03:23
5c640b9 to
757f62a
Compare
lerebear
force-pushed
the
lerebear/push-ouwoxyvtlyks
branch
from
May 7, 2026 03:23
aa1041d to
0f17962
Compare
lerebear
force-pushed
the
lerebear/push-pwlqvypsrllr
branch
from
May 7, 2026 03:41
757f62a to
d3ce232
Compare
lerebear
force-pushed
the
lerebear/push-pwlqvypsrllr
branch
from
May 7, 2026 05:23
d3ce232 to
0c8981e
Compare
carljm
added a commit
that referenced
this pull request
May 8, 2026
* main: (23 commits) Lazily initialize `UnionBuilder` when transforming a `Union` (#25050) [ty] Speed-up find-references by using multithreading for cross-file searches (#25042) Revert "[ty] Lazily initialize `UnionBuilder` when transforming a union type" (#25049) [ty] Make text-based find-references pre-filter identifier boundary aware (#25036) [ty] Fix cross-file find-references for keyword arguments (#25043) [ty] Include TypedDict type context when inferring mixed constructors (#25039) [ty] Include TypedDict type context when inferring string keys (#25037) [ty] Lazily initialize `UnionBuilder` when transforming a union type (#24929) [ty] Skip global search for references if identifier is not externally visible (#25033) [ty] Support narrowing on `__class__` checks (#24997) [ty] Bump ecosystem-analyzer (#25032) [ty] Include error context for overload consistency diagnostics (#24950) [ty] Check non-generic overload implementations (#24936) Filter overloads based on return type for ParamSpec mapping (#24769) [ty] Add versioned parameters for dataclass decorator (#25029) [ty] Project reachability constraints before narrowing (#24982) [ty] Emit folding ranges from the language server for multi-line block headers. (#24978) [ty] Allow ParamSpec specialization through unioned generic classes (#24826) [ty] Adjust start of block folding range to preserve visible header for character-precise LSP clients. (#24917) [ty] Use `as_i64` when comparing `IntLiteralType` instances (#25023) ...
lerebear
added a commit
that referenced
this pull request
May 13, 2026
…ine block headers." (#25118) This reverts #24978, introduced incorrect folding behaviour in several LSP clients. In so doing, we restore #24917 as the latest change to code folding. The trouble with the code we are now reverting was that, while it is spec-compliant to emit two distinct folding ranges that share an end/start line, the second range is typically made inaccessible by the first. After this PR, the folding ranges we emit from the language server will preserve a block header in it's entirety, but allow folding of the block body. In other words, this code: ```py def foo( bar, baz, ): qux = bar + baz return qux ``` Will fold like so: ```py def foo( bar, baz, ): ... ``` I will follow this up shortly with a change that allows folding a function in its entirety: #25113.
thejchap
pushed a commit
to thejchap/ruff
that referenced
this pull request
May 23, 2026
…k headers. (astral-sh#24978) <!-- Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull requests.) - Does this pull request include references to any relevant issues? - Does this PR follow our AI policy (https://github.com/astral-sh/.github/blob/main/AI_POLICY.md)? --> ## Summary As the final step of astral-sh/ty#3320, this builds on astral-sh#24917 to emit folding ranges for multi-line block headers in addition the the header-preserving block body ranges that we emitted previously. The result is that a method like this one: ```py def foo( x, y, ): z = x + y return z ``` Will no longer fold like: ```py def foo( x, y, ): ... ``` But will instead fold like: ```py def foo(...): ... ``` <!-- What's the purpose of the change? What does it do, and why? --> Closes astral-sh/ty#3320. ## Approach - I suspect it will be helpful to review this diff commit-by-commit. - I added a new `add_block_header_ranges` helper which emits folding ranges that are contained within a block header. That method, and the previous `add_block_body_range` method, are now both called by a method called `add_block_ranges` which we use at most callsites[^1]. - I also added an `add_expression_range` helper which ensures that the normal folding ranges that we emit for expressions (e.g. multi-line lists) do not duplicate or overlap with the new folds that we emit for block headers. - Note that we will now fold expressions within block headers slightly differently than we do outside them. Within a block header, an the delimiters of an expression will be retained (e.g., `x = [...]`), whereas outside a block header they will be elided (e.g., `x = ...`). This was an intentional choice to bias in the direction that I think we should be moving for these new folds: folds are more expressive with delimiters than without. I plan to also adjust the folding behaviour outside of block headers in a follow-up. [^1]: The remaining callsites also call `add_block_header_ranges` and `add_block_body_range`, but do so via `add_block_body_range_after_keyword`, which does some additional token scanning to account for block bodies that do not have a corresponding AST node. ## Test Plan Please see added/updated tests. <!-- How was it tested? -->
thejchap
pushed a commit
to thejchap/ruff
that referenced
this pull request
May 23, 2026
…ine block headers." (astral-sh#25118) This reverts astral-sh#24978, introduced incorrect folding behaviour in several LSP clients. In so doing, we restore astral-sh#24917 as the latest change to code folding. The trouble with the code we are now reverting was that, while it is spec-compliant to emit two distinct folding ranges that share an end/start line, the second range is typically made inaccessible by the first. After this PR, the folding ranges we emit from the language server will preserve a block header in it's entirety, but allow folding of the block body. In other words, this code: ```py def foo( bar, baz, ): qux = bar + baz return qux ``` Will fold like so: ```py def foo( bar, baz, ): ... ``` I will follow this up shortly with a change that allows folding a function in its entirety: astral-sh#25113.
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.
Summary
As the final step of astral-sh/ty#3320, this builds on #24917 to emit folding ranges for multi-line block headers in addition the the header-preserving block body ranges that we emitted previously.
The result is that a method like this one:
Will no longer fold like:
But will instead fold like:
Closes astral-sh/ty#3320.
Approach
add_block_header_rangeshelper which emits folding ranges that are contained within a block header. That method, and the previousadd_block_body_rangemethod, are now both called by a method calledadd_block_rangeswhich we use at most callsites1.add_expression_rangehelper which ensures that the normal folding ranges that we emit for expressions (e.g. multi-line lists) do not duplicate or overlap with the new folds that we emit for block headers.x = [...]), whereas outside a block header they will be elided (e.g.,x = ...). This was an intentional choice to bias in the direction that I think we should be moving for these new folds: folds are more expressive with delimiters than without. I plan to also adjust the folding behaviour outside of block headers in a follow-up.Test Plan
Please see added/updated tests.
Footnotes
The remaining callsites also call
add_block_header_rangesandadd_block_body_range, but do so viaadd_block_body_range_after_keyword, which does some additional token scanning to account for block bodies that do not have a corresponding AST node. ↩