Skip to content

feat: Introduce FoldingPreferences foundation and 'includeClosures' compatibility layer#298676

Open
n-gist wants to merge 6 commits intomicrosoft:mainfrom
n-gist:feat-foldingPreferences
Open

feat: Introduce FoldingPreferences foundation and 'includeClosures' compatibility layer#298676
n-gist wants to merge 6 commits intomicrosoft:mainfrom
n-gist:feat-foldingPreferences

Conversation

@n-gist
Copy link
Contributor

@n-gist n-gist commented Mar 2, 2026

This change lays the groundwork for making folding providers aware of user-defined folding preferences.

Closes #3352 and redirects future related issues to language folding providers or to the includeClosures compatibility layer.
Clarifies responsibility for issues related to folding behavior.


Changes

  • editor.foldingPreferences option
    Adds a structured API for specifying user folding preferences.

  • FoldingPreferencesCapabilities interface
    Allows folding providers to indicate which preferences they natively support.

  • RangeProvider update
    Extends the interface to include the new capabilities property.

  • IndentRangeProvider and SyntaxRangeProvider updates
    Adds placeholder capabilities objects to conform to the updated interface.

  • FoldingPreferencesCompatibility layer
    Implements a compatibility layer to adjust folding behavior for preferences not natively supported by providers.

  • FoldingController integration
    Injects the compatibility layer into folding model computation and updates.

  • CompatibilityAdjusterIncludeClosures
    Adds initial support for the includeClosures preference in the compatibility pipeline.


Backward and future compatibility

  • Existing folding behavior remains unchanged unless editor.foldingPreferences is explicitly set.
  • Providers without native support for preferences continue to function through the compatibility layer.
  • When providers declare native support for a preference, the corresponding compatibility layer adjustment is disabled, allowing the provider to take full control of the user preference's effect.

Reasons
tl;dr
Initially, this change aimed to solve #3352. The plan was to add a single setting and implement support for it directly in VS Code built-in language folding providers. CSS was chosen first due to its simple syntax, with blocks always defined by `{ }` braces. However, this immediately became a roadblock, as its implementation resides in a separate repository and would require introducing an agreed-upon API in the https://github.com/microsoft/vscode-css-languageservice.git.?target=https://github.com While possible in the future, it is not a good starting point for changes.

The next idea was to develop a post-processing layer applied on top of incoming folding regions as a temporary patch for a whitelist of languages whose folding region implementation is suitable for this. This was done, and the plan was to iterate over languages and popular extensions to define the whitelist. The idea was to keep a language on the list until its provider adopted the user setting. However, the list would be large and permanent, since the likelihood of all existing providers supporting the preference is zero. This also adds maintenance costs, as any addition of native support by providers would require removal from the whitelist.

Switching to a blacklist approach, starting from an empty list and adding languages as their folding providers evolve, is a better strategy. But this blacklist should track extensions rather than languages. Additionally, new preferences may be added later. Maintaining separate lists for each preference is not viable. The original plan aimed to safeguard users from incorrect folding adjustments, but it relied on maintaining extensive language or extension lists, which proved to be an overengineered solution. A cleaner approach is to have folding providers be aware of user preferences and indicate which preferences they support. When a provider declares support for a specific preference, the compatibility patch should not be applied, even if the preference is set.

The correct approach seems to be the following:
The responsibility for enabling preference-related compatibility adjustments for providers that lack native support ultimately lies with the user.
No predefined language lists or unnecessary maintenance costs are needed, and such preferences should be marked with * in their descriptions.

Ultimately, the effect is intentionally activated by the user, and if it produces an undesired result, the user can:
a) Not use the preference for the given language.
b) Adapt the code layout.
c) Request native support for the preference from language extension developers, provided VS Code offers an API for that.

After these conclusions, the idea crystallized into the architectural modifications presented here. This explains why the original issue persisted so long despite appearing simple to fix.

includeClosures preference, or why folding provider outputs actually need to be improved

Consider some simple cases and how different providers handle inclusion of closure delimiters in folding regions, comparing css vs html (CSS closures are }; HTML closures are tags like </div>):

examples
.unfolded1 {
    height: 0;
}

.folded1 {
}
<div>
    text
</div>

<div>
</div>

This is perfectly consistent.

.noregion2 {
}
<div>
</div>

Consistent — does not produce folding regions. However:

.unfolded3 {
height:0;}
<div>
text</div>

CSS produces a folding region, while HTML does not — inconsistent across languages, but more significantly:

.unfolded1 {
    height: 0;
}

.folded1 {
}

vs.

.unfolded3 {
height:0;}

.folded3 {

This is inconsistent even within CSS regarding inclusion of the closure delimiter.

This example may represent poor code layout, and it is unclear whether CSS handles this intentionally. However, it indicates that user preference can be meaningful. Current behavior can be mapped to auto and exists for a reason, but the user may want to always include or always exclude the closure from regions.

The current includeClosures compatibility implementation is simple. It adds one line to a region if doing so does not overlap another region's start. Clearly, it does not handle all cases correctly (e.g. case3). However, the compatibility layer cannot provide perfect behavior for all languages and providers; correct behavior is achievable only through native support.


What's next

If this change is confirmed as viable, I would like to try to implement native support for the includeClosures preference in IndentRangeProvider and explore adding it to the TypeScript provider. This would allow folding providers to access user preferences directly and to declare which preferences they natively support.

@aeschli
Copy link
Contributor

aeschli commented Mar 2, 2026

It's already possible for an extension to implement custom folding providers such as a folding provider that also includes the line of the closing }.
I think https://marketplace.visualstudio.com/items?itemName=MohammadBaqer.better-folding already does that.

We have currently no plans to invest in the built-in indentation based folding support.

@n-gist
Copy link
Contributor Author

n-gist commented Mar 3, 2026

@aeschli

It’s true that extensions can implement custom folding providers.

However, in practice this approach has limitations. Better Folding appears to be unmaintained and currently has open issues preventing it from working reliably in recent versions of VS Code. There are also other extensions (e.g. Explicit Folding) that attempt to solve similar problems using pattern-based approaches.

The broader issue is that these extensions replace the language’s native folding provider and implement their own region detection logic. This typically means:

  • Re-implementing folding logic per language
  • Relying on indentation or pattern matching rather than syntax-aware parsing
  • Losing tight integration with language services

As a result, while such extensions can help in specific scenarios, they cannot fully preserve syntax-aware folding behavior across languages and edge cases.

The goal of this PR is different: it does not aim to replace folding providers, but to provide a structured mechanism for providers to become aware of user preferences, while preserving native syntax-aware folding. The compatibility layer exists only as a fallback when providers do not declare native support.

At a higher level, this PR introduces a structured flow for preference-aware folding:

  • A language folding provider produces fold ranges according to its existing logic.

  • If a user explicitly enables a folding preference, that preference becomes part of the folding configuration.

  • The preference is made available to the folding provider.

  • During fold range recomputation, FoldingController passes the produced ranges through a compatibility layer.

  • The compatibility layer checks whether the provider declares native support for the given preference.

    • If native support is declared, the ranges are left untouched.
    • If not, and if a compatibility adjuster exists for that preference, it applies a corresponding post-processing adjustment.
  • If no folding preferences are explicitly set, behavior remains unchanged.

@n-gist
Copy link
Contributor Author

n-gist commented Mar 3, 2026

IndentRangeProvider is not the primary target of this change. I mentioned it only to indicate that I am ready to implement native support for the includeClosures preference there as a follow-up. If such changes are not desired, it may be left without native support.

Update: after testing, it appears that the compatibility layer’s includeClosures: true adjustment already works correctly for IndentRangeProvider, so adding native support there is not strictly necessary. Native handling would mainly avoid an extra ranges copy in this case, but it does not affect the resulting behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[folding] Collapse ending brace to the same line

2 participants