提交 ba49586c 编写于 作者: S Sandeep Somavarapu

#9786 fold and unfold multiple levels commands

上级 16dd81da
......@@ -7,6 +7,7 @@
'use strict';
import * as nls from 'vs/nls';
import * as types from 'vs/base/common/types';
import {RunOnceScheduler} from 'vs/base/common/async';
import {KeyCode, KeyMod} from 'vs/base/common/keyCodes';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
......@@ -16,125 +17,12 @@ import {Range} from 'vs/editor/common/core/range';
import {editorAction, ServicesAccessor, EditorAction, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions';
import {ICodeEditor, IEditorMouseEvent} from 'vs/editor/browser/editorBrowser';
import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions';
import {IFoldingRange} from 'vs/editor/contrib/folding/common/foldingRange';
import {CollapsibleRegion, getCollapsibleRegionsToFoldAtLine, getCollapsibleRegionsToUnfoldAtLine, doesLineBelongsToCollapsibleRegion, IFoldingRange} from 'vs/editor/contrib/folding/common/foldingModel';
import {computeRanges, limitByIndent} from 'vs/editor/contrib/folding/common/indentFoldStrategy';
import {Selection} from 'vs/editor/common/core/selection';
import EditorContextKeys = editorCommon.EditorContextKeys;
class CollapsibleRegion {
private decorationIds: string[];
private _isCollapsed: boolean;
private _indent: number;
private _lastRange: IFoldingRange;
public constructor(range:IFoldingRange, model:editorCommon.IModel, changeAccessor:editorCommon.IModelDecorationsChangeAccessor) {
this.decorationIds = [];
this.update(range, model, changeAccessor);
}
public get isCollapsed(): boolean {
return this._isCollapsed;
}
public get isExpanded(): boolean {
return !this._isCollapsed;
}
public get indent(): number {
return this._indent;
}
public get startLineNumber(): number {
return this._lastRange ? this._lastRange.startLineNumber : void 0;
}
public get endLineNumber(): number {
return this._lastRange ? this._lastRange.endLineNumber : void 0;
}
public setCollapsed(isCollaped: boolean, changeAccessor:editorCommon.IModelDecorationsChangeAccessor): void {
this._isCollapsed = isCollaped;
if (this.decorationIds.length > 0) {
changeAccessor.changeDecorationOptions(this.decorationIds[0], this.getVisualDecorationOptions());
}
}
public getDecorationRange(model:editorCommon.IModel): Range {
if (this.decorationIds.length > 0) {
return model.getDecorationRange(this.decorationIds[1]);
}
return null;
}
private getVisualDecorationOptions(): editorCommon.IModelDecorationOptions {
if (this._isCollapsed) {
return {
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
inlineClassName: 'inline-folded',
linesDecorationsClassName: 'folding collapsed'
};
} else {
return {
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
linesDecorationsClassName: 'folding'
};
}
}
private getRangeDecorationOptions(): editorCommon.IModelDecorationOptions {
return {
stickiness: editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore
};
}
public update(newRange:IFoldingRange, model:editorCommon.IModel, changeAccessor:editorCommon.IModelDecorationsChangeAccessor): void {
this._lastRange = newRange;
this._isCollapsed = !!newRange.isCollapsed;
this._indent = newRange.indent;
let newDecorations : editorCommon.IModelDeltaDecoration[] = [];
let maxColumn = model.getLineMaxColumn(newRange.startLineNumber);
let visualRng = {
startLineNumber: newRange.startLineNumber,
startColumn: maxColumn - 1,
endLineNumber: newRange.startLineNumber,
endColumn: maxColumn
};
newDecorations.push({ range: visualRng, options: this.getVisualDecorationOptions() });
let colRng = {
startLineNumber: newRange.startLineNumber,
startColumn: 1,
endLineNumber: newRange.endLineNumber,
endColumn: model.getLineMaxColumn(newRange.endLineNumber)
};
newDecorations.push({ range: colRng, options: this.getRangeDecorationOptions() });
this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, newDecorations);
}
public dispose(changeAccessor:editorCommon.IModelDecorationsChangeAccessor): void {
this._lastRange = null;
this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, []);
}
public toString(): string {
let str = this.isCollapsed ? 'collapsed ': 'expanded ';
if (this._lastRange) {
str += (this._lastRange.startLineNumber + '/' + this._lastRange.endLineNumber);
} else {
str += 'no range';
}
return str;
}
}
export class FoldingController implements editorCommon.IEditorContribution {
private static ID = 'editor.contrib.folding';
......@@ -483,85 +371,45 @@ export class FoldingController implements editorCommon.IEditorContribution {
this.editor.revealPositionInCenterIfOutsideViewport(revealPosition);
}
public unfold(): void {
public unfold(levels: number = 1): void {
let model = this.editor.getModel();
let hasChanges = false;
let selections = this.editor.getSelections();
let selectionsHasChanged = false;
selections.forEach((selection, index) => {
let lineNumber = selection.startLineNumber;
let surroundingUnfolded: Range;
for (let i = 0, len = this.decorations.length; i < len; i++) {
let dec = this.decorations[i];
let decRange = dec.getDecorationRange(model);
if (!decRange) {
continue;
}
if (decRange.startLineNumber <= lineNumber) {
if (lineNumber <= decRange.endLineNumber) {
if (dec.isCollapsed) {
this.editor.changeDecorations(changeAccessor => {
dec.setCollapsed(false, changeAccessor);
hasChanges = true;
});
return;
}
surroundingUnfolded = decRange;
}
} else { // decRange.startLineNumber > lineNumber
if (surroundingUnfolded && Range.containsRange(surroundingUnfolded, decRange)) {
if (dec.isCollapsed) {
this.editor.changeDecorations(changeAccessor => {
dec.setCollapsed(false, changeAccessor);
hasChanges = true;
let lineNumber = decRange.startLineNumber, column = model.getLineMaxColumn(decRange.startLineNumber);
selections[index] = selection.setEndPosition(lineNumber, column).setStartPosition(lineNumber, column);
selectionsHasChanged = true;
});
return;
}
} else {
return;
}
let toUnfold: CollapsibleRegion[] = getCollapsibleRegionsToUnfoldAtLine(this.decorations, model, selection.startLineNumber, levels);
if (toUnfold.length > 0) {
toUnfold.forEach((collapsibleRegion, index) => {
this.editor.changeDecorations(changeAccessor => {
collapsibleRegion.setCollapsed(false, changeAccessor);
hasChanges = true;
});
});
if (!doesLineBelongsToCollapsibleRegion(toUnfold[0].foldingRange, selection.startLineNumber)) {
let lineNumber = toUnfold[0].startLineNumber, column = model.getLineMaxColumn(toUnfold[0].startLineNumber);
selections[index] = selection.setEndPosition(lineNumber, column).setStartPosition(lineNumber, column);
selectionsHasChanged = true;
}
}
});
if (selectionsHasChanged) {
this.editor.setSelections(selections);
}
if (hasChanges) {
this.updateHiddenAreas(selections[0].startLineNumber);
}
}
public fold(): void {
public fold(levels: number = 1): void {
let hasChanges = false;
let model = this.editor.getModel();
let selections = this.editor.getSelections();
selections.forEach(selection => {
let lineNumber = selection.startLineNumber;
let toFold: CollapsibleRegion = null;
for (let i = 0, len = this.decorations.length; i < len; i++) {
let dec = this.decorations[i];
let decRange = dec.getDecorationRange(model);
if (!decRange) {
continue;
}
if (decRange.startLineNumber <= lineNumber) {
if (lineNumber <= decRange.endLineNumber && !dec.isCollapsed) {
toFold = dec;
}
} else {
break;
}
};
if (toFold) {
this.editor.changeDecorations(changeAccessor => {
toFold.setCollapsed(true, changeAccessor);
hasChanges = true;
});
}
let toFold: CollapsibleRegion[] = getCollapsibleRegionsToFoldAtLine(this.decorations, this.editor.getModel(), lineNumber, levels);
toFold.forEach(collapsibleRegion => this.editor.changeDecorations(changeAccessor => {
collapsibleRegion.setCollapsed(true, changeAccessor);
hasChanges = true;
}));
});
if (hasChanges) {
this.updateHiddenAreas(selections[0].startLineNumber);
......@@ -648,18 +496,23 @@ export class FoldingController implements editorCommon.IEditorContribution {
}
}
abstract class FoldingAction extends EditorAction {
abstract class FoldingAction<T> extends EditorAction {
abstract invoke(foldingController: FoldingController, editor:editorCommon.ICommonCodeEditor): void;
abstract invoke(foldingController: FoldingController, editor:editorCommon.ICommonCodeEditor, args:T): void;
public run(accessor:ServicesAccessor, editor:editorCommon.ICommonCodeEditor): void {
public runEditorCommand(accessor:ServicesAccessor, editor: editorCommon.ICommonCodeEditor, args: T): void | TPromise<void> {
this.reportTelemetry(accessor);
let foldingController = FoldingController.get(editor);
this.invoke(foldingController, editor);
this.invoke(foldingController, editor, args);
}
public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): void {
}
}
@editorAction
class UnfoldAction extends FoldingAction {
class UnfoldAction extends FoldingAction<{levels?: number}> {
constructor() {
super({
......@@ -670,17 +523,29 @@ class UnfoldAction extends FoldingAction {
kbOpts: {
kbExpr: EditorContextKeys.TextFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET
},
description: {
description: 'Unfold the content in the editor',
args: [
{
name: 'Unfold editor argument',
description: `Property-value pairs that can be passed through this argument:
'level': Number of levels to unfold
`,
constraint: args => types.isUndefined(args) || (types.isObject(args) && (types.isUndefined(args.level) || types.isNumber(args.level)))
}
]
}
});
}
invoke(foldingController: FoldingController, editor:editorCommon.ICommonCodeEditor): void {
foldingController.unfold();
invoke(foldingController: FoldingController, editor:editorCommon.ICommonCodeEditor, args: {levels?: number}): void {
foldingController.unfold(args ? args.levels || 1 : 1);
}
}
@editorAction
class UnFoldRecursivelyAction extends FoldingAction {
class UnFoldRecursivelyAction extends FoldingAction<void> {
constructor() {
super({
......@@ -695,13 +560,13 @@ class UnFoldRecursivelyAction extends FoldingAction {
});
}
invoke(foldingController: FoldingController, editor:editorCommon.ICommonCodeEditor): void {
invoke(foldingController: FoldingController, editor:editorCommon.ICommonCodeEditor, args:any): void {
foldingController.foldUnfoldRecursively(false);
}
}
@editorAction
class FoldAction extends FoldingAction {
class FoldAction extends FoldingAction<{levels?: number}> {
constructor() {
super({
......@@ -712,17 +577,29 @@ class FoldAction extends FoldingAction {
kbOpts: {
kbExpr: EditorContextKeys.TextFocus,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET
},
description: {
description: 'Fold the content in the editor',
args: [
{
name: 'Fold editor argument',
description: `Property-value pairs that can be passed through this argument:
'levels': Number of levels to fold
`,
constraint: args => types.isUndefined(args) || (types.isObject(args) && (types.isUndefined(args.level) || types.isNumber(args.level)))
}
]
}
});
}
invoke(foldingController: FoldingController, editor:editorCommon.ICommonCodeEditor): void {
foldingController.fold();
invoke(foldingController: FoldingController, editor:editorCommon.ICommonCodeEditor, args: {levels?: number}): void {
foldingController.fold(args ? args.levels || 1 : 1);
}
}
@editorAction
class FoldRecursivelyAction extends FoldingAction {
class FoldRecursivelyAction extends FoldingAction<void> {
constructor() {
super({
......@@ -743,7 +620,7 @@ class FoldRecursivelyAction extends FoldingAction {
}
@editorAction
class FoldAllAction extends FoldingAction {
class FoldAllAction extends FoldingAction<void> {
constructor() {
super({
......@@ -764,7 +641,7 @@ class FoldAllAction extends FoldingAction {
}
@editorAction
class UnfoldAllAction extends FoldingAction {
class UnfoldAllAction extends FoldingAction<void> {
constructor() {
super({
......@@ -784,7 +661,7 @@ class UnfoldAllAction extends FoldingAction {
}
}
class FoldLevelAction extends FoldingAction {
class FoldLevelAction extends FoldingAction<void> {
private static ID_PREFIX = 'editor.foldLevel';
public static ID = (level:number) => FoldLevelAction.ID_PREFIX + level;
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as editorCommon from 'vs/editor/common/editorCommon';
import {Range} from 'vs/editor/common/core/range';
export interface IFoldingRange {
startLineNumber:number;
endLineNumber:number;
indent:number;
isCollapsed?:boolean;
}
export function toString(range: IFoldingRange): string {
return (range ? range.startLineNumber + '/' + range.endLineNumber : 'null') + (range.isCollapsed ? ' (collapsed)' : '') + ' - ' + range.indent;
}
export class CollapsibleRegion {
private decorationIds: string[];
private _isCollapsed: boolean;
private _indent: number;
private _lastRange: IFoldingRange;
public constructor(range:IFoldingRange, model:editorCommon.IModel, changeAccessor:editorCommon.IModelDecorationsChangeAccessor) {
this.decorationIds = [];
this.update(range, model, changeAccessor);
}
public get isCollapsed(): boolean {
return this._isCollapsed;
}
public get isExpanded(): boolean {
return !this._isCollapsed;
}
public get indent(): number {
return this._indent;
}
public get foldingRange(): IFoldingRange {
return this._lastRange;
}
public get startLineNumber(): number {
return this._lastRange ? this._lastRange.startLineNumber : void 0;
}
public get endLineNumber(): number {
return this._lastRange ? this._lastRange.endLineNumber : void 0;
}
public setCollapsed(isCollaped: boolean, changeAccessor:editorCommon.IModelDecorationsChangeAccessor): void {
this._isCollapsed = isCollaped;
if (this.decorationIds.length > 0) {
changeAccessor.changeDecorationOptions(this.decorationIds[0], this.getVisualDecorationOptions());
}
}
public getDecorationRange(model:editorCommon.IModel): Range {
if (this.decorationIds.length > 0) {
return model.getDecorationRange(this.decorationIds[1]);
}
return null;
}
private getVisualDecorationOptions(): editorCommon.IModelDecorationOptions {
if (this._isCollapsed) {
return {
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
inlineClassName: 'inline-folded',
linesDecorationsClassName: 'folding collapsed'
};
} else {
return {
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
linesDecorationsClassName: 'folding'
};
}
}
private getRangeDecorationOptions(): editorCommon.IModelDecorationOptions {
return {
stickiness: editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore
};
}
public update(newRange:IFoldingRange, model:editorCommon.IModel, changeAccessor:editorCommon.IModelDecorationsChangeAccessor): void {
this._lastRange = newRange;
this._isCollapsed = !!newRange.isCollapsed;
this._indent = newRange.indent;
let newDecorations : editorCommon.IModelDeltaDecoration[] = [];
let maxColumn = model.getLineMaxColumn(newRange.startLineNumber);
let visualRng = {
startLineNumber: newRange.startLineNumber,
startColumn: maxColumn - 1,
endLineNumber: newRange.startLineNumber,
endColumn: maxColumn
};
newDecorations.push({ range: visualRng, options: this.getVisualDecorationOptions() });
let colRng = {
startLineNumber: newRange.startLineNumber,
startColumn: 1,
endLineNumber: newRange.endLineNumber,
endColumn: model.getLineMaxColumn(newRange.endLineNumber)
};
newDecorations.push({ range: colRng, options: this.getRangeDecorationOptions() });
this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, newDecorations);
}
public dispose(changeAccessor:editorCommon.IModelDecorationsChangeAccessor): void {
this._lastRange = null;
this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, []);
}
public toString(): string {
let str = this.isCollapsed ? 'collapsed ': 'expanded ';
if (this._lastRange) {
str += (this._lastRange.startLineNumber + '/' + this._lastRange.endLineNumber);
} else {
str += 'no range';
}
return str;
}
}
export function getCollapsibleRegionsToFoldAtLine(allRegions: CollapsibleRegion[], model: editorCommon.IModel, lineNumber: number, levels: number): CollapsibleRegion[] {
let surroundingRegion: CollapsibleRegion = getCollapsibleRegionAtLine(allRegions, model, lineNumber);
if (!surroundingRegion) {
return [];
}
if (levels === 1) {
return [surroundingRegion];
}
let result = new CollapsibleRegionsTree(surroundingRegion, allRegions, model).getRegionsTill(levels);
return result.filter(collapsibleRegion => !collapsibleRegion.isCollapsed);
}
export function getCollapsibleRegionsToUnfoldAtLine(allRegions: CollapsibleRegion[], model: editorCommon.IModel, lineNumber: number, levels: number): CollapsibleRegion[] {
let surroundingRegion: CollapsibleRegion = getCollapsibleRegionAtLine(allRegions, model, lineNumber);
if (!surroundingRegion) {
return [];
}
if (levels === 1) {
let regionToUnfold = surroundingRegion.isCollapsed ? surroundingRegion : getFoldedCollapsibleRegionAfterLine(allRegions, model, surroundingRegion, lineNumber);
return regionToUnfold ? [regionToUnfold] : [];
}
let result = new CollapsibleRegionsTree(surroundingRegion, allRegions, model).getRegionsTill(levels);
return result.filter(collapsibleRegion => collapsibleRegion.isCollapsed);
}
function getCollapsibleRegionAtLine(allRegions: CollapsibleRegion[], model: editorCommon.IModel, lineNumber: number): CollapsibleRegion {
let collapsibleRegion: CollapsibleRegion = null;
for (let i = 0, len = allRegions.length; i < len; i++) {
let dec = allRegions[i];
let decRange = dec.getDecorationRange(model);
if (decRange) {
if (doesLineBelongsToCollapsibleRegion(decRange, lineNumber)) {
collapsibleRegion = dec;
}
if (doesCollapsibleRegionIsAfterLine(decRange, lineNumber)) {
break;
}
}
}
return collapsibleRegion;
}
function getFoldedCollapsibleRegionAfterLine(allRegions: CollapsibleRegion[], model: editorCommon.IModel, surroundingRegion: CollapsibleRegion, lineNumber: number): CollapsibleRegion {
let index = allRegions.indexOf(surroundingRegion);
for (let i = index + 1; i < allRegions.length; i++) {
let dec = allRegions[i];
let decRange = dec.getDecorationRange(model);
if (decRange) {
if (doesCollapsibleRegionIsAfterLine(decRange, lineNumber)) {
if (!doesCollapsibleRegionContains(surroundingRegion.foldingRange, decRange)) {
return null;
}
if (dec.isCollapsed) {
return dec;
}
}
}
}
return null;
}
export function doesLineBelongsToCollapsibleRegion(range: IFoldingRange | Range, lineNumber: number): boolean {
return lineNumber >= range.startLineNumber && lineNumber <= range.endLineNumber;
}
function doesCollapsibleRegionIsAfterLine(range: IFoldingRange | Range, lineNumber: number): boolean {
return lineNumber < range.startLineNumber;
}
function doesCollapsibleRegionContains(range1: IFoldingRange | Range, range2: Range): boolean {
if (range1 instanceof Range) {
return range1.containsRange(range2);
}
return range1.startLineNumber <= range2.startLineNumber && range1.endLineNumber >= range2.endLineNumber;
}
class CollapsibleRegionsTree {
children: CollapsibleRegionsTree[] = [];
lastChildIndex: number;
constructor(private region: CollapsibleRegion, allRegions: CollapsibleRegion[], model: editorCommon.IModel) {
for (let index = allRegions.indexOf(region) + 1; index < allRegions.length; index++) {
let dec = allRegions[index];
let decRange = dec.getDecorationRange(model);
if (decRange) {
if (doesCollapsibleRegionContains(region.foldingRange, decRange)) {
index = this.processChildRegion(dec, allRegions, model, index);
}
if (doesCollapsibleRegionIsAfterLine(decRange, region.foldingRange.endLineNumber)) {
break;
}
}
}
}
private processChildRegion(dec: CollapsibleRegion, allRegions: CollapsibleRegion[], model: editorCommon.IModel, index : number): number {
let childRegion = new CollapsibleRegionsTree(dec, allRegions, model);
this.children.push(childRegion);
this.lastChildIndex = index;
return childRegion.children.length > 0 ? childRegion.lastChildIndex : index;
}
public getRegionsTill(level: number): CollapsibleRegion[] {
let result = [this.region];
if (level > 1) {
this.children.forEach(region => result = result.concat(region.getRegionsTill(level - 1)));
}
return result;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export interface IFoldingRange {
startLineNumber:number;
endLineNumber:number;
indent:number;
isCollapsed?:boolean;
}
export function toString(range: IFoldingRange): string {
return (range ? range.startLineNumber + '/' + range.endLineNumber : 'null') + (range.isCollapsed ? ' (collapsed)' : '') + ' - ' + range.indent;
}
\ No newline at end of file
......@@ -6,7 +6,7 @@
'use strict';
import {IModel} from 'vs/editor/common/editorCommon';
import {IFoldingRange} from 'vs/editor/contrib/folding/common/foldingRange';
import {IFoldingRange} from 'vs/editor/contrib/folding/common/foldingModel';
export function computeRanges(model: IModel): IFoldingRange[] {
// we get here a clone of the model's indent ranges
......
......@@ -5,7 +5,7 @@
'use strict';
import * as assert from 'assert';
import {IFoldingRange} from 'vs/editor/contrib/folding/common/foldingRange';
import {IFoldingRange} from 'vs/editor/contrib/folding/common/foldingModel';
import {limitByIndent} from 'vs/editor/contrib/folding/common/indentFoldStrategy';
suite('Indentation Folding', () => {
......
......@@ -7,7 +7,7 @@
import * as assert from 'assert';
import {Model} from 'vs/editor/common/model/model';
import {IFoldingRange} from 'vs/editor/contrib/folding/common/foldingRange';
import {IFoldingRange} from 'vs/editor/contrib/folding/common/foldingModel';
import {computeRanges} from 'vs/editor/common/model/indentRanges';
suite('Indentation Folding', () => {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册