提交 0faa5d11 编写于 作者: M Martin Aeschlimann

folding provider: first version

上级 c2a227b7
......@@ -8,8 +8,8 @@ import * as path from 'path';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { workspace, languages, ExtensionContext, extensions, Uri, LanguageConfiguration } from 'vscode';
import { LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType, DidChangeConfigurationNotification } from 'vscode-languageclient';
import { workspace, languages, ExtensionContext, extensions, Uri, LanguageConfiguration, TextDocument, FoldingRangeList as VSFoldingRangeList, FoldingRange as VSFoldingRange } from 'vscode';
import { LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType, DidChangeConfigurationNotification, TextDocumentIdentifier } from 'vscode-languageclient';
import TelemetryReporter from 'vscode-extension-telemetry';
import { hash } from './utils/hash';
......@@ -30,6 +30,57 @@ namespace SchemaAssociationNotification {
export const type: NotificationType<ISchemaAssociations, any> = new NotificationType('json/schemaAssociations');
}
interface FoldingRangeList {
/**
* The folding ranges.
*/
ranges: FoldingRange[];
}
export enum FoldingRangeType {
/**
* Folding range for a comment
*/
Comment = 'comment',
/**
* Folding range for a imports or includes
*/
Imports = 'imports',
/**
* Folding range for a region (e.g. `#region`)
*/
Region = 'region'
}
interface FoldingRange {
/**
* The start line number
*/
startLine: number;
/**
* The end line number
*/
endLine: number;
/**
* The actual color value for this color range.
*/
type?: FoldingRangeType;
}
interface FoldingRangeRequest {
/**
* The text document.
*/
textDocument: TextDocumentIdentifier;
}
namespace FoldingRangesRequest {
export const type: RequestType<FoldingRangeRequest, FoldingRangeList | null, any, any> = new RequestType('textDocument/foldingRanges');
}
interface IPackageInfo {
name: string;
version: string;
......@@ -124,6 +175,17 @@ export function activate(context: ExtensionContext) {
toDispose.push(workspace.onDidCloseTextDocument(d => handleContentChange(d.uri)));
client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociation(context));
languages.registerFoldingProvider(documentSelector, {
provideFoldingRanges(document: TextDocument) {
return client.sendRequest(FoldingRangesRequest.type, { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document) }).then(res => {
if (res && Array.isArray(res.ranges)) {
return new VSFoldingRangeList(res.ranges.map(r => new VSFoldingRange(r.startLine, r.endLine, r.type)));
}
return null;
});
}
});
});
let languageConfiguration: LanguageConfiguration = {
......
......@@ -7,7 +7,7 @@
import {
createConnection, IConnection,
TextDocuments, TextDocument, InitializeParams, InitializeResult, NotificationType, RequestType,
DocumentRangeFormattingRequest, Disposable, ServerCapabilities
DocumentRangeFormattingRequest, Disposable, ServerCapabilities, TextDocumentIdentifier
} from 'vscode-languageserver';
import { DocumentColorRequest, ServerCapabilities as CPServerCapabilities, ColorPresentationRequest } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed';
......@@ -20,6 +20,7 @@ import Strings = require('./utils/strings');
import { formatError, runSafe, runSafeAsync } from './utils/errors';
import { JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration } from 'vscode-json-languageservice';
import { getLanguageModelCache } from './languageModelCache';
import { createScanner, SyntaxKind } from 'jsonc-parser';
interface ISchemaAssociations {
[pattern: string]: string[];
......@@ -37,6 +38,57 @@ namespace SchemaContentChangeNotification {
export const type: NotificationType<string, any> = new NotificationType('json/schemaContent');
}
interface FoldingRangeList {
/**
* The folding ranges.
*/
ranges: FoldingRange[];
}
export enum FoldingRangeType {
/**
* Folding range for a comment
*/
Comment = 'comment',
/**
* Folding range for a imports or includes
*/
Imports = 'imports',
/**
* Folding range for a region (e.g. `#region`)
*/
Region = 'region'
}
interface FoldingRange {
/**
* The start line number
*/
startLine: number;
/**
* The end line number
*/
endLine: number;
/**
* The actual color value for this color range.
*/
type?: FoldingRangeType | string;
}
interface FoldingRangeRequest {
/**
* The text document.
*/
textDocument: TextDocumentIdentifier;
}
namespace FoldingRangesRequest {
export const type: RequestType<FoldingRangeRequest, FoldingRangeList | null, any, any> = new RequestType('textDocument/foldingRanges');
}
// Create a connection for the server
let connection: IConnection = createConnection();
......@@ -347,7 +399,83 @@ connection.onRequest(ColorPresentationRequest.type, params => {
return languageService.getColorPresentations(document, jsonDocument, params.color, params.range);
}
return [];
}, [], `Error while computing color presentationsd for ${params.textDocument.uri}`);
}, [], `Error while computing color presentations for ${params.textDocument.uri}`);
});
connection.onRequest(FoldingRangesRequest.type, params => {
return runSafe(() => {
let document = documents.get(params.textDocument.uri);
if (document) {
let ranges: FoldingRange[] = [];
let stack: FoldingRange[] = [];
let prevStart = -1;
let scanner = createScanner(document.getText(), false);
let token = scanner.scan();
while (token !== SyntaxKind.EOF) {
switch (token) {
case SyntaxKind.OpenBraceToken:
case SyntaxKind.OpenBracketToken: {
let startLine = document.positionAt(scanner.getTokenOffset()).line;
let range = { startLine, endLine: startLine, type: token === SyntaxKind.OpenBraceToken ? 'object' : 'array' };
stack.push(range);
}
break;
case SyntaxKind.CloseBraceToken:
case SyntaxKind.CloseBracketToken: {
let type = token === SyntaxKind.CloseBraceToken ? 'object' : 'array';
if (stack.length > 0 && stack[stack.length - 1].type === type) {
let range = stack.pop();
let line = document.positionAt(scanner.getTokenOffset()).line;
if (range && line > range.startLine + 1 && prevStart !== range.startLine) {
range.endLine = line - 1;
ranges.push(range);
prevStart = range.startLine;
}
}
}
break;
case SyntaxKind.BlockCommentTrivia: {
let startLine = document.positionAt(scanner.getTokenOffset()).line;
let endLine = document.positionAt(scanner.getTokenOffset() + scanner.getTokenLength()).line;
if (startLine < endLine) {
ranges.push({ startLine, endLine, type: FoldingRangeType.Comment });
prevStart = startLine;
}
}
break;
case SyntaxKind.LineCommentTrivia: {
let text = document.getText().substr(scanner.getTokenOffset(), scanner.getTokenLength());
let m = text.match(/^\/\/\s*#(region\b)|(endregion\b)/);
if (m) {
let line = document.positionAt(scanner.getTokenOffset()).line;
if (m[1]) { // start pattern match
let range = { startLine: line, endLine: line, type: FoldingRangeType.Region };
stack.push(range);
} else {
let i = stack.length - 1;
while (i >= 0 && stack[i].type !== FoldingRangeType.Region) {
i--;
}
if (i >= 0) {
let range = stack[i];
stack.length = i;
if (line > range.startLine && prevStart !== range.startLine) {
range.endLine = line;
ranges.push(range);
prevStart = range.startLine;
}
}
}
}
}
break;
}
token = scanner.scan();
}
return <FoldingRangeList>{ ranges };
}
return null;
}, null, `Error while computing folding ranges for ${params.textDocument.uri}`);
});
// Listen on the connection
......
......@@ -826,6 +826,67 @@ export interface DocumentColorProvider {
provideColorPresentations(model: model.ITextModel, colorInfo: IColorInformation, token: CancellationToken): IColorPresentation[] | Thenable<IColorPresentation[]>;
}
/**
* A provider of colors for editor models.
*/
/**
* @internal
*/
export interface FoldingProvider {
/**
* Provides the color ranges for a specific model.
*/
provideFoldingRanges(model: model.ITextModel, token: CancellationToken): IFoldingRangeList | Thenable<IFoldingRangeList>;
}
/**
* @internal
*/
export interface IFoldingRangeList {
ranges: IFoldingRange[];
}
/**
* @internal
*/
export interface IFoldingRange {
/**
* The start line number
*/
startLineNumber: number;
/**
* The end line number
*/
endLineNumber: number;
/**
* The optional type of the folding range
*/
type?: FoldingRangeType | string;
// auto-collapse
// header span
}
/**
* @internal
*/
export enum FoldingRangeType {
/**
* Folding range for a comment
*/
Comment = 'comment',
/**
* Folding range for a imports or includes
*/
Imports = 'imports',
/**
* Folding range for a region (e.g. `#region`)
*/
Region = 'region'
}
/**
* @internal
*/
......@@ -971,6 +1032,11 @@ export const LinkProviderRegistry = new LanguageFeatureRegistry<LinkProvider>();
*/
export const ColorProviderRegistry = new LanguageFeatureRegistry<DocumentColorProvider>();
/**
* @internal
*/
export const FoldingProviderRegistry = new LanguageFeatureRegistry<FoldingProvider>();
/**
* @internal
*/
......
......@@ -19,17 +19,24 @@ import { registerEditorAction, registerEditorContribution, ServicesAccessor, Edi
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { FoldingModel, setCollapseStateAtLevel, CollapseMemento, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateForMatchingLines } from 'vs/editor/contrib/folding/foldingModel';
import { FoldingDecorationProvider } from './foldingDecorations';
import { FoldingRegions } from './foldingRanges';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
import { IMarginData, IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget';
import { HiddenRangeModel } from 'vs/editor/contrib/folding/hiddenRangeModel';
import { IRange } from 'vs/editor/common/core/range';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { computeRanges as computeIndentRanges } from 'vs/editor/contrib/folding/indentRangeProvider';
import { IndentRangeProvider } from 'vs/editor/contrib/folding/indentRangeProvider';
import { IPosition } from 'vs/editor/common/core/position';
import { FoldingProviderRegistry } from 'vs/editor/common/modes';
import { SyntaxRangeProvider } from './syntaxRangeProvider';
export const ID = 'editor.contrib.folding';
export interface RangeProvider {
compute(editorModel: ITextModel): TPromise<FoldingRegions>;
}
export class FoldingController implements IEditorContribution {
static MAX_FOLDING_REGIONS = 5000;
......@@ -48,6 +55,8 @@ export class FoldingController implements IEditorContribution {
private foldingModel: FoldingModel;
private hiddenRangeModel: HiddenRangeModel;
private rangeProvider: RangeProvider;
private foldingModelPromise: TPromise<FoldingModel>;
private updateScheduler: Delayer<FoldingModel>;
......@@ -69,6 +78,7 @@ export class FoldingController implements IEditorContribution {
this.foldingDecorationProvider.autoHideFoldingControls = this._autoHideFoldingControls;
this.globalToDispose.push(this.editor.onDidChangeModel(() => this.onModelChanged()));
this.globalToDispose.push(FoldingProviderRegistry.onDidChange(() => this.onModelChanged()));
this.globalToDispose.push(this.editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => {
if (e.contribInfo) {
......@@ -167,17 +177,22 @@ export class FoldingController implements IEditorContribution {
this.foldingModelPromise = null;
this.hiddenRangeModel = null;
this.cursorChangedScheduler = null;
this.rangeProvider = null;
}
});
this.onModelContentChanged();
}
private computeRanges(editorModel: ITextModel) {
let foldingRules = LanguageConfigurationRegistry.getFoldingRules(editorModel.getLanguageIdentifier().id);
let offSide = foldingRules && foldingRules.offSide;
let markers = foldingRules && foldingRules.markers;
let ranges = computeIndentRanges(editorModel, offSide, markers);
return ranges;
private getRangeProvider(): RangeProvider {
if (!this.rangeProvider) {
let foldingProviders = FoldingProviderRegistry.ordered(this.foldingModel.textModel);
if (foldingProviders.length) {
this.rangeProvider = new SyntaxRangeProvider(foldingProviders);
} else {
this.rangeProvider = new IndentRangeProvider();
}
}
return this.rangeProvider;
}
public getFoldingModel() {
......@@ -191,9 +206,12 @@ export class FoldingController implements IEditorContribution {
// some cursors might have moved into hidden regions, make sure they are in expanded regions
let selections = this.editor.getSelections();
let selectionLineNumbers = selections ? selections.map(s => s.startLineNumber) : [];
this.foldingModel.update(this.computeRanges(this.foldingModel.textModel), selectionLineNumbers);
return this.getRangeProvider().compute(this.foldingModel.textModel).then(foldingRanges => {
this.foldingModel.update(foldingRanges, selectionLineNumbers);
return this.foldingModel;
});
}
return this.foldingModel;
return null;
});
}
}
......
......@@ -5,7 +5,7 @@
import { ITextModel, IModelDecorationOptions, IModelDeltaDecoration, IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
import Event, { Emitter } from 'vs/base/common/event';
import { FoldingRanges, ILineRange, FoldingRegion } from './foldingRanges';
import { FoldingRegions, ILineRange, FoldingRegion } from './foldingRanges';
export interface IDecorationProvider {
getDecorationOption(isCollapsed: boolean): IModelDecorationOptions;
......@@ -24,13 +24,13 @@ export class FoldingModel {
private _textModel: ITextModel;
private _decorationProvider: IDecorationProvider;
private _ranges: FoldingRanges;
private _regions: FoldingRegions;
private _editorDecorationIds: string[];
private _isInitialized: boolean;
private _updateEventEmitter = new Emitter<FoldingModelChangeEvent>();
public get ranges(): FoldingRanges { return this._ranges; }
public get regions(): FoldingRegions { return this._regions; }
public get onDidChange(): Event<FoldingModelChangeEvent> { return this._updateEventEmitter.event; }
public get textModel() { return this._textModel; }
public get isInitialized() { return this._isInitialized; }
......@@ -38,7 +38,7 @@ export class FoldingModel {
constructor(textModel: ITextModel, decorationProvider: IDecorationProvider) {
this._textModel = textModel;
this._decorationProvider = decorationProvider;
this._ranges = new FoldingRanges(new Uint32Array(0), new Uint32Array(0));
this._regions = new FoldingRegions(new Uint32Array(0), new Uint32Array(0));
this._editorDecorationIds = [];
this._isInitialized = false;
}
......@@ -54,8 +54,8 @@ export class FoldingModel {
let editorDecorationId = this._editorDecorationIds[index];
if (editorDecorationId && !processed[editorDecorationId]) {
processed[editorDecorationId] = true;
let newCollapseState = !this._ranges.isCollapsed(index);
this._ranges.setCollapsed(index, newCollapseState);
let newCollapseState = !this._regions.isCollapsed(index);
this._regions.setCollapsed(index, newCollapseState);
accessor.changeDecorationOptions(editorDecorationId, this._decorationProvider.getDecorationOption(newCollapseState));
}
}
......@@ -63,7 +63,7 @@ export class FoldingModel {
this._updateEventEmitter.fire({ model: this, collapseStateChanged: regions });
}
public update(newRanges: FoldingRanges, blockedLineNumers: number[] = []): void {
public update(newRegions: FoldingRegions, blockedLineNumers: number[] = []): void {
let newEditorDecorations = [];
let isBlocked = (startLineNumber, endLineNumber) => {
......@@ -76,11 +76,11 @@ export class FoldingModel {
};
let initRange = (index: number, isCollapsed: boolean) => {
let startLineNumber = newRanges.getStartLineNumber(index);
if (isCollapsed && isBlocked(startLineNumber, newRanges.getEndLineNumber(index))) {
let startLineNumber = newRegions.getStartLineNumber(index);
if (isCollapsed && isBlocked(startLineNumber, newRegions.getEndLineNumber(index))) {
isCollapsed = false;
}
newRanges.setCollapsed(index, isCollapsed);
newRegions.setCollapsed(index, isCollapsed);
let maxColumn = this._textModel.getLineMaxColumn(startLineNumber);
let decorationRange = {
startLineNumber: startLineNumber,
......@@ -93,8 +93,8 @@ export class FoldingModel {
let i = 0;
let nextCollapsed = () => {
while (i < this._ranges.length) {
let isCollapsed = this._ranges.isCollapsed(i);
while (i < this._regions.length) {
let isCollapsed = this._regions.isCollapsed(i);
i++;
if (isCollapsed) {
return i - 1;
......@@ -105,14 +105,14 @@ export class FoldingModel {
let k = 0;
let collapsedIndex = nextCollapsed();
while (collapsedIndex !== -1 && k < newRanges.length) {
while (collapsedIndex !== -1 && k < newRegions.length) {
// get the latest range
let decRange = this._textModel.getDecorationRange(this._editorDecorationIds[collapsedIndex]);
if (decRange) {
let collapsedStartLineNumber = decRange.startLineNumber;
if (this._textModel.getLineMaxColumn(collapsedStartLineNumber) === decRange.startColumn) { // test that the decoration is still at the end otherwise it got deleted
while (k < newRanges.length) {
let startLineNumber = newRanges.getStartLineNumber(k);
while (k < newRegions.length) {
let startLineNumber = newRegions.getStartLineNumber(k);
if (collapsedStartLineNumber >= startLineNumber) {
initRange(k, collapsedStartLineNumber === startLineNumber);
k++;
......@@ -124,13 +124,13 @@ export class FoldingModel {
}
collapsedIndex = nextCollapsed();
}
while (k < newRanges.length) {
while (k < newRegions.length) {
initRange(k, false);
k++;
}
this._editorDecorationIds = this._decorationProvider.deltaDecorations(this._editorDecorationIds, newEditorDecorations);
this._ranges = newRanges;
this._regions = newRegions;
this._isInitialized = true;
this._updateEventEmitter.fire({ model: this });
}
......@@ -140,12 +140,12 @@ export class FoldingModel {
*/
public getMemento(): CollapseMemento {
let collapsedRanges: ILineRange[] = [];
for (let i = 0; i < this._ranges.length; i++) {
if (this._ranges.isCollapsed(i)) {
for (let i = 0; i < this._regions.length; i++) {
if (this._regions.isCollapsed(i)) {
let range = this._textModel.getDecorationRange(this._editorDecorationIds[i]);
if (range) {
let startLineNumber = range.startLineNumber;
let endLineNumber = range.endLineNumber + this._ranges.getEndLineNumber(i) - this._ranges.getStartLineNumber(i);
let endLineNumber = range.endLineNumber + this._regions.getEndLineNumber(i) - this._regions.getStartLineNumber(i);
collapsedRanges.push({ startLineNumber, endLineNumber });
}
}
......@@ -179,11 +179,11 @@ export class FoldingModel {
getAllRegionsAtLine(lineNumber: number, filter?: (r: FoldingRegion, level: number) => boolean): FoldingRegion[] {
let result: FoldingRegion[] = [];
if (this._ranges) {
let index = this._ranges.findRange(lineNumber);
if (this._regions) {
let index = this._regions.findRange(lineNumber);
let level = 1;
while (index >= 0) {
let current = this._ranges.toRegion(index);
let current = this._regions.toRegion(index);
if (!filter || filter(current, level)) {
result.push(current);
}
......@@ -195,10 +195,10 @@ export class FoldingModel {
}
getRegionAtLine(lineNumber: number): FoldingRegion {
if (this._ranges) {
let index = this._ranges.findRange(lineNumber);
if (this._regions) {
let index = this._regions.findRange(lineNumber);
if (index >= 0) {
return this._ranges.toRegion(index);
return this._regions.toRegion(index);
}
}
return null;
......@@ -210,9 +210,9 @@ export class FoldingModel {
let levelStack: FoldingRegion[] = trackLevel ? [] : null;
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 = this._ranges.toRegion(i);
if (this._ranges.getStartLineNumber(i) < endLineNumber) {
for (let i = index, len = this._regions.length; i < len; i++) {
let current = this._regions.toRegion(i);
if (this._regions.getStartLineNumber(i) < endLineNumber) {
if (trackLevel) {
while (levelStack.length > 0 && !current.containedBy(levelStack[levelStack.length - 1])) {
levelStack.pop();
......@@ -296,13 +296,13 @@ export function setCollapseStateAtLevel(foldingModel: FoldingModel, foldLevel: n
*/
export function setCollapseStateForMatchingLines(foldingModel: FoldingModel, regExp: RegExp, doCollapse: boolean): void {
let editorModel = foldingModel.textModel;
let ranges = foldingModel.ranges;
let regions = foldingModel.regions;
let toToggle = [];
for (let i = ranges.length - 1; i >= 0; i--) {
if (doCollapse !== ranges.isCollapsed(i)) {
let startLineNumber = ranges.getStartLineNumber(i);
for (let i = regions.length - 1; i >= 0; i--) {
if (doCollapse !== regions.isCollapsed(i)) {
let startLineNumber = regions.getStartLineNumber(i);
if (regExp.test(editorModel.getLineContent(startLineNumber))) {
toToggle.push(ranges.toRegion(i));
toToggle.push(regions.toRegion(i));
}
}
}
......
......@@ -15,7 +15,7 @@ export const MAX_LINE_NUMBER = 0xFFFFFF;
const MASK_INDENT = 0xFF000000;
export class FoldingRanges {
export class FoldingRegions {
private _startIndexes: Uint32Array;
private _endIndexes: Uint32Array;
private _collapseStates: Uint32Array;
......@@ -138,7 +138,7 @@ export class FoldingRanges {
export class FoldingRegion {
constructor(private ranges: FoldingRanges, private index: number) {
constructor(private ranges: FoldingRegions, private index: number) {
}
public get startLineNumber() {
......
......@@ -23,7 +23,7 @@ export class HiddenRangeModel {
this._foldingModel = model;
this._foldingModelListener = model.onDidChange(_ => this.updateHiddenRanges());
this._hiddenRanges = [];
if (model.ranges.length) {
if (model.regions.length) {
this.updateHiddenRanges();
}
}
......@@ -37,7 +37,7 @@ export class HiddenRangeModel {
let lastCollapsedStart = Number.MAX_VALUE;
let lastCollapsedEnd = -1;
let ranges = this._foldingModel.ranges;
let ranges = this._foldingModel.regions;
for (; i < ranges.length; i++) {
if (!ranges.isCollapsed(i)) {
continue;
......
......@@ -7,25 +7,37 @@
import { ITextModel } from 'vs/editor/common/model';
import { FoldingMarkers } from 'vs/editor/common/modes/languageConfiguration';
import { FoldingRanges, MAX_LINE_NUMBER } from 'vs/editor/contrib/folding/foldingRanges';
import { FoldingRegions, MAX_LINE_NUMBER } from 'vs/editor/contrib/folding/foldingRanges';
import { TextModel } from 'vs/editor/common/model/textModel';
import { RangeProvider } from './folding';
import { TPromise } from 'vs/base/common/winjs.base';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
const MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT = 5000;
export class IndentRangeProvider implements RangeProvider {
compute(editorModel: ITextModel): TPromise<FoldingRegions> {
let foldingRules = LanguageConfigurationRegistry.getFoldingRules(editorModel.getLanguageIdentifier().id);
let offSide = foldingRules && foldingRules.offSide;
let markers = foldingRules && foldingRules.markers;
return TPromise.as(computeRanges(editorModel, offSide, markers));
}
}
// public only for testing
export class RangesCollector {
private _startIndexes: number[];
private _endIndexes: number[];
private _indentOccurrences: number[];
private _length: number;
private _FoldingRangesLimit: number;
private _foldingRangesLimit: number;
constructor(FoldingRangesLimit: number) {
constructor(foldingRangesLimit: number) {
this._startIndexes = [];
this._endIndexes = [];
this._indentOccurrences = [];
this._length = 0;
this._FoldingRangesLimit = FoldingRangesLimit;
this._foldingRangesLimit = foldingRangesLimit;
}
public insertFirst(startLineNumber: number, endLineNumber: number, indent: number) {
......@@ -42,7 +54,7 @@ export class RangesCollector {
}
public toIndentRanges(model: ITextModel) {
if (this._length <= this._FoldingRangesLimit) {
if (this._length <= this._foldingRangesLimit) {
// reverse and create arrays of the exact length
let startIndexes = new Uint32Array(this._length);
let endIndexes = new Uint32Array(this._length);
......@@ -50,14 +62,14 @@ export class RangesCollector {
startIndexes[k] = this._startIndexes[i];
endIndexes[k] = this._endIndexes[i];
}
return new FoldingRanges(startIndexes, endIndexes);
return new FoldingRegions(startIndexes, endIndexes);
} else {
let entries = 0;
let maxIndent = this._indentOccurrences.length;
for (let i = 0; i < this._indentOccurrences.length; i++) {
let n = this._indentOccurrences[i];
if (n) {
if (n + entries > this._FoldingRangesLimit) {
if (n + entries > this._foldingRangesLimit) {
maxIndent = i;
break;
}
......@@ -78,7 +90,7 @@ export class RangesCollector {
k++;
}
}
return new FoldingRanges(startIndexes, endIndexes);
return new FoldingRegions(startIndexes, endIndexes);
}
}
......@@ -87,9 +99,9 @@ export class RangesCollector {
interface PreviousRegion { indent: number; line: number; marker: boolean; }
export function computeRanges(model: ITextModel, offSide: boolean, markers?: FoldingMarkers, FoldingRangesLimit = MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT): FoldingRanges {
export function computeRanges(model: ITextModel, offSide: boolean, markers?: FoldingMarkers, foldingRangesLimit = MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT): FoldingRegions {
const tabSize = model.getOptions().tabSize;
let result = new RangesCollector(FoldingRangesLimit);
let result = new RangesCollector(foldingRangesLimit);
let pattern = void 0;
if (markers) {
......
/*---------------------------------------------------------------------------------------------
* 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 { FoldingProvider, IFoldingRange } from 'vs/editor/common/modes';
import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { asWinJsPromise } from 'vs/base/common/async';
import { ITextModel } from 'vs/editor/common/model';
import { RangeProvider } from './folding';
import { TPromise } from 'vs/base/common/winjs.base';
import { MAX_LINE_NUMBER, FoldingRegions } from './foldingRanges';
const MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT = 5000;
export interface IFoldingRangeData extends IFoldingRange {
rank: number;
}
export class SyntaxRangeProvider implements RangeProvider {
constructor(private providers: FoldingProvider[]) {
}
compute(model: ITextModel): TPromise<FoldingRegions> {
return collectSyntaxRanges(this.providers, model).then(ranges => {
return sanitizeRanges(ranges);
});
}
}
function collectSyntaxRanges(providers: FoldingProvider[], model: ITextModel): TPromise<IFoldingRangeData[]> {
const rangeData: IFoldingRangeData[] = [];
let promises = providers.map((provider, rank) => asWinJsPromise(token => provider.provideFoldingRanges(model, token)).then(list => {
if (list && Array.isArray(list.ranges)) {
for (let r of list.ranges) {
rangeData.push({ startLineNumber: r.startLineNumber, endLineNumber: r.endLineNumber, rank, type: r.type });
}
}
}, onUnexpectedExternalError));
return TPromise.join(promises).then(() => {
return rangeData;
});
}
export class RangesCollector {
private _startIndexes: number[];
private _endIndexes: number[];
private _nestingLevels: number[];
private _nestingLevelCounts: number[];
private _length: number;
private _foldingRangesLimit: number;
constructor(foldingRangesLimit: number) {
this._startIndexes = [];
this._endIndexes = [];
this._nestingLevels = [];
this._nestingLevelCounts = [];
this._length = 0;
this._foldingRangesLimit = foldingRangesLimit;
}
public add(startLineNumber: number, endLineNumber: number, nestingLevel: number) {
if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) {
return;
}
let index = this._length;
this._startIndexes[index] = startLineNumber;
this._endIndexes[index] = endLineNumber;
this._nestingLevels[index] = nestingLevel;
this._length++;
if (nestingLevel < 30) {
this._nestingLevelCounts[nestingLevel] = (this._nestingLevelCounts[nestingLevel] || 0) + 1;
}
}
public toIndentRanges() {
if (this._length <= this._foldingRangesLimit) {
let startIndexes = new Uint32Array(this._length);
let endIndexes = new Uint32Array(this._length);
for (let i = 0; i < this._length; i++) {
startIndexes[i] = this._startIndexes[i];
endIndexes[i] = this._endIndexes[i];
}
return new FoldingRegions(startIndexes, endIndexes);
} else {
let entries = 0;
let maxLevel = this._nestingLevelCounts.length;
for (let i = 0; i < this._nestingLevelCounts.length; i++) {
let n = this._nestingLevelCounts[i];
if (n) {
if (n + entries > this._foldingRangesLimit) {
maxLevel = i;
break;
}
entries += n;
}
}
let startIndexes = new Uint32Array(entries);
let endIndexes = new Uint32Array(entries);
for (let i = 0, k = 0; i < this._length; i++) {
let level = this._nestingLevels[i];
if (level < maxLevel) {
startIndexes[k] = this._startIndexes[i];
endIndexes[k] = this._endIndexes[i];
k++;
}
}
return new FoldingRegions(startIndexes, endIndexes);
}
}
}
export function sanitizeRanges(rangeData: IFoldingRangeData[]): FoldingRegions {
let sorted = rangeData.sort((d1, d2) => {
let diff = d1.startLineNumber - d2.startLineNumber;
if (diff === 0) {
diff = d1.rank - d2.rank;
}
return diff;
});
let collector = new RangesCollector(MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT);
let top: IFoldingRangeData = null;
let previous = [];
for (let entry of sorted) {
if (!top) {
top = entry;
collector.add(entry.startLineNumber, entry.endLineNumber, previous.length);
} else {
if (entry.startLineNumber > top.startLineNumber) {
if (entry.endLineNumber <= top.endLineNumber) {
previous.push(top);
top = entry;
collector.add(entry.startLineNumber, entry.endLineNumber, previous.length);
} else if (entry.startLineNumber > top.endLineNumber) {
do {
top = previous.pop();
} while (top && entry.startLineNumber > top.endLineNumber);
previous.push(top);
top = entry;
collector.add(entry.startLineNumber, entry.endLineNumber, previous.length);
}
}
}
}
return collector.toIndentRanges();
}
\ No newline at end of file
......@@ -61,7 +61,7 @@ suite('Folding Model', () => {
function assertFoldedRanges(foldingModel: FoldingModel, expectedRegions: ExpectedRegion[], message?: string) {
let actualRanges = [];
let actual = foldingModel.ranges;
let actual = foldingModel.regions;
for (let i = 0; i < actual.length; i++) {
if (actual.isCollapsed(i)) {
actualRanges.push(r(actual.getStartLineNumber(i), actual.getEndLineNumber(i)));
......@@ -72,7 +72,7 @@ suite('Folding Model', () => {
function assertRanges(foldingModel: FoldingModel, expectedRegions: ExpectedRegion[], message?: string) {
let actualRanges = [];
let actual = foldingModel.ranges;
let actual = foldingModel.regions;
for (let i = 0; i < actual.length; i++) {
actualRanges.push(r(actual.getStartLineNumber(i), actual.getEndLineNumber(i), actual.isCollapsed(i)));
}
......
......@@ -389,6 +389,14 @@ export function registerColorProvider(languageId: string, provider: modes.Docume
return modes.ColorProviderRegistry.register(languageId, provider);
}
/**
* Register a folding provider
*/
/*export function registerFoldingProvider(languageId: string, provider: modes.FoldingProvider): IDisposable {
return modes.FoldingProviderRegistry.register(languageId, provider);
}*/
/**
* Contains additional diagnostic information about the context in which
* a [code action](#CodeActionProvider.provideCodeActions) is run.
......
......@@ -4047,6 +4047,9 @@ declare module monaco.languages {
*/
export function registerColorProvider(languageId: string, provider: DocumentColorProvider): IDisposable;
/**
* Register a folding provider
*/
/**
* Contains additional diagnostic information about the context in which
* a [code action](#CodeActionProvider.provideCodeActions) is run.
......
......@@ -7,6 +7,63 @@
declare module 'vscode' {
export class FoldingRangeList {
/**
* The folding ranges.
*/
ranges: FoldingRange[];
/**
* Creates mew folding range list.
*
* @param ranges The folding ranges
*/
constructor(ranges: FoldingRange[]);
}
export class FoldingRange {
/**
* The start line number (0-based)
*/
startLine: number;
/**
* The end line number (0-based)
*/
endLine: number;
/**
* The actual color value for this color range.
*/
type?: FoldingRangeType | string;
/**
* Creates a new folding range.
*
* @param startLineNumber The first line of the fold
* @param type The last line of the fold
*/
constructor(startLineNumber: number, endLineNumber: number, type?: FoldingRangeType);
}
export enum FoldingRangeType {
/**
* Folding range for a comment
*/
Comment = 'comment',
/**
* Folding range for a imports or includes
*/
Imports = 'imports',
/**
* Folding range for a region (e.g. `#region`)
*/
Region = 'region'
}
// export enum FileErrorCodes {
// /**
// * Not owner.
......@@ -340,10 +397,27 @@ declare module 'vscode' {
}
export namespace languages {
/**
* Register a folding provider.
*
* Multiple folding can be registered for a language. In that case providers are sorted
* by their [score](#languages.match) and the best-matching provider is used. Failure
* of the selected provider will cause a failure of the whole operation.
*
* @param selector A selector that defines the documents this provider is applicable to.
* @param provider A folding provider.
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerFoldingProvider(selector: DocumentSelector, provider: FoldingProvider): Disposable;
export interface RenameProvider2 extends RenameProvider {
resolveInitialRenameValue?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<RenameInitialValue>;
}
}
export interface FoldingProvider {
provideFoldingRanges(document: TextDocument, token: CancellationToken): ProviderResult<FoldingRangeList>;
}
/**
* Represents the validation type of the Source Control input.
......
......@@ -346,6 +346,17 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
});
}
// --- folding
$registerFoldingProvider(handle: number, selector: vscode.DocumentSelector): void {
const proxy = this._proxy;
this._registrations[handle] = modes.FoldingProviderRegistry.register(toLanguageSelector(selector), <modes.FoldingProvider>{
provideFoldingRanges: (model, token) => {
return wireCancellationToken(token, proxy.$provideFoldingRanges(handle, model.uri));
}
});
}
// --- configuration
private static _reviveRegExp(regExp: ISerializedRegExp): RegExp {
......
......@@ -292,6 +292,9 @@ export function createApiFactory(
registerColorProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentColorProvider): vscode.Disposable {
return extHostLanguageFeatures.registerColorProvider(selector, provider);
},
registerFoldingProvider: proposedApiFunction(extension, (selector: vscode.DocumentSelector, provider: vscode.FoldingProvider): vscode.Disposable => {
return extHostLanguageFeatures.registerFoldingProvider(selector, provider);
}),
setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration): vscode.Disposable => {
return extHostLanguageFeatures.setLanguageConfiguration(language, configuration);
}
......@@ -634,7 +637,10 @@ export function createApiFactory(
RelativePattern: extHostTypes.RelativePattern,
FileChangeType: extHostTypes.FileChangeType,
FileType: extHostTypes.FileType
FileType: extHostTypes.FileType,
FoldingRangeList: extHostTypes.FoldingRangeList,
FoldingRange: extHostTypes.FoldingRange,
FoldingRangeType: extHostTypes.FoldingRangeType
};
};
}
......
......@@ -282,6 +282,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
$registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): void;
$registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): void;
$registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): void;
$registerFoldingProvider(handle: number, selector: vscode.DocumentSelector): void;
$setLanguageConfiguration(handle: number, languageId: string, configuration: ISerializedLanguageConfiguration): void;
}
......@@ -692,6 +693,7 @@ export interface ExtHostLanguageFeaturesShape {
$resolveDocumentLink(handle: number, link: modes.ILink): TPromise<modes.ILink>;
$provideDocumentColors(handle: number, resource: UriComponents): TPromise<IRawColorInfo[]>;
$provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo): TPromise<modes.IColorPresentation[]>;
$provideFoldingRanges(handle: number, resource: UriComponents): TPromise<modes.IFoldingRangeList>;
}
export interface ExtHostQuickOpenShape {
......
......@@ -804,10 +804,29 @@ class ColorProviderAdapter {
}
}
class FoldingProviderAdapter {
constructor(
private _documents: ExtHostDocuments,
private _provider: vscode.FoldingProvider
) { }
provideFoldingRanges(resource: URI): TPromise<modes.IFoldingRangeList> {
const doc = this._documents.getDocumentData(resource).document;
return asWinJsPromise(token => this._provider.provideFoldingRanges(doc, token)).then(list => {
if (!Array.isArray(list.ranges)) {
return void 0;
}
return TypeConverters.FoldingRangeList.from(list);
});
}
}
type Adapter = OutlineAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter
| DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter
| RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
| SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter | ColorProviderAdapter;
| SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter
| ColorProviderAdapter | FoldingProviderAdapter;
export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
......@@ -1108,6 +1127,16 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColorPresentations(URI.revive(resource), colorInfo));
}
registerFoldingProvider(selector: vscode.DocumentSelector, provider: vscode.FoldingProvider): vscode.Disposable {
const handle = this._addNewAdapter(new FoldingProviderAdapter(this._documents, provider));
this._proxy.$registerFoldingProvider(handle, selector);
return this._createDisposable(handle);
}
$provideFoldingRanges(handle: number, resource: UriComponents): TPromise<modes.IFoldingRangeList> {
return this._withAdapter(handle, FoldingProviderAdapter, adapter => adapter.provideFoldingRanges(URI.revive(resource)));
}
// --- configuration
private static _serializeRegExp(regExp: RegExp): ISerializedRegExp {
......
......@@ -586,6 +586,14 @@ export namespace ProgressLocation {
}
}
export namespace FoldingRangeList {
export function from(rangeList: vscode.FoldingRangeList): modes.IFoldingRangeList {
return {
ranges: rangeList.ranges.map(r => ({ startLineNumber: r.startLine + 1, endLineNumber: r.endLine + 1 }))
};
}
}
export function toTextEditorOptions(options?: vscode.TextDocumentShowOptions): ITextEditorOptions {
if (options) {
return {
......
......@@ -1659,3 +1659,46 @@ export enum FileType {
}
//#endregion
//#region folding api
export class FoldingRangeList {
ranges: FoldingRange[];
constructor(ranges: FoldingRange[]) {
this.ranges = ranges;
}
}
export class FoldingRange {
startLine: number;
endLine: number;
type?: FoldingRangeType;
constructor(startLine: number, endLine: number, type?: FoldingRangeType) {
this.startLine = startLine;
this.endLine = endLine;
this.type = type;
}
}
export enum FoldingRangeType {
/**
* Folding range for a comment
*/
Comment = 'comment',
/**
* Folding range for a imports or includes
*/
Imports = 'imports',
/**
* Folding range for a region (e.g. `#region`)
*/
Region = 'region'
}
//#endregion
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册