From bce608e4bf1df5325c68d6f0fb2b5fdefcbebf02 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 22 Nov 2017 17:27:05 +0100 Subject: [PATCH] [folding] collapse block comment shortcut. Fixes #11524 & fixes #4670 --- src/vs/editor/contrib/folding/folding.ts | 28 ++++++++- src/vs/editor/contrib/folding/foldingModel.ts | 61 +++++++------------ .../editor/contrib/folding/foldingRanges.ts | 40 ++++++++++++ .../contrib/folding/test/foldingModel.test.ts | 44 ++++++++++++- 4 files changed, 133 insertions(+), 40 deletions(-) diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 7f1d4e94571..56c2fcec5da 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -8,6 +8,7 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; +import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { RunOnceScheduler, Delayer } from 'vs/base/common/async'; import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -15,7 +16,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { ScrollType, IModel, IEditorContribution } from 'vs/editor/common/editorCommon'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, registerInstantiatedEditorAction } from 'vs/editor/browser/editorExtensions'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { FoldingModel, setCollapseStateAtLevel, CollapseMemento, setCollapseStateLevelsDown, setCollapseStateLevelsUp } from 'vs/editor/contrib/folding/foldingModel'; +import { FoldingModel, setCollapseStateAtLevel, CollapseMemento, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateForMatchingLines } from 'vs/editor/contrib/folding/foldingModel'; import { FoldingDecorationProvider } from './foldingDecorations'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; @@ -508,6 +509,30 @@ class FoldRecursivelyAction extends FoldingAction { } } +class FoldAllBlockCommentsAction extends FoldingAction { + + constructor() { + super({ + id: 'editor.foldAllBlockComments', + label: nls.localize('foldAllBlockComments.label', "Fold All Block Comments"), + alias: 'Fold All Block Comments', + precondition: null, + kbOpts: { + kbExpr: EditorContextKeys.textFocus, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_SLASH) + } + }); + } + + invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void { + let comments = LanguageConfigurationRegistry.getComments(editor.getModel().getLanguageIdentifier().id); + if (comments && comments.blockCommentStartToken) { + let regExp = new RegExp('^\\s*' + escapeRegExpCharacters(comments.blockCommentStartToken)); + setCollapseStateForMatchingLines(foldingModel, regExp, true); + } + } +} + class FoldAllAction extends FoldingAction { constructor() { @@ -568,6 +593,7 @@ registerEditorAction(FoldAction); registerEditorAction(FoldRecursivelyAction); registerEditorAction(FoldAllAction); registerEditorAction(UnfoldAllAction); +registerEditorAction(FoldAllBlockCommentsAction); for (let i = 1; i <= 9; i++) { registerInstantiatedEditorAction( diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index 63c883a5857..ea3d547a526 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -5,7 +5,7 @@ import { IModel, IModelDecorationOptions, IModelDeltaDecoration, IModelDecorationsChangeAccessor } from 'vs/editor/common/editorCommon'; import Event, { Emitter } from 'vs/base/common/event'; -import { FoldingRanges, ILineRange } from './foldingRanges'; +import { FoldingRanges, ILineRange, FoldingRegion } from './foldingRanges'; export interface IDecorationProvider { getDecorationOption(isCollapsed: boolean): IModelDecorationOptions; @@ -167,7 +167,7 @@ export class FoldingModel { let index = this._ranges.findRange(lineNumber); let level = 1; while (index >= 0) { - let current = new FoldingRegion(this._ranges, index); + let current = this._ranges.toRegion(index); if (!filter || filter(current, level)) { result.push(current); } @@ -182,7 +182,7 @@ export class FoldingModel { if (this._ranges) { let index = this._ranges.findRange(lineNumber); if (index >= 0) { - return new FoldingRegion(this._ranges, index); + return this._ranges.toRegion(index); } } return null; @@ -195,7 +195,7 @@ export class FoldingModel { let index = region ? region.regionIndex + 1 : 0; let endLineNumber = region ? region.endLineNumber : Number.MAX_VALUE; for (let i = index, len = this._ranges.length; i < len; i++) { - let current = new FoldingRegion(this._ranges, i); + let current = this._ranges.toRegion(i); if (this._ranges.getStartLineNumber(i) < endLineNumber) { if (trackLevel) { while (levelStack.length > 0 && !current.containedBy(levelStack[levelStack.length - 1])) { @@ -217,41 +217,7 @@ export class FoldingModel { } -export class FoldingRegion { - constructor(private ranges: FoldingRanges, private index: number) { - } - - public get startLineNumber() { - return this.ranges.getStartLineNumber(this.index); - } - - public get endLineNumber() { - return this.ranges.getEndLineNumber(this.index); - } - - public get regionIndex() { - return this.index; - } - - public get parentIndex() { - return this.ranges.getParentIndex(this.index); - } - - public get isCollapsed() { - return this.ranges.isCollapsed(this.index); - } - - containedBy(range: ILineRange): boolean { - return range.startLineNumber <= this.startLineNumber && range.endLineNumber >= this.endLineNumber; - } - containsLine(lineNumber: number) { - return this.startLineNumber <= lineNumber && lineNumber <= this.endLineNumber; - } - hidesLine(lineNumber: number) { - return this.startLineNumber < lineNumber && lineNumber <= this.endLineNumber; - } -} /** * Collapse or expand the regions at the given locations including all children. @@ -306,4 +272,23 @@ export function setCollapseStateAtLevel(foldingModel: FoldingModel, foldLevel: n let filter = (region: FoldingRegion, level: number) => level === foldLevel && region.isCollapsed !== doCollapse && !blockedLineNumbers.some(line => region.containsLine(line)); let toToggle = foldingModel.getRegionsInside(null, filter); foldingModel.toggleCollapseState(toToggle); +} + +/** + * Folds all regions for which the lines start with a given regex + * @param foldingModel the folding model + */ +export function setCollapseStateForMatchingLines(foldingModel: FoldingModel, regExp: RegExp, doCollapse: boolean): void { + let editorModel = foldingModel.textModel; + let ranges = foldingModel.ranges; + let toToggle = []; + for (let i = ranges.length - 1; i >= 0; i--) { + if (doCollapse !== ranges.isCollapsed(i)) { + let startLineNumber = ranges.getStartLineNumber(i); + if (regExp.test(editorModel.getLineContent(startLineNumber))) { + toToggle.push(ranges.toRegion(i)); + } + } + } + foldingModel.toggleCollapseState(toToggle); } \ No newline at end of file diff --git a/src/vs/editor/contrib/folding/foldingRanges.ts b/src/vs/editor/contrib/folding/foldingRanges.ts index bbe244de30a..666e174f1b2 100644 --- a/src/vs/editor/contrib/folding/foldingRanges.ts +++ b/src/vs/editor/contrib/folding/foldingRanges.ts @@ -84,6 +84,10 @@ export class FoldingRanges { } } + public toRegion(index: number): FoldingRegion { + return new FoldingRegion(this, index); + } + public getParentIndex(index: number) { this.ensureParentIndices(); let parent = ((this._startIndexes[index] & MASK_INDENT) >>> 24) + ((this._endIndexes[index] & MASK_INDENT) >>> 16); @@ -130,4 +134,40 @@ export class FoldingRanges { } return -1; } +} + +export class FoldingRegion { + + constructor(private ranges: FoldingRanges, private index: number) { + } + + public get startLineNumber() { + return this.ranges.getStartLineNumber(this.index); + } + + public get endLineNumber() { + return this.ranges.getEndLineNumber(this.index); + } + + public get regionIndex() { + return this.index; + } + + public get parentIndex() { + return this.ranges.getParentIndex(this.index); + } + + public get isCollapsed() { + return this.ranges.isCollapsed(this.index); + } + + containedBy(range: ILineRange): boolean { + return range.startLineNumber <= this.startLineNumber && range.endLineNumber >= this.endLineNumber; + } + containsLine(lineNumber: number) { + return this.startLineNumber <= lineNumber && lineNumber <= this.endLineNumber; + } + hidesLine(lineNumber: number) { + return this.startLineNumber < lineNumber && lineNumber <= this.endLineNumber; + } } \ No newline at end of file diff --git a/src/vs/editor/contrib/folding/test/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/foldingModel.test.ts index a00ef95b045..287baab9c2d 100644 --- a/src/vs/editor/contrib/folding/test/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/foldingModel.test.ts @@ -5,7 +5,7 @@ 'use strict'; import * as assert from 'assert'; -import { FoldingModel, FoldingRegion, setCollapseStateAtLevel, setCollapseStateLevelsDown, setCollapseStateLevelsUp } from 'vs/editor/contrib/folding/foldingModel'; +import { FoldingModel, setCollapseStateAtLevel, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateForMatchingLines } from 'vs/editor/contrib/folding/foldingModel'; import { Model } from 'vs/editor/common/model/model'; import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; @@ -13,6 +13,8 @@ import { TrackedRangeStickiness, IModelDeltaDecoration, IModel, IModelDecoration import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { FoldingRegion } from 'vs/editor/contrib/folding/foldingRanges'; +import { escapeRegExpCharacters } from 'vs/base/common/strings'; interface ExpectedRegion { @@ -588,4 +590,44 @@ suite('Folding Model', () => { }); + + test('setCollapseStateForMatchingLines', () => { + let lines = [ + /* 1*/ '/**', + /* 2*/ ' * the class', + /* 3*/ ' */', + /* 4*/ 'class A {', + /* 5*/ ' /**', + /* 6*/ ' * the foo', + /* 7*/ ' */', + /* 8*/ ' void foo() {', + /* 9*/ ' /*', + /* 10*/ ' * the comment', + /* 11*/ ' */', + /* 12*/ ' }', + /* 13*/ '}']; + + let textModel = Model.createFromString(lines.join('\n')); + try { + let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); + + let ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ }); + foldingModel.update(ranges); + + let r1 = r(1, 3, false); + let r2 = r(4, 12, false); + let r3 = r(5, 7, false); + let r4 = r(8, 11, false); + let r5 = r(9, 11, false); + assertRanges(foldingModel, [r1, r2, r3, r4, r5]); + + let regExp = new RegExp('^\\s*' + escapeRegExpCharacters('/*')); + setCollapseStateForMatchingLines(foldingModel, regExp, true); + assertFoldedRanges(foldingModel, [r1, r3, r5], '1'); + } finally { + textModel.dispose(); + } + + }); + }); \ No newline at end of file