Skip to content

Commit

Permalink
#9786 fold and unfold multiple levels commands
Browse files Browse the repository at this point in the history
  • Loading branch information
sandy081 committed Aug 22, 2016
1 parent 16dd81d commit ba49586
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 209 deletions.
255 changes: 66 additions & 189 deletions src/vs/editor/contrib/folding/browser/folding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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({
Expand All @@ -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({
Expand All @@ -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({
Expand All @@ -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({
Expand All @@ -743,7 +620,7 @@ class FoldRecursivelyAction extends FoldingAction {
}

@editorAction
class FoldAllAction extends FoldingAction {
class FoldAllAction extends FoldingAction<void> {

constructor() {
super({
Expand All @@ -764,7 +641,7 @@ class FoldAllAction extends FoldingAction {
}

@editorAction
class UnfoldAllAction extends FoldingAction {
class UnfoldAllAction extends FoldingAction<void> {

constructor() {
super({
Expand All @@ -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;

Expand Down
Loading

0 comments on commit ba49586

Please sign in to comment.