Repository navigation
Automatically fold inactive regions #14796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Colen Garoutte-Carson (Colengms)
merged 19 commits into
main
from
dev/coleng/auto_fold_inactive_regions
Oct 1, 2026
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
0d90ac0
Automatically fold inactive regions
Colengms 2a330dd
Address AI PR feedback
Colengms c321d62
Address AI PR feedback
Colengms 089ccc5
Address AI PR feedback
Colengms 83dcb22
Merge branch 'main' of https://github.com/microsoft/vscode-cpptools i…
Colengms 6178b70
Add UnfoldAllInactiveRegions
Colengms 817f796
Address PR feedback
Colengms beb3892
Preserve inactive fold state across client recovery
Colengms cd241ac
Handle empty inactive-region auto-fold results
Colengms 8c864c4
Refresh folding ranges when enabling code folding
Colengms c5fd362
Refresh cached inactive-region decorations
Colengms cd6dcc8
Track pending inactive folds per editor
Colengms fc580b3
Stabilize split-editor folding test focus
Colengms 50cf1ff
Use visible ranges in split-editor folding test
Colengms 8270923
Merge branch 'main' into dev/coleng/auto_fold_inactive_regions
Colengms 0da8520
Avoid overloading dimInactiveRegions setting. (requires native side c…
Colengms 9ca0eee
Merge branch 'dev/coleng/auto_fold_inactive_regions' of https://githu…
Colengms 5242a05
Update min test version
Colengms 49a5651
Merge branch 'main' into dev/coleng/auto_fold_inactive_regions
Colengms File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
Large diffs are not rendered by default.
Oops, something went wrong.
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| /* -------------------------------------------------------------------------------------------- | ||
| * Copyright (c) Microsoft Corporation. All Rights Reserved. | ||
| * See 'LICENSE' in the project root for license information. | ||
| * ------------------------------------------------------------------------------------------ */ | ||
|
|
||
| export interface InactiveRegion { | ||
| startLine: number; | ||
| startColumn: number; | ||
| endLine: number; | ||
| endColumn: number; | ||
| } | ||
|
|
||
| interface InactiveRegionSet { | ||
| regions: InactiveRegion[]; | ||
| isComplete: boolean; | ||
| } | ||
|
|
||
| export class InactiveRegionStore { | ||
| private readonly regionSets = new Map<string, InactiveRegionSet>(); | ||
|
|
||
| public update(uri: string, regions: InactiveRegion[], startNewSet: boolean, isComplete: boolean): void { | ||
| let regionSet: InactiveRegionSet | undefined = this.regionSets.get(uri); | ||
| if (startNewSet || regionSet === undefined) { | ||
| regionSet = { | ||
| regions: [], | ||
| isComplete: false | ||
| }; | ||
| this.regionSets.set(uri, regionSet); | ||
| } | ||
|
|
||
| for (const region of regions) { | ||
| regionSet.regions.push(region); | ||
| } | ||
| regionSet.isComplete = isComplete; | ||
| } | ||
|
|
||
| public getComplete(uri: string): readonly InactiveRegion[] | undefined { | ||
| const regionSet: InactiveRegionSet | undefined = this.regionSets.get(uri); | ||
| return regionSet?.isComplete ? regionSet.regions : undefined; | ||
| } | ||
|
|
||
| public forEach(callback: (regions: readonly InactiveRegion[], uri: string) => void): void { | ||
| this.regionSets.forEach((regionSet, uri) => callback(regionSet.regions, uri)); | ||
| } | ||
|
|
||
| public clear(): void { | ||
| this.regionSets.clear(); | ||
| } | ||
|
|
||
| public delete(uri: string): void { | ||
| this.regionSets.delete(uri); | ||
| } | ||
| } | ||
|
|
||
| export function getInactiveRegionStartLines(regions: readonly InactiveRegion[]): number[] { | ||
| return [...new Set(regions.map(region => region.startLine))].sort((a, b) => a - b); | ||
| } |
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| { | ||
| "minBinaryVersion": "1.33.0" | ||
| "minBinaryVersion": "1.35.3" | ||
| } | ||
139 changes: 139 additions & 0 deletions
139
Extension/test/scenarios/MultirootDeadlockTest/tests/inactiveFolding.test.ts
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| /* -------------------------------------------------------------------------------------------- | ||
| * Copyright (c) Microsoft Corporation. All Rights Reserved. | ||
| * See 'LICENSE' in the project root for license information. | ||
| * ------------------------------------------------------------------------------------------ */ | ||
|
|
||
| /* eslint-disable @typescript-eslint/triple-slash-reference */ | ||
| /// <reference path="../../../../vscode.d.ts" /> | ||
|
|
||
| import * as assert from 'assert'; | ||
| import { suite } from 'mocha'; | ||
| import * as path from 'path'; | ||
| import * as sinon from 'sinon'; | ||
| import * as vscode from 'vscode'; | ||
| import * as api from 'vscode-cpptools'; | ||
| import * as apit from 'vscode-cpptools/out/testApi'; | ||
| import * as extension from '../../../../src/LanguageServer/extension'; | ||
| import * as testHelpers from '../../../common/testHelpers'; | ||
|
|
||
| suite("Inactive region folding in a multi-root workspace", function (): void { | ||
| let testHook: apit.CppToolsTestHook; | ||
| let firstWorkspaceFolder: vscode.WorkspaceFolder; | ||
| let workspaceFolder: vscode.WorkspaceFolder; | ||
|
|
||
| suiteSetup(async function (): Promise<void> { | ||
| await testHelpers.activateCppExtension(); | ||
| const cpptools: apit.CppToolsTestApi = await apit.getCppToolsTestApi(api.Version.latest) | ||
| ?? assert.fail("Could not get CppToolsTestApi"); | ||
| testHook = cpptools.getTestHook(); | ||
| firstWorkspaceFolder = vscode.workspace.workspaceFolders?.[0] | ||
| ?? assert.fail("First workspace folder is unavailable"); | ||
| workspaceFolder = vscode.workspace.workspaceFolders?.[1] | ||
| ?? assert.fail("Second workspace folder is unavailable"); | ||
| }); | ||
|
|
||
| suiteTeardown(function (): void { | ||
| testHook.dispose(); | ||
| }); | ||
|
|
||
| test("routes manual folding operations by URI during a workspace client switch", async () => { | ||
| const configuration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("C_Cpp", workspaceFolder.uri); | ||
| const previousCodeFoldingValue: string | undefined = configuration.inspect<string>("codeFolding")?.globalValue; | ||
| await configuration.update("codeFolding", "enabled", vscode.ConfigurationTarget.Global); | ||
| let releaseEditorChange: (() => void) | undefined; | ||
| let editorChangeStub: sinon.SinonStub | undefined; | ||
| let editorChangePromise: Promise<void> | undefined; | ||
| let editor: vscode.TextEditor | undefined; | ||
| let firstEditor: vscode.TextEditor | undefined; | ||
| try { | ||
| editor = await openFileAndWaitForIntelliSense(); | ||
| await vscode.commands.executeCommand("editor.unfoldAll"); | ||
|
|
||
| const firstDocument: vscode.TextDocument = await vscode.workspace.openTextDocument( | ||
| path.join(firstWorkspaceFolder.uri.fsPath, "test.cpp")); | ||
| firstEditor = await vscode.window.showTextDocument(firstDocument); | ||
| await extension.clients.didChangeActiveEditor(firstEditor); | ||
| const firstClient = extension.clients.ActiveClient; | ||
| const owner = extension.clients.getClientFor(editor.document.uri); | ||
| assert.notStrictEqual(firstClient, owner); | ||
|
|
||
| const pendingEditorChange: Promise<void> = new Promise(resolve => releaseEditorChange = resolve); | ||
| editorChangeStub = sinon.stub(owner, "didChangeActiveEditor").returns(pendingEditorChange); | ||
| const activeEditor: vscode.TextEditor = await vscode.window.showTextDocument(editor.document, editor.viewColumn, false); | ||
| editorChangePromise = extension.clients.didChangeActiveEditor(activeEditor); | ||
| assert.strictEqual(extension.clients.ActiveClient, firstClient); | ||
|
|
||
| await vscode.commands.executeCommand("C_Cpp.FoldAllInactiveRegions"); | ||
| await assertInactiveBranchIsFolded(editor); | ||
|
|
||
| await vscode.commands.executeCommand("C_Cpp.UnfoldAllInactiveRegions"); | ||
| await assertInactiveBranchIsUnfolded(editor); | ||
| } finally { | ||
| releaseEditorChange?.(); | ||
| await editorChangePromise; | ||
| editorChangeStub?.restore(); | ||
| if (editor) { | ||
| await closeEditor(editor); | ||
| } | ||
| if (firstEditor) { | ||
| await closeEditor(firstEditor); | ||
| } | ||
| await testHelpers.delay(150); | ||
| await configuration.update("codeFolding", previousCodeFoldingValue, vscode.ConfigurationTarget.Global); | ||
| } | ||
| }); | ||
|
|
||
| async function closeEditor(editor: vscode.TextEditor): Promise<void> { | ||
| await vscode.window.showTextDocument(editor.document, editor.viewColumn, false); | ||
| await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); | ||
| } | ||
|
|
||
| async function assertInactiveBranchIsFolded(editor: vscode.TextEditor): Promise<void> { | ||
| let line: number = 24; | ||
| for (let i: number = 0; i < 20; ++i) { | ||
| const activeEditor: vscode.TextEditor = await vscode.window.showTextDocument(editor.document, editor.viewColumn, false); | ||
| activeEditor.selection = new vscode.Selection(24, 0, 24, 0); | ||
| await vscode.commands.executeCommand("cursorMove", { to: "down", by: "wrappedLine", value: 1 }); | ||
| line = activeEditor.selection.active.line; | ||
| if (line === 28) { | ||
| return; | ||
| } | ||
| await testHelpers.delay(50); | ||
| } | ||
|
|
||
| assert.strictEqual(line, 28); | ||
| } | ||
|
|
||
| async function assertInactiveBranchIsUnfolded(editor: vscode.TextEditor): Promise<void> { | ||
| const activeEditor: vscode.TextEditor = await vscode.window.showTextDocument(editor.document, editor.viewColumn, false); | ||
| activeEditor.selection = new vscode.Selection(24, 0, 24, 0); | ||
| await vscode.commands.executeCommand("cursorMove", { to: "down", by: "wrappedLine", value: 1 }); | ||
| assert.strictEqual(activeEditor.selection.active.line, 25); | ||
| } | ||
|
|
||
| function waitForIntelliSenseReady(fileName: string): Promise<void> { | ||
| return new Promise<void>((resolve, reject) => { | ||
| const timeout: NodeJS.Timeout = setTimeout(() => { | ||
| listener.dispose(); | ||
| reject(new Error(`Timed out waiting for IntelliSense for ${fileName}`)); | ||
| }, testHelpers.defaultTimeout); | ||
| const listener: vscode.Disposable = testHook.IntelliSenseStatusChanged(result => { | ||
| if (result.filename === fileName && result.status === apit.Status.IntelliSenseReady) { | ||
| clearTimeout(timeout); | ||
| listener.dispose(); | ||
| resolve(); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| async function openFileAndWaitForIntelliSense(): Promise<vscode.TextEditor> { | ||
| const fileName: string = "code_folding.cpp"; | ||
| const filePath: string = path.join(workspaceFolder.uri.fsPath, fileName); | ||
| const ready: Promise<void> = waitForIntelliSenseReady(fileName); | ||
| const document: vscode.TextDocument = await vscode.workspace.openTextDocument(filePath); | ||
| const editor: vscode.TextEditor = await vscode.window.showTextDocument(document); | ||
| await ready; | ||
| return editor; | ||
| } | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.