feat: Introduce FoldingPreferences foundation and 'includeClosures' compatibility layer#298676
feat: Introduce FoldingPreferences foundation and 'includeClosures' compatibility layer#298676n-gist wants to merge 6 commits intomicrosoft:mainfrom
Conversation
|
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 We have currently no plans to invest in the built-in indentation based folding support. |
|
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:
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:
|
|
Update: after testing, it appears that the compatibility layer’s |
|
@aeschli, English is not my native language, so I apologize if my explanations were unclear. I would like to try to rephrase the main idea of the PR in case it is still not clear. This PR is not specifically about the } inclusion case. Its goal is to allow users to express folding preferences in a common, language-agnostic way, which may cover broader scenarios than this single case. It also introduces the foundation for an API that folding providers can use to consume these preferences in their logic. Existing built-in or extension providers are not required to support them, but the possibility is added. At the moment this part is only partially implemented and represented by placeholders. Completing it would involve passing preferences to providers at instantiation and wiring preference updates, or just passing them along with ranges recomputation requests. Having a common way to expose user preferences also enables a folding controller compatibility layer to exist. Its purpose is to compensate for providers that do not natively support certain preferences, allowing language-agnostic adjustments to approximate the desired behavior when users explicitly enable them. Because these adjustments must remain generic and performant, they may not produce perfect results for all languages or layouts. This limitation is made explicit to users by marking such preferences accordingly in their descriptions. I tried to document the change clearly to keep the design understandable and maintainable, and I’m happy to adjust the approach if needed. |
|
I claim there's no language agnostic way of describing what you want. Each language is different. Such a general setting will just cause more confusion than benefits
That's why I think the right answer is specific settings per folding provider. If you want the feature for typescript folding strategy, ask them to support it (or look into a PR, or come up with your own folding provider) and provide a setting. If you need something similar for HTML, go there. Markdown? Solve it there. Its the same with formatters and linters. |
|
@aeschli thank you for the feedback. I understand the concerns. The question of whether such architectural improvements are needed is exactly what I was hoping to clarify before completing this work. I would also like to point out that this was done taking into account all your clarifications in #3352 before starting.
That's true. However, the intention of this proposal is not to replace provider-specific logic, but to allow providers to optionally consume a set of generic user preferences if they find them meaningful. A provider can:
The proposal does not attempt to enforce a single interpretation across languages; it only provides a shared mechanism for expressing user intent. At the same time, there are concepts that appear across many languages. Closure delimiters are one example, but there are others as well, such as:
I think it is possible to imagine other aspects. I mentioned several possible future preferences in the interface description IEditorFoldingPreferencesShape Providers would remain free to interpret or ignore these preferences depending on the language.
Even partial support could still be useful. In some cases it may help even when no providers implement native support. For example, the If modifying provider outputs in this way is not the correct approach, I would be interested to know what the preferred solution would be. Currently there does not seem to be a way to implement a provider that takes another provider’s output and post-processes it. At the same time, if a provider later decides to support the preference natively, it can declare support for it and the corresponding compatibility adjustment will be skipped. This also provides an easy path for providers under development to adopt an existing preference instead of introducing a new provider-specific setting.
Preferences are not applied as a single generalized concept; support is granular per preference. Changing existing preferences would require some care, but adding new values to a preference or introducing new preferences should not break existing implementations. This is a good point, though — it would be worth documenting that preference domains may evolve over time by adding new values, so providers should implement value branching in a way that remains forward-compatible. I would have considered adding an
Provider-specific settings are indeed often the best solution. However, creating provider-specific settings also has a cost. When a preference can reasonably be expressed in a language-agnostic way, defining it once in VS Code makes it easier for providers to adopt it simply by consuming an existing flag. This can make feature requests less ad-hoc and more predictable for extension developers, while still leaving them completely free to ignore or decline support if it does not make sense for their language. If this direction still does not align with the intended architecture of folding in VS Code, I completely understand. My goal with this PR was mainly to explore whether a shared preference mechanism could help reduce duplicated solutions while keeping the responsibility for semantics with folding providers. |
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
includeClosurescompatibility layer.Clarifies responsibility for issues related to folding behavior.
Changes
editor.foldingPreferencesoptionAdds a structured API for specifying user folding preferences.
FoldingPreferencesCapabilitiesinterfaceAllows folding providers to indicate which preferences they natively support.
RangeProviderupdateExtends the interface to include the new
capabilitiesproperty.IndentRangeProviderandSyntaxRangeProviderupdatesAdds placeholder
capabilitiesobjects to conform to the updated interface.FoldingPreferencesCompatibilitylayerImplements a compatibility layer to adjust folding behavior for preferences not natively supported by providers.
FoldingControllerintegrationInjects the compatibility layer into folding model computation and updates.
CompatibilityAdjusterIncludeClosuresAdds initial support for the
includeClosurespreference in the compatibility pipeline.Backward and future compatibility
editor.foldingPreferencesis explicitly set.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. 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.
includeClosurespreference, or why folding provider outputs actually need to be improvedConsider some simple cases and how different providers handle inclusion of closure delimiters in folding regions, comparing
cssvshtml(CSS closures are}; HTML closures are tags like</div>):examples
This is perfectly consistent.
.noregion2 { }Consistent — does not produce folding regions. However:
CSS produces a folding region, while HTML does not — inconsistent across languages, but more significantly:
vs.
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
autoand exists for a reason, but the user may want to always include or always exclude the closure from regions.The current
includeClosurescompatibility 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
includeClosurespreference inIndentRangeProviderand 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.