[folding] collapse block comment shortcut. Fixes #11524 & fixes #4670

This commit is contained in:
Martin Aeschlimann 2017-11-22 17:27:05 +01:00
parent 0fd1ba9903
commit bce608e4bf
4 changed files with 133 additions and 40 deletions

View file

@ -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<void> {
}
}
class FoldAllBlockCommentsAction extends FoldingAction<void> {
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<void> {
constructor() {
@ -568,6 +593,7 @@ registerEditorAction(FoldAction);
registerEditorAction(FoldRecursivelyAction);
registerEditorAction(FoldAllAction);
registerEditorAction(UnfoldAllAction);
registerEditorAction(FoldAllBlockCommentsAction);
for (let i = 1; i <= 9; i++) {
registerInstantiatedEditorAction(

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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();
}
});
});