diff --git a/package.json b/package.json index 4544bda7d2..0104f56f5a 100644 --- a/package.json +++ b/package.json @@ -478,6 +478,11 @@ "default": true, "description": "Enables syntax based code folding. When disabled, the default indentation based code folding is used." }, + "powershell.codeFolding.showLastLine": { + "type": "boolean", + "default": true, + "description": "Shows the last line of a folded section similar to the default VSCode folding style. When disabled, the entire folded region is hidden." + }, "powershell.codeFormatting.preset": { "type": "string", "enum": [ diff --git a/src/features/Folding.ts b/src/features/Folding.ts index 1e68f3b33b..6b99ab0df2 100644 --- a/src/features/Folding.ts +++ b/src/features/Folding.ts @@ -157,7 +157,10 @@ class LineNumberRange { * Creates a vscode.FoldingRange object based on this object * @returns A Folding Range object for use with the Folding Provider */ - public toFoldingRange(): vscode.FoldingRange { + public toFoldingRange(settings: Settings.ISettings): vscode.FoldingRange { + if (settings.codeFolding && settings.codeFolding.showLastLine) { + return new vscode.FoldingRange(this.startline, this.endline - 1, this.rangeKind); + } return new vscode.FoldingRange(this.startline, this.endline, this.rangeKind); } } @@ -252,15 +255,26 @@ export class FoldingProvider implements vscode.FoldingRangeProvider { return 0; }); + const settings = this.currentSettings(); return foldableRegions // It's possible to have duplicate or overlapping ranges, that is, regions which have the same starting // line number as the previous region. Therefore only emit ranges which have a different starting line // than the previous range. .filter((item, index, src) => index === 0 || item.startline !== src[index - 1].startline) // Convert the internal representation into the VSCode expected type - .map((item) => item.toFoldingRange()); + .map((item) => item.toFoldingRange(settings)); } + /** + * A helper to return the current extension settings. This helper is primarily for use by unit testing so + * that extension settings can be mocked. + * - The settings cannot be set in the constructor as the settings should be evalauted on each folding request + * so that setting changes are immediate, i.e. they do not require an extension reload + * - The method signature for provideFoldingRanges can not be changed as it is explicitly set in the VSCode API, + * therefore the settings can not be passed in the method call, which would be preferred + */ + public currentSettings(): Settings.ISettings { return Settings.load(); } + /** * Given a start and end textmate scope name, find matching grammar tokens * and pair them together. Uses a simple stack to take into account nested regions. diff --git a/src/settings.ts b/src/settings.ts index 42fe34f803..c7a87ffef7 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -31,6 +31,7 @@ export interface IBugReportingSettings { export interface ICodeFoldingSettings { enable?: boolean; + showLastLine?: boolean; } export interface ICodeFormattingSettings { @@ -116,6 +117,7 @@ export function load(): ISettings { const defaultCodeFoldingSettings: ICodeFoldingSettings = { enable: true, + showLastLine: false, }; const defaultCodeFormattingSettings: ICodeFormattingSettings = { diff --git a/test/features/folding.test.ts b/test/features/folding.test.ts index 8f94f96c65..02c15510ad 100644 --- a/test/features/folding.test.ts +++ b/test/features/folding.test.ts @@ -7,6 +7,7 @@ import * as path from "path"; import * as vscode from "vscode"; import { DocumentSelector } from "vscode-languageclient"; import * as folding from "../../src/features/Folding"; +import * as Settings from "../../src/settings"; import { MockLogger } from "../test_utils"; const fixturePath = path.join(__dirname, "..", "..", "..", "test", "fixtures"); @@ -22,6 +23,13 @@ function assertFoldingRegions(result, expected): void { assert.equal(result.length, expected.length); } +// Wrap the FoldingProvider class with our own custom settings for testing +class CustomSettingFoldingProvider extends folding.FoldingProvider { + public customSettings: Settings.ISettings = Settings.load(); + // Overridde the super currentSettings method with our own custom test settings + public currentSettings(): Settings.ISettings { return this.customSettings; } +} + suite("Features", () => { suite("Folding Provider", async () => { @@ -38,21 +46,21 @@ suite("Features", () => { suite("For a single document", async () => { const expectedFoldingRegions = [ - { start: 0, end: 4, kind: 3 }, - { start: 1, end: 3, kind: 1 }, - { start: 10, end: 15, kind: 1 }, - { start: 16, end: 60, kind: null }, - { start: 17, end: 22, kind: 1 }, - { start: 23, end: 26, kind: null }, - { start: 28, end: 31, kind: null }, - { start: 35, end: 37, kind: 1 }, - { start: 39, end: 49, kind: 3 }, - { start: 41, end: 45, kind: 3 }, - { start: 51, end: 53, kind: null }, - { start: 56, end: 59, kind: null }, - { start: 64, end: 66, kind: 1 }, - { start: 67, end: 72, kind: 3 }, - { start: 68, end: 70, kind: 1 }, + { start: 0, end: 3, kind: 3 }, + { start: 1, end: 2, kind: 1 }, + { start: 10, end: 14, kind: 1 }, + { start: 16, end: 59, kind: null }, + { start: 17, end: 21, kind: 1 }, + { start: 23, end: 25, kind: null }, + { start: 28, end: 30, kind: null }, + { start: 35, end: 36, kind: 1 }, + { start: 39, end: 48, kind: 3 }, + { start: 41, end: 44, kind: 3 }, + { start: 51, end: 52, kind: null }, + { start: 56, end: 58, kind: null }, + { start: 64, end: 65, kind: 1 }, + { start: 67, end: 71, kind: 3 }, + { start: 68, end: 69, kind: 1 }, ]; test("Can detect all of the foldable regions in a document with CRLF line endings", async () => { @@ -83,9 +91,31 @@ suite("Features", () => { assertFoldingRegions(result, expectedFoldingRegions); }); + suite("Where showLastLine setting is false", async () => { + const customprovider = (new CustomSettingFoldingProvider(psGrammar)); + customprovider.customSettings.codeFolding.showLastLine = false; + + test("Can detect all foldable regions in a document", async () => { + // Integration test against the test fixture 'folding-lf.ps1' that contains + // all of the different types of folding available + const uri = vscode.Uri.file(path.join(fixturePath, "folding-lf.ps1")); + const document = await vscode.workspace.openTextDocument(uri); + const result = await customprovider.provideFoldingRanges(document, null, null); + + // Incrememnt the end line of the expected regions by one as we will + // be hiding the last line + const expectedLastLineRegions = expectedFoldingRegions.map( (item) => { + item.end++; + return item; + }); + + assertFoldingRegions(result, expectedLastLineRegions); + }); + }); + test("Can detect all of the foldable regions in a document with mismatched regions", async () => { const expectedMismatchedFoldingRegions = [ - { start: 2, end: 4, kind: 3 }, + { start: 2, end: 3, kind: 3 }, ]; // Integration test against the test fixture 'folding-mismatch.ps1' that contains @@ -99,8 +129,8 @@ suite("Features", () => { test("Does not return duplicate or overlapping regions", async () => { const expectedMismatchedFoldingRegions = [ - { start: 1, end: 2, kind: null }, - { start: 2, end: 4, kind: null }, + { start: 1, end: 1, kind: null }, + { start: 2, end: 3, kind: null }, ]; // Integration test against the test fixture 'folding-mismatch.ps1' that contains