提交 71c8b150 编写于 作者: M Martin Aeschlimann

Folding using indentation

上级 c3d1b9fa
......@@ -38,6 +38,7 @@ define([
'vs/editor/contrib/wordHighlighter/common/wordHighlighter',
'vs/editor/contrib/workerStatusReporter/browser/workerStatusReporter',
'vs/editor/contrib/defineKeybinding/browser/defineKeybinding',
"vs/editor/contrib/folding/browser/folding",
// include these in the editor bundle because they are widely used by many languages
'vs/editor/common/languages.common'
......
......@@ -591,6 +591,11 @@ export interface ICodeEditor extends EditorCommon.ICommonCodeEditor {
* Warning: the results of this method are innacurate for positions that are outside the current editor viewport.
*/
getScrolledVisiblePosition(position: EditorCommon.IPosition): { top: number; left: number; height: number; };
/**
* Set the model ranges that will be hidden in the view.
*/
setHiddenAreas(ranges:EditorCommon.IRange[]): void;
}
/**
......
......@@ -365,6 +365,12 @@ export class CodeEditorWidget extends CommonCodeEditor implements EditorBrowser.
this._view.render(true);
}
public setHiddenAreas(ranges:EditorCommon.IRange[]): void {
if (this.viewModel) {
this.viewModel.setHiddenAreas(ranges);
}
}
_attachModel(model:EditorCommon.IModel): void {
this._view = null;
......
......@@ -5,6 +5,7 @@
'use strict';
import {Position} from 'vs/editor/common/core/position';
import {Range} from 'vs/editor/common/core/range';
import {PrefixSumComputer, IPrefixSumIndexOfResult} from 'vs/editor/common/viewModel/prefixSumComputer';
import {FilteredLineTokens, IdentityFilteredLineTokens} from 'vs/editor/common/viewModel/filteredLineTokens';
import {ILinesCollection} from 'vs/editor/common/viewModel/viewModel';
......@@ -41,6 +42,8 @@ export interface IModel {
}
export interface ISplitLine {
isVisible():boolean;
setVisible(isVisible:boolean):void;
getOutputLineCount(): number;
getOutputLineContent(model: IModel, myLineNumber: number, outputLineIndex: number): string;
getOutputLineMinColumn(model: IModel, myLineNumber: number, outputLineIndex: number): number;
......@@ -52,33 +55,66 @@ export interface ISplitLine {
class IdentitySplitLine implements ISplitLine {
public static INSTANCE = new IdentitySplitLine();
private _isVisible: boolean;
public constructor(isVisible: boolean) {
this._isVisible = isVisible;
}
public isVisible():boolean {
return this._isVisible;
}
public setVisible(isVisible:boolean):void {
this._isVisible = isVisible;
}
public getOutputLineCount(): number {
if (!this._isVisible) {
return 0;
}
return 1;
}
public getOutputLineContent(model:IModel, myLineNumber:number, outputLineIndex:number): string {
if (!this._isVisible) {
throw new Error('Not supported');
}
return model.getLineContent(myLineNumber);
}
public getOutputLineMinColumn(model: IModel, myLineNumber: number, outputLineIndex: number): number {
if (!this._isVisible) {
throw new Error('Not supported');
}
return model.getLineMinColumn(myLineNumber);
}
public getOutputLineMaxColumn(model:IModel, myLineNumber:number, outputLineIndex:number): number {
if (!this._isVisible) {
throw new Error('Not supported');
}
return model.getLineMaxColumn(myLineNumber);
}
public getOutputLineTokens(model:IModel, myLineNumber:number, outputLineIndex:number, inaccurateTokensAcceptable:boolean): EditorCommon.IViewLineTokens {
if (!this._isVisible) {
throw new Error('Not supported');
}
return new IdentityFilteredLineTokens(model.getLineTokens(myLineNumber, inaccurateTokensAcceptable), model.getLineMaxColumn(myLineNumber) - 1);
}
public getInputColumnOfOutputPosition(outputLineIndex:number, outputColumn:number): number {
if (!this._isVisible) {
throw new Error('Not supported');
}
return outputColumn;
}
public getOutputPositionOfInputPosition(deltaLineNumber:number, inputColumn:number): EditorCommon.IEditorPosition {
if (!this._isVisible) {
throw new Error('Not supported');
}
return new Position(deltaLineNumber, inputColumn);
}
}
......@@ -90,15 +126,28 @@ export class SplitLine implements ISplitLine {
private wrappedIndent:string;
private wrappedIndentLength:number;
private _isVisible: boolean;
constructor(positionMapper:ILineMapping) {
constructor(positionMapper:ILineMapping, isVisible: boolean) {
this.positionMapper = positionMapper;
this.wrappedIndent = this.positionMapper.getWrappedLinesIndent();
this.wrappedIndentLength = this.wrappedIndent.length;
this.outputLineCount = this.positionMapper.getOutputLineCount();
this._isVisible = isVisible;
}
public isVisible():boolean {
return this._isVisible;
}
public setVisible(isVisible:boolean):void {
this._isVisible = isVisible;
}
public getOutputLineCount(): number {
if (!this._isVisible) {
return 0;
}
return this.outputLineCount;
}
......@@ -114,6 +163,9 @@ export class SplitLine implements ISplitLine {
}
public getOutputLineContent(model:IModel, myLineNumber:number, outputLineIndex:number): string {
if (!this._isVisible) {
throw new Error('Not supported');
}
var startOffset = this.getInputStartOffsetOfOutputLineIndex(outputLineIndex);
var endOffset = this.getInputEndOffsetOfOutputLineIndex(model, myLineNumber, outputLineIndex);
var r = model.getLineContent(myLineNumber).substring(startOffset, endOffset);
......@@ -127,6 +179,9 @@ export class SplitLine implements ISplitLine {
public getOutputLineMinColumn(model:IModel, myLineNumber:number, outputLineIndex:number): number {
if (!this._isVisible) {
throw new Error('Not supported');
}
if (outputLineIndex > 0) {
return this.wrappedIndentLength + 1;
}
......@@ -134,10 +189,16 @@ export class SplitLine implements ISplitLine {
}
public getOutputLineMaxColumn(model:IModel, myLineNumber:number, outputLineIndex:number): number {
if (!this._isVisible) {
throw new Error('Not supported');
}
return this.getOutputLineContent(model, myLineNumber, outputLineIndex).length + 1;
}
public getOutputLineTokens(model:IModel, myLineNumber:number, outputLineIndex:number, inaccurateTokensAcceptable:boolean): EditorCommon.IViewLineTokens {
if (!this._isVisible) {
throw new Error('Not supported');
}
var startOffset = this.getInputStartOffsetOfOutputLineIndex(outputLineIndex);
var endOffset = this.getInputEndOffsetOfOutputLineIndex(model, myLineNumber, outputLineIndex);
var deltaStartIndex = 0;
......@@ -148,6 +209,9 @@ export class SplitLine implements ISplitLine {
}
public getInputColumnOfOutputPosition(outputLineIndex:number, outputColumn:number): number {
if (!this._isVisible) {
throw new Error('Not supported');
}
var adjustedColumn = outputColumn - 1;
if (outputLineIndex > 0) {
if (adjustedColumn < this.wrappedIndentLength) {
......@@ -160,6 +224,9 @@ export class SplitLine implements ISplitLine {
}
public getOutputPositionOfInputPosition(deltaLineNumber:number, inputColumn:number): EditorCommon.IEditorPosition {
if (!this._isVisible) {
throw new Error('Not supported');
}
this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1, tmpOutputPosition);
var outputLineIndex = tmpOutputPosition.outputLineIndex;
var outputColumn = tmpOutputPosition.outputOffset + 1;
......@@ -173,13 +240,13 @@ export class SplitLine implements ISplitLine {
}
}
function createSplitLine(linePositionMapperFactory:ILineMapperFactory, text:string, tabSize:number, wrappingColumn:number, columnsForFullWidthChar:number, wrappingIndent:EditorCommon.WrappingIndent): ISplitLine {
function createSplitLine(linePositionMapperFactory:ILineMapperFactory, text:string, tabSize:number, wrappingColumn:number, columnsForFullWidthChar:number, wrappingIndent:EditorCommon.WrappingIndent, isVisible: boolean): ISplitLine {
var positionMapper = linePositionMapperFactory.createLineMapping(text, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
if (positionMapper === null) {
// No mapping needed
return IdentitySplitLine.INSTANCE;
return new IdentitySplitLine(isVisible);
} else {
return new SplitLine(positionMapper);
return new SplitLine(positionMapper, isVisible);
}
}
......@@ -197,6 +264,7 @@ export class SplitLinesCollection implements ILinesCollection {
private linePositionMapperFactory:ILineMapperFactory;
private tmpIndexOfResult: IPrefixSumIndexOfResult;
private hiddenAreasIds:string[];
constructor(model:EditorCommon.IModel, linePositionMapperFactory:ILineMapperFactory, tabSize:number, wrappingColumn:number, columnsForFullWidthChar:number, wrappingIndent:EditorCommon.WrappingIndent) {
this.model = model;
......@@ -215,6 +283,10 @@ export class SplitLinesCollection implements ILinesCollection {
};
}
public dispose(): void {
this.hiddenAreasIds = this.model.deltaDecorations(this.hiddenAreasIds, []);
}
private _ensureValidState(): void {
var modelVersion = this.model.getVersionId();
if (modelVersion !== this._validModelVersionId) {
......@@ -224,13 +296,14 @@ export class SplitLinesCollection implements ILinesCollection {
private constructLines(): void {
this.lines = [];
this.hiddenAreasIds = [];
var line:ISplitLine,
values:number[] = [],
linesContent = this.model.getLinesContent();
for (var i = 0, lineCount = linesContent.length; i < lineCount; i++) {
line = createSplitLine(this.linePositionMapperFactory, linesContent[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent);
line = createSplitLine(this.linePositionMapperFactory, linesContent[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, true);
values[i] = line.getOutputLineCount();
this.lines[i] = line;
}
......@@ -240,6 +313,62 @@ export class SplitLinesCollection implements ILinesCollection {
this.prefixSumComputer = new PrefixSumComputer(values);
}
private getHiddenAreas(): EditorCommon.IEditorRange[] {
return this.hiddenAreasIds.map((decId) => {
return this.model.getDecorationRange(decId);
}).sort(Range.compareRangesUsingStarts);
}
public setHiddenAreas(ranges:EditorCommon.IRange[], emit:(evenType:string, payload:any)=>void): void {
var newDecorations:EditorCommon.IModelDeltaDecoration[] = [];
for (var i = 0; i < ranges.length; i++) {
newDecorations.push({
range: ranges[i],
options: {
}
});
}
this.hiddenAreasIds = this.model.deltaDecorations(this.hiddenAreasIds, newDecorations);
var hiddenAreas = this.getHiddenAreas();
var hiddenAreaStart = 1, hiddenAreaEnd = 0;
var hiddenAreaIdx = -1;
var nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.lines.length + 2;
for (var i = 0; i < this.lines.length; i++) {
var lineNumber = i + 1;
if (lineNumber === nextLineNumberToUpdateHiddenArea) {
hiddenAreaIdx++;
hiddenAreaStart = hiddenAreas[hiddenAreaIdx].startLineNumber;
hiddenAreaEnd = hiddenAreas[hiddenAreaIdx].endLineNumber;
nextLineNumberToUpdateHiddenArea = (hiddenAreaIdx + 1 < hiddenAreas.length) ? hiddenAreaEnd + 1 : this.lines.length + 2;
}
var lineChanged = false;
if (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd) {
// Line should be hidden
if (this.lines[i].isVisible()) {
this.lines[i].setVisible(false);
lineChanged = true;
}
} else {
// Line should be visible
if (!this.lines[i].isVisible()) {
this.lines[i].setVisible(true);
lineChanged = true;
}
}
if (lineChanged) {
var newOutputLineCount = this.lines[i].getOutputLineCount();
this.prefixSumComputer.changeValue(i, newOutputLineCount);
}
}
emit(EditorCommon.ViewEventNames.ModelFlushedEvent, null);
}
public setTabSize(newTabSize:number, emit:(evenType:string, payload:any)=>void): boolean {
if (this.tabSize === newTabSize) {
return false;
......@@ -286,6 +415,7 @@ export class SplitLinesCollection implements ILinesCollection {
return;
}
this._validModelVersionId = versionId;
var outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(fromLineNumber - 2) + 1);
var outputToLineNumber = this.prefixSumComputer.getAccumulatedValue(toLineNumber - 1);
......@@ -304,6 +434,17 @@ export class SplitLinesCollection implements ILinesCollection {
return;
}
this._validModelVersionId = versionId;
var hiddenAreas = this.getHiddenAreas();
var isInHiddenArea = false;
var testPosition = new Position(fromLineNumber, 1);
for (var i = 0; i < hiddenAreas.length; i++) {
if (hiddenAreas[i].containsPosition(testPosition)) {
isInHiddenArea = true;
break;
}
}
var outputFromLineNumber = (fromLineNumber === 1 ? 1 : this.prefixSumComputer.getAccumulatedValue(fromLineNumber - 2) + 1);
var line: ISplitLine,
......@@ -314,7 +455,7 @@ export class SplitLinesCollection implements ILinesCollection {
insertPrefixSumValues: number[] = [];
for (var i = 0, len = text.length; i < len; i++) {
var line = createSplitLine(this.linePositionMapperFactory, text[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent);
var line = createSplitLine(this.linePositionMapperFactory, text[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, !isInHiddenArea);
insertLines.push(line);
outputLineCount = line.getOutputLineCount();
......@@ -341,7 +482,8 @@ export class SplitLinesCollection implements ILinesCollection {
var lineIndex = lineNumber - 1;
var oldOutputLineCount = this.lines[lineIndex].getOutputLineCount();
var line = createSplitLine(this.linePositionMapperFactory, newText, this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent);
var isVisible = this.lines[lineIndex].isVisible();
var line = createSplitLine(this.linePositionMapperFactory, newText, this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, isVisible);
this.lines[lineIndex] = line;
var newOutputLineCount = this.lines[lineIndex].getOutputLineCount();
......@@ -460,8 +602,25 @@ export class SplitLinesCollection implements ILinesCollection {
if (inputLineNumber > this.lines.length) {
inputLineNumber = this.lines.length;
}
var deltaLineNumber = 1 + (inputLineNumber === 1 ? 0 : this.prefixSumComputer.getAccumulatedValue(inputLineNumber - 2));
var r = this.lines[inputLineNumber - 1].getOutputPositionOfInputPosition(deltaLineNumber, inputColumn);
let lineIndex = inputLineNumber - 1, lineIndexChanged = false;
while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) {
lineIndex--;
lineIndexChanged = true;
}
if (lineIndex === 0 && !this.lines[lineIndex].isVisible()) {
// Could not reach a real line
return new Position(1, 1);
}
var deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1));
var r:EditorCommon.IEditorPosition;
if (lineIndexChanged) {
r = this.lines[lineIndex].getOutputPositionOfInputPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1));
} else {
r = this.lines[inputLineNumber - 1].getOutputPositionOfInputPosition(deltaLineNumber, inputColumn);
}
// console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + r.lineNumber + ',' + r.column);
return r;
}
......
......@@ -30,6 +30,8 @@ export interface ILinesCollection {
getOutputLineTokens(outputLineNumber:number, inaccurateTokensAcceptable:boolean): EditorCommon.IViewLineTokens;
convertOutputPositionToInputPosition(viewLineNumber:number, viewColumn:number): EditorCommon.IEditorPosition;
convertInputPositionToOutputPosition(inputLineNumber:number, inputColumn:number): EditorCommon.IEditorPosition;
setHiddenAreas(ranges:EditorCommon.IRange[], emit:(evenType:string, payload:any)=>void): void;
dispose(): void;
}
export class ViewModel extends EventEmitter implements EditorCommon.IViewModel {
......@@ -78,6 +80,14 @@ export class ViewModel extends EventEmitter implements EditorCommon.IViewModel {
}));
}
public setHiddenAreas(ranges:EditorCommon.IRange[]): void {
this.deferredEmit(() => {
this.lines.setHiddenAreas(ranges, (eventType:string, payload:any) => this.emit(eventType, payload));
this.decorations.onLineMappingChanged((eventType:string, payload:any) => this.emit(eventType, payload));
this.cursors.onLineMappingChanged((eventType:string, payload:any) => this.emit(eventType, payload));
});
}
public dispose(): void {
this.listenersToRemove.forEach((element) => {
element();
......@@ -86,6 +96,7 @@ export class ViewModel extends EventEmitter implements EditorCommon.IViewModel {
this.listenersToRemove = [];
this.decorations.dispose();
this.decorations = null;
this.lines.dispose();
this.lines = null;
this.configuration = null;
this.model = null;
......
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e8e8e8" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#646465" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e8e8e8" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#646465" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>
\ 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.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .folding {
background-image: url('arrow-expand.svg');
background-repeat: no-repeat;
margin-left: 5px;
}
.monaco-editor .folding.collapsed {
background-image: url('arrow-collapse.svg');
}
.vs-dark .monaco-editor .folding {
background-image: url('arrow-expand-dark.svg');
}
.vs-dark .monaco-editor .folding.collapsed {
background-image: url('arrow-collapse-dark.svg');
}
.monaco-editor .inline-folded {
color:grey !important;
}
.monaco-editor .inline-folded:after {
color: grey;
margin: 0.1em 0.2em 0 0.2em;
content: "⋯";
display: inline-block;
line-height: 1em;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <amd-dependency path="vs/css!./folding" />
'use strict';
import {RunOnceScheduler} from 'vs/base/common/async';
import {Range} from 'vs/editor/common/core/range';
import EditorCommon = require('vs/editor/common/editorCommon');
import {IMouseEvent, ICodeEditor} from 'vs/editor/browser/editorBrowser';
import {INullService} from 'vs/platform/instantiation/common/instantiation';
import {IDisposable, disposeAll} from 'vs/base/common/lifecycle';
import Modes = require('vs/editor/common/modes');
import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions';
import {TPromise} from 'vs/base/common/winjs.base';
import foldStrategy = require('vs/editor/contrib/folding/common/indentFoldStrategy');
import {IFoldingRange} from 'vs/editor/contrib/folding/common/foldingRange';
class CollapsableRegion {
public visualDecorationId:string;
public rangeDecorationId:string;
public isCollapsed:boolean;
public constructor(range:IFoldingRange, model:EditorCommon.IModel, changeAccessor:EditorCommon.IModelDecorationsChangeAccessor, isCollapsed:boolean) {
this.isCollapsed = isCollapsed;
var maxColumn = model.getLineMaxColumn(range.startLineNumber);
var visualRng = {
startLineNumber: range.startLineNumber,
startColumn: maxColumn - 1,
endLineNumber: range.startLineNumber,
endColumn: maxColumn
};
this.visualDecorationId = changeAccessor.addDecoration(visualRng, this.getVisualDecorationOptions());
var colRng = {
startLineNumber: range.startLineNumber,
startColumn: 1,
endLineNumber: range.endLineNumber,
endColumn: model.getLineMaxColumn(range.endLineNumber)
};
this.rangeDecorationId = changeAccessor.addDecoration(colRng, {});
}
public getVisualDecorationOptions():EditorCommon.IModelDecorationOptions {
if (this.isCollapsed) {
return {
inlineClassName: 'inline-folded',
linesDecorationsClassName: 'folding collapsed'
};
} else {
return {
linesDecorationsClassName: 'folding'
};
}
}
public dispose(changeAccessor:EditorCommon.IModelDecorationsChangeAccessor): void {
changeAccessor.removeDecoration(this.visualDecorationId);
changeAccessor.removeDecoration(this.rangeDecorationId);
}
}
export class Folding implements EditorCommon.IEditorContribution {
static ID = 'editor.contrib.folding';
private editor:ICodeEditor;
private globalToDispose:IDisposable[];
private computeToken:number;
private updateScheduler:RunOnceScheduler;
private localToDispose:IDisposable[];
private decorations:CollapsableRegion[];
constructor(editor:ICodeEditor, @INullService nullService) {
this.editor = editor;
this.globalToDispose = [];
this.localToDispose = [];
this.decorations = [];
this.computeToken = 0;
this.globalToDispose.push(this.editor.addListener2(EditorCommon.EventType.ModelChanged, () => this.onModelChanged()));
this.globalToDispose.push(this.editor.addListener2(EditorCommon.EventType.ModelModeChanged, () => this.onModelChanged()));
this.onModelChanged();
}
public getId(): string {
return Folding.ID;
}
public dispose(): void {
this.cleanState();
this.globalToDispose = disposeAll(this.globalToDispose);
}
private cleanState(): void {
this.localToDispose = disposeAll(this.localToDispose);
}
private onModelChanged(): void {
this.cleanState();
var model = this.editor.getModel();
if (!model) {
return;
}
this.updateScheduler = new RunOnceScheduler(() => {
var myToken = (++this.computeToken);
this.computeCollapsableRegions().then(regions => {
if (myToken !== this.computeToken) {
// A new request was made in the meantime or the model was changed
return;
}
this.editor.changeDecorations((changeAccessor:EditorCommon.IModelDecorationsChangeAccessor) => {
var collapsedStartLineNumbers:{[lineNumber:string]:boolean} = {};
this.decorations.forEach((dec) => {
if (dec.isCollapsed) {
var oldRange = model.getDecorationRange(dec.visualDecorationId);
collapsedStartLineNumbers[oldRange.startLineNumber.toString()] = true;
}
changeAccessor.removeDecoration(dec.rangeDecorationId);
changeAccessor.removeDecoration(dec.visualDecorationId);
});
this.decorations = [];
regions.forEach(region => {
this.decorations.push(new CollapsableRegion(region, model, changeAccessor, collapsedStartLineNumbers[region.startLineNumber.toString()]));
});
});
this.updateHiddenAreas();
});
}, 500);
this.localToDispose.push(this.updateScheduler);
this.localToDispose.push(this.editor.addListener2('change', () => this.updateScheduler.schedule()));
this.localToDispose.push({ dispose: () => {
++this.computeToken;
this.editor.changeDecorations((changeAccessor:EditorCommon.IModelDecorationsChangeAccessor) => {
this.decorations.forEach((dec) => dec.dispose(changeAccessor));
});
}});
this.localToDispose.push(this.editor.addListener2(EditorCommon.EventType.MouseDown, (e) => this._onEditorMouseDown(e)));
this.updateScheduler.schedule();
}
private computeCollapsableRegions() : TPromise<IFoldingRange[]> {
let tabSize = this.editor.getIndentationOptions().tabSize;
var model = this.editor.getModel();
if (!model) {
return TPromise.as([]);
}
let ranges = foldStrategy.computeRanges(model, tabSize);
return TPromise.as(ranges);
}
private _onEditorMouseDown(e:IMouseEvent): void {
if (e.target.type !== EditorCommon.MouseTargetType.GUTTER_LINE_DECORATIONS) {
return;
}
if (this.decorations.length === 0) {
return;
}
var position = e.target.position;
if (!position) {
return;
}
var model = this.editor.getModel();
var hasChanges = false;
this.editor.changeDecorations((changeAccessor:EditorCommon.IModelDecorationsChangeAccessor) => {
for (var i = 0; i < this.decorations.length; i++) {
var dec = this.decorations[i];
var decRange = model.getDecorationRange(dec.rangeDecorationId);
if (decRange.startLineNumber === position.lineNumber) {
dec.isCollapsed = !dec.isCollapsed;
changeAccessor.changeDecorationOptions(dec.visualDecorationId, dec.getVisualDecorationOptions());
hasChanges = true;
break;
}
}
});
if (hasChanges) {
this.updateHiddenAreas();
}
}
private updateHiddenAreas(): void {
var model = this.editor.getModel();
var hiddenAreas:EditorCommon.IRange[] = [];
this.decorations.filter((dec) => dec.isCollapsed).forEach((dec) => {
var decRange = model.getDecorationRange(dec.rangeDecorationId);
hiddenAreas.push({
startLineNumber: decRange.startLineNumber + 1,
startColumn: 1,
endLineNumber: decRange.endLineNumber,
endColumn: 1
});
});
this.editor.setHiddenAreas(hiddenAreas);
}
}
EditorBrowserRegistry.registerEditorContribution(Folding);
\ 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;
}
\ 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';
import {TPromise} from 'vs/base/common/winjs.base';
import EditorCommon = require('vs/editor/common/editorCommon');
import {IFoldingRange} from 'vs/editor/contrib/folding/common/foldingRange';
export function computeRanges(model:EditorCommon.IModel, tabSize: number, minimumRangeSize: number = 1) : IFoldingRange[] {
let result : IFoldingRange[] = [];
let createRange = (startLineNumber: number, endLineNumber: number) => {
// create a collapsible region only if has the minimumRangeSize
if (endLineNumber - startLineNumber >= minimumRangeSize) {
result.push({startLineNumber, endLineNumber});
}
}
var previousRegions : { indent: number, line: number}[] = [];
previousRegions.push({indent: -1, line: model.getLineCount() + 1}); // sentinel, not make sure there's at least one entry
for (var line = model.getLineCount(); line > 0; line--) {
var indent = computeIndentLevel(model.getLineContent(line), tabSize);
let previous = previousRegions[previousRegions.length - 1];
// all previous regions with larger indent can be completed
if (previous.indent > indent) {
do {
previousRegions.pop();
previous = previousRegions[previousRegions.length - 1];
} while (previous.indent > indent);
createRange(line, previous.line - 1);
if (previous.indent === indent) {
previous.line = line;
}
}
if (previous.indent < indent) {
// new region with a bigger indent
previousRegions.push({indent, line});
}
}
return result;
}
function computeIndentLevel(line: string, tabSize: number): number {
let i = 0;
let indent = 0;
while (i < line.length) {
let ch = line.charAt(i);
if (ch === ' ') {
indent++;
} else if (ch === '\t') {
indent++;
indent += (indent % tabSize);
} else {
break;
}
i++;
}
return indent;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import assert = require('assert');
import foldStrategy = require('vs/editor/contrib/folding/common/indentFoldStrategy');
import {IFoldingRange} from 'vs/editor/contrib/folding/common/foldingRange';
import {Model} from 'vs/editor/common/model/model';
suite('Folding', () => {
function assertRanges(lines: string[], tabSize: number, expected:IFoldingRange[]): void {
let model = new Model(lines.join('\n'), null);
let actual = foldStrategy.computeRanges(model, tabSize);
actual.sort((r1, r2) => r1.startLineNumber - r2.startLineNumber);
assert.deepEqual(actual, expected);
model.dispose();
}
function r(startLineNumber: number, endLineNumber: number): IFoldingRange {
return { startLineNumber, endLineNumber };
}
test('t1', () => {
assertRanges([
'A',
' A',
' A',
' A'
], 4, [r(1, 4)]);
});
test('t2', () => {
assertRanges([
'A',
' A',
' A',
' A',
' A'
], 4, [r(1, 5), r(3, 5)] );
});
test('t3', () => {
assertRanges([
'A',
' A',
' A',
' A',
'A'
], 4, [r(1, 4), r(2, 4), r(3, 4)] );
});
test('t4', () => {
assertRanges([
' A',
' A',
'A'
], 4, [] );
});
test('Javadoc', () => {
assertRanges([
'/**',
' * Comment',
' */',
'class A {',
' void foo() {',
' }',
'}',
], 4, [r(1, 3), r(4, 6)] );
});
})
\ No newline at end of file
......@@ -83,8 +83,8 @@ function pos(lineNumber: number, column: number): Position.Position {
return new Position.Position(lineNumber, column);
}
function createSplitLine(splitLengths:number[], wrappedLinesPrefix:string): SplitLinesCollection.SplitLine {
return new SplitLinesCollection.SplitLine(createLineMapping(splitLengths, wrappedLinesPrefix));
function createSplitLine(splitLengths:number[], wrappedLinesPrefix:string, isVisible: boolean = true): SplitLinesCollection.SplitLine {
return new SplitLinesCollection.SplitLine(createLineMapping(splitLengths, wrappedLinesPrefix), isVisible);
}
function createLineMapping(breakingLengths:number[], wrappedLinesPrefix:string): SplitLinesCollection.ILineMapping {
......
......@@ -111,6 +111,7 @@ export abstract class BaseTextEditor extends BaseEditor {
overviewRulerLanes: 3,
readOnly: this.contextService.getOptions().readOnly,
glyphMargin: true,
lineDecorationsWidth: 24,
lineNumbersMinChars: 3,
theme: this._storageService.get(Preferences.THEME, StorageScope.GLOBAL, DEFAULT_THEME_ID)
};
......
......@@ -49,24 +49,20 @@
/* Git dirty diff editor decorations */
.monaco-editor .git-dirty-modified-diff-glyph {
background-color: rgba(0, 122, 204, 0.6);
border-left: 3px solid rgba(0, 122, 204, 0.6);
margin-left: 5px;
width: 3px !important;
}
.monaco-editor.vs-dark .git-dirty-modified-diff-glyph {
background-color: rgba(0, 188, 242, 0.6);
border-left: 3px solid rgba(0, 188, 242, 0.6);
margin-left: 5px;
width: 3px !important;
}
.monaco-editor .git-dirty-added-diff-glyph {
background-color: rgba(45, 136, 62, 0.6);
border-left: 3px solid rgba(45, 136, 62, 0.6);
margin-left: 5px;
width: 3px !important;
}
.monaco-editor.vs-dark .git-dirty-added-diff-glyph {
background-color: rgba(127, 186, 0, 0.6);
border-left: 3px solid rgba(127, 186, 0, 0.6);
margin-left: 5px;
width: 3px !important;
}
.monaco-editor .git-dirty-deleted-diff-glyph:after {
content: '';
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册