mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 02:48:30 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
158
src/vs/editor/common/services/abstractCodeEditorService.ts
Normal file
158
src/vs/editor/common/services/abstractCodeEditorService.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { ICommonCodeEditor, ICommonDiffEditor, IDecorationRenderOptions, IModelDecorationOptions, IModel } from 'vs/editor/common/editorCommon';
|
||||
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
|
||||
|
||||
export abstract class AbstractCodeEditorService implements ICodeEditorService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private _onCodeEditorAdd: Emitter<ICommonCodeEditor>;
|
||||
private _onCodeEditorRemove: Emitter<ICommonCodeEditor>;
|
||||
private _codeEditors: { [editorId: string]: ICommonCodeEditor; };
|
||||
|
||||
private _onDiffEditorAdd: Emitter<ICommonDiffEditor>;
|
||||
private _onDiffEditorRemove: Emitter<ICommonDiffEditor>;
|
||||
private _diffEditors: { [editorId: string]: ICommonDiffEditor; };
|
||||
|
||||
constructor() {
|
||||
this._codeEditors = Object.create(null);
|
||||
this._diffEditors = Object.create(null);
|
||||
this._onCodeEditorAdd = new Emitter<ICommonCodeEditor>();
|
||||
this._onCodeEditorRemove = new Emitter<ICommonCodeEditor>();
|
||||
this._onDiffEditorAdd = new Emitter<ICommonDiffEditor>();
|
||||
this._onDiffEditorRemove = new Emitter<ICommonDiffEditor>();
|
||||
}
|
||||
|
||||
addCodeEditor(editor: ICommonCodeEditor): void {
|
||||
this._codeEditors[editor.getId()] = editor;
|
||||
this._onCodeEditorAdd.fire(editor);
|
||||
}
|
||||
|
||||
get onCodeEditorAdd(): Event<ICommonCodeEditor> {
|
||||
return this._onCodeEditorAdd.event;
|
||||
}
|
||||
|
||||
removeCodeEditor(editor: ICommonCodeEditor): void {
|
||||
if (delete this._codeEditors[editor.getId()]) {
|
||||
this._onCodeEditorRemove.fire(editor);
|
||||
}
|
||||
}
|
||||
|
||||
get onCodeEditorRemove(): Event<ICommonCodeEditor> {
|
||||
return this._onCodeEditorRemove.event;
|
||||
}
|
||||
|
||||
getCodeEditor(editorId: string): ICommonCodeEditor {
|
||||
return this._codeEditors[editorId] || null;
|
||||
}
|
||||
|
||||
listCodeEditors(): ICommonCodeEditor[] {
|
||||
return Object.keys(this._codeEditors).map(id => this._codeEditors[id]);
|
||||
}
|
||||
|
||||
addDiffEditor(editor: ICommonDiffEditor): void {
|
||||
this._diffEditors[editor.getId()] = editor;
|
||||
this._onDiffEditorAdd.fire(editor);
|
||||
}
|
||||
|
||||
get onDiffEditorAdd(): Event<ICommonDiffEditor> {
|
||||
return this._onDiffEditorAdd.event;
|
||||
}
|
||||
|
||||
removeDiffEditor(editor: ICommonDiffEditor): void {
|
||||
if (delete this._diffEditors[editor.getId()]) {
|
||||
this._onDiffEditorRemove.fire(editor);
|
||||
}
|
||||
}
|
||||
|
||||
get onDiffEditorRemove(): Event<ICommonDiffEditor> {
|
||||
return this._onDiffEditorRemove.event;
|
||||
}
|
||||
|
||||
getDiffEditor(editorId: string): ICommonDiffEditor {
|
||||
return this._diffEditors[editorId] || null;
|
||||
}
|
||||
|
||||
listDiffEditors(): ICommonDiffEditor[] {
|
||||
return Object.keys(this._diffEditors).map(id => this._diffEditors[id]);
|
||||
}
|
||||
|
||||
getFocusedCodeEditor(): ICommonCodeEditor {
|
||||
let editorWithWidgetFocus: ICommonCodeEditor = null;
|
||||
|
||||
let editors = this.listCodeEditors();
|
||||
for (let i = 0; i < editors.length; i++) {
|
||||
let editor = editors[i];
|
||||
|
||||
if (editor.isFocused()) {
|
||||
// bingo!
|
||||
return editor;
|
||||
}
|
||||
|
||||
if (editor.hasWidgetFocus()) {
|
||||
editorWithWidgetFocus = editor;
|
||||
}
|
||||
}
|
||||
|
||||
return editorWithWidgetFocus;
|
||||
}
|
||||
|
||||
abstract registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void;
|
||||
abstract removeDecorationType(key: string): void;
|
||||
abstract resolveDecorationOptions(decorationTypeKey: string, writable: boolean): IModelDecorationOptions;
|
||||
|
||||
private _transientWatchers: { [uri: string]: ModelTransientSettingWatcher; } = {};
|
||||
|
||||
public setTransientModelProperty(model: IModel, key: string, value: any): void {
|
||||
const uri = model.uri.toString();
|
||||
|
||||
let w: ModelTransientSettingWatcher;
|
||||
if (this._transientWatchers.hasOwnProperty(uri)) {
|
||||
w = this._transientWatchers[uri];
|
||||
} else {
|
||||
w = new ModelTransientSettingWatcher(uri, model, this);
|
||||
this._transientWatchers[uri] = w;
|
||||
}
|
||||
|
||||
w.set(key, value);
|
||||
}
|
||||
|
||||
public getTransientModelProperty(model: IModel, key: string): any {
|
||||
const uri = model.uri.toString();
|
||||
|
||||
if (!this._transientWatchers.hasOwnProperty(uri)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this._transientWatchers[uri].get(key);
|
||||
}
|
||||
|
||||
_removeWatcher(w: ModelTransientSettingWatcher): void {
|
||||
delete this._transientWatchers[w.uri];
|
||||
}
|
||||
}
|
||||
|
||||
export class ModelTransientSettingWatcher {
|
||||
public readonly uri: string;
|
||||
private readonly _values: { [key: string]: any; };
|
||||
|
||||
constructor(uri: string, model: IModel, owner: AbstractCodeEditorService) {
|
||||
this.uri = uri;
|
||||
this._values = {};
|
||||
model.onWillDispose(() => owner._removeWatcher(this));
|
||||
}
|
||||
|
||||
public set(key: string, value: any): void {
|
||||
this._values[key] = value;
|
||||
}
|
||||
|
||||
public get(key: string): any {
|
||||
return this._values[key];
|
||||
}
|
||||
}
|
||||
391
src/vs/editor/common/services/bulkEdit.ts
Normal file
391
src/vs/editor/common/services/bulkEdit.ts
Normal file
@@ -0,0 +1,391 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
import { IStringDictionary, forEach, values, groupBy, size } from 'vs/base/common/collections';
|
||||
import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService';
|
||||
import { IFileService, IFileChange } from 'vs/platform/files/common/files';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { Selection, ISelection } from 'vs/editor/common/core/selection';
|
||||
import { IIdentifiedSingleEditOperation, IModel, EndOfLineSequence, ICommonCodeEditor } from 'vs/editor/common/editorCommon';
|
||||
import { IProgressRunner } from 'vs/platform/progress/common/progress';
|
||||
|
||||
export interface IResourceEdit {
|
||||
resource: URI;
|
||||
range?: IRange;
|
||||
newText: string;
|
||||
newEol?: EndOfLineSequence;
|
||||
}
|
||||
|
||||
interface IRecording {
|
||||
stop(): void;
|
||||
hasChanged(resource: URI): boolean;
|
||||
allChanges(): IFileChange[];
|
||||
}
|
||||
|
||||
class ChangeRecorder {
|
||||
|
||||
private _fileService: IFileService;
|
||||
|
||||
constructor(fileService?: IFileService) {
|
||||
this._fileService = fileService;
|
||||
}
|
||||
|
||||
public start(): IRecording {
|
||||
|
||||
const changes: IStringDictionary<IFileChange[]> = Object.create(null);
|
||||
|
||||
let stop: IDisposable;
|
||||
if (this._fileService) {
|
||||
stop = this._fileService.onFileChanges((event) => {
|
||||
event.changes.forEach(change => {
|
||||
|
||||
const key = String(change.resource);
|
||||
let array = changes[key];
|
||||
|
||||
if (!array) {
|
||||
changes[key] = array = [];
|
||||
}
|
||||
|
||||
array.push(change);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
stop: () => { return stop && stop.dispose(); },
|
||||
hasChanged: (resource: URI) => !!changes[resource.toString()],
|
||||
allChanges: () => flatten(values(changes))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class EditTask implements IDisposable {
|
||||
|
||||
private _initialSelections: Selection[];
|
||||
private _endCursorSelection: Selection;
|
||||
private get _model(): IModel { return this._modelReference.object.textEditorModel; }
|
||||
private _modelReference: IReference<ITextEditorModel>;
|
||||
private _edits: IIdentifiedSingleEditOperation[];
|
||||
private _newEol: EndOfLineSequence;
|
||||
|
||||
constructor(modelReference: IReference<ITextEditorModel>) {
|
||||
this._endCursorSelection = null;
|
||||
this._modelReference = modelReference;
|
||||
this._edits = [];
|
||||
}
|
||||
|
||||
public addEdit(edit: IResourceEdit): void {
|
||||
|
||||
if (typeof edit.newEol === 'number') {
|
||||
// honor eol-change
|
||||
this._newEol = edit.newEol;
|
||||
}
|
||||
|
||||
if (edit.range || edit.newText) {
|
||||
// create edit operation
|
||||
let range: Range;
|
||||
if (!edit.range) {
|
||||
range = this._model.getFullModelRange();
|
||||
} else {
|
||||
range = Range.lift(edit.range);
|
||||
}
|
||||
this._edits.push(EditOperation.replaceMove(range, edit.newText));
|
||||
}
|
||||
}
|
||||
|
||||
public apply(): void {
|
||||
if (this._edits.length > 0) {
|
||||
|
||||
this._edits = this._edits.map((value, index) => ({ value, index })).sort((a, b) => {
|
||||
let ret = Range.compareRangesUsingStarts(a.value.range, b.value.range);
|
||||
if (ret === 0) {
|
||||
ret = a.index - b.index;
|
||||
}
|
||||
return ret;
|
||||
}).map(element => element.value);
|
||||
|
||||
this._initialSelections = this._getInitialSelections();
|
||||
this._model.pushStackElement();
|
||||
this._model.pushEditOperations(this._initialSelections, this._edits, (edits) => this._getEndCursorSelections(edits));
|
||||
this._model.pushStackElement();
|
||||
}
|
||||
if (this._newEol !== undefined) {
|
||||
this._model.pushStackElement();
|
||||
this._model.setEOL(this._newEol);
|
||||
this._model.pushStackElement();
|
||||
}
|
||||
}
|
||||
|
||||
protected _getInitialSelections(): Selection[] {
|
||||
const firstRange = this._edits[0].range;
|
||||
const initialSelection = new Selection(
|
||||
firstRange.startLineNumber,
|
||||
firstRange.startColumn,
|
||||
firstRange.endLineNumber,
|
||||
firstRange.endColumn
|
||||
);
|
||||
return [initialSelection];
|
||||
}
|
||||
|
||||
private _getEndCursorSelections(inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] {
|
||||
let relevantEditIndex = 0;
|
||||
for (let i = 0; i < inverseEditOperations.length; i++) {
|
||||
const editRange = inverseEditOperations[i].range;
|
||||
for (let j = 0; j < this._initialSelections.length; j++) {
|
||||
const selectionRange = this._initialSelections[j];
|
||||
if (Range.areIntersectingOrTouching(editRange, selectionRange)) {
|
||||
relevantEditIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const srcRange = inverseEditOperations[relevantEditIndex].range;
|
||||
this._endCursorSelection = new Selection(
|
||||
srcRange.endLineNumber,
|
||||
srcRange.endColumn,
|
||||
srcRange.endLineNumber,
|
||||
srcRange.endColumn
|
||||
);
|
||||
return [this._endCursorSelection];
|
||||
}
|
||||
|
||||
public getEndCursorSelection(): Selection {
|
||||
return this._endCursorSelection;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._model) {
|
||||
this._modelReference.dispose();
|
||||
this._modelReference = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SourceModelEditTask extends EditTask {
|
||||
|
||||
private _knownInitialSelections: Selection[];
|
||||
|
||||
constructor(modelReference: IReference<ITextEditorModel>, initialSelections: Selection[]) {
|
||||
super(modelReference);
|
||||
this._knownInitialSelections = initialSelections;
|
||||
}
|
||||
|
||||
protected _getInitialSelections(): Selection[] {
|
||||
return this._knownInitialSelections;
|
||||
}
|
||||
}
|
||||
|
||||
class BulkEditModel implements IDisposable {
|
||||
|
||||
private _textModelResolverService: ITextModelService;
|
||||
private _numberOfResourcesToModify: number = 0;
|
||||
private _numberOfChanges: number = 0;
|
||||
private _edits: IStringDictionary<IResourceEdit[]> = Object.create(null);
|
||||
private _tasks: EditTask[];
|
||||
private _sourceModel: URI;
|
||||
private _sourceSelections: Selection[];
|
||||
private _sourceModelTask: SourceModelEditTask;
|
||||
|
||||
constructor(textModelResolverService: ITextModelService, sourceModel: URI, sourceSelections: Selection[], edits: IResourceEdit[], private progress: IProgressRunner = null) {
|
||||
this._textModelResolverService = textModelResolverService;
|
||||
this._sourceModel = sourceModel;
|
||||
this._sourceSelections = sourceSelections;
|
||||
this._sourceModelTask = null;
|
||||
|
||||
for (let edit of edits) {
|
||||
this._addEdit(edit);
|
||||
}
|
||||
}
|
||||
|
||||
public resourcesCount(): number {
|
||||
return this._numberOfResourcesToModify;
|
||||
}
|
||||
|
||||
public changeCount(): number {
|
||||
return this._numberOfChanges;
|
||||
}
|
||||
|
||||
private _addEdit(edit: IResourceEdit): void {
|
||||
let array = this._edits[edit.resource.toString()];
|
||||
if (!array) {
|
||||
this._edits[edit.resource.toString()] = array = [];
|
||||
this._numberOfResourcesToModify += 1;
|
||||
}
|
||||
this._numberOfChanges += 1;
|
||||
array.push(edit);
|
||||
}
|
||||
|
||||
public prepare(): TPromise<BulkEditModel> {
|
||||
|
||||
if (this._tasks) {
|
||||
throw new Error('illegal state - already prepared');
|
||||
}
|
||||
|
||||
this._tasks = [];
|
||||
const promises: TPromise<any>[] = [];
|
||||
|
||||
if (this.progress) {
|
||||
this.progress.total(this._numberOfResourcesToModify * 2);
|
||||
}
|
||||
|
||||
forEach(this._edits, entry => {
|
||||
const promise = this._textModelResolverService.createModelReference(URI.parse(entry.key)).then(ref => {
|
||||
const model = ref.object;
|
||||
|
||||
if (!model || !model.textEditorModel) {
|
||||
throw new Error(`Cannot load file ${entry.key}`);
|
||||
}
|
||||
|
||||
const textEditorModel = model.textEditorModel;
|
||||
let task: EditTask;
|
||||
|
||||
if (this._sourceModel && textEditorModel.uri.toString() === this._sourceModel.toString()) {
|
||||
this._sourceModelTask = new SourceModelEditTask(ref, this._sourceSelections);
|
||||
task = this._sourceModelTask;
|
||||
} else {
|
||||
task = new EditTask(ref);
|
||||
}
|
||||
|
||||
entry.value.forEach(edit => task.addEdit(edit));
|
||||
this._tasks.push(task);
|
||||
if (this.progress) {
|
||||
this.progress.worked(1);
|
||||
}
|
||||
});
|
||||
promises.push(promise);
|
||||
});
|
||||
|
||||
|
||||
return TPromise.join(promises).then(_ => this);
|
||||
}
|
||||
|
||||
public apply(): Selection {
|
||||
this._tasks.forEach(task => this.applyTask(task));
|
||||
let r: Selection = null;
|
||||
if (this._sourceModelTask) {
|
||||
r = this._sourceModelTask.getEndCursorSelection();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private applyTask(task: EditTask): void {
|
||||
task.apply();
|
||||
if (this.progress) {
|
||||
this.progress.worked(1);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._tasks = dispose(this._tasks);
|
||||
}
|
||||
}
|
||||
|
||||
export interface BulkEdit {
|
||||
progress(progress: IProgressRunner): void;
|
||||
add(edit: IResourceEdit[]): void;
|
||||
finish(): TPromise<ISelection>;
|
||||
ariaMessage(): string;
|
||||
}
|
||||
|
||||
export function bulkEdit(textModelResolverService: ITextModelService, editor: ICommonCodeEditor, edits: IResourceEdit[], fileService?: IFileService, progress: IProgressRunner = null): TPromise<any> {
|
||||
let bulk = createBulkEdit(textModelResolverService, editor, fileService);
|
||||
bulk.add(edits);
|
||||
bulk.progress(progress);
|
||||
return bulk.finish();
|
||||
}
|
||||
|
||||
export function createBulkEdit(textModelResolverService: ITextModelService, editor?: ICommonCodeEditor, fileService?: IFileService): BulkEdit {
|
||||
|
||||
let all: IResourceEdit[] = [];
|
||||
let recording = new ChangeRecorder(fileService).start();
|
||||
let progressRunner: IProgressRunner;
|
||||
|
||||
function progress(progress: IProgressRunner) {
|
||||
progressRunner = progress;
|
||||
}
|
||||
|
||||
function add(edits: IResourceEdit[]): void {
|
||||
all.push(...edits);
|
||||
}
|
||||
|
||||
function getConcurrentEdits() {
|
||||
let names: string[];
|
||||
for (let edit of all) {
|
||||
if (recording.hasChanged(edit.resource)) {
|
||||
if (!names) {
|
||||
names = [];
|
||||
}
|
||||
names.push(edit.resource.fsPath);
|
||||
}
|
||||
}
|
||||
if (names) {
|
||||
return nls.localize('conflict', "These files have changed in the meantime: {0}", names.join(', '));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function finish(): TPromise<ISelection> {
|
||||
|
||||
if (all.length === 0) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
let concurrentEdits = getConcurrentEdits();
|
||||
if (concurrentEdits) {
|
||||
return TPromise.wrapError<ISelection>(new Error(concurrentEdits));
|
||||
}
|
||||
|
||||
let uri: URI;
|
||||
let selections: Selection[];
|
||||
|
||||
if (editor && editor.getModel()) {
|
||||
uri = editor.getModel().uri;
|
||||
selections = editor.getSelections();
|
||||
}
|
||||
|
||||
const model = new BulkEditModel(textModelResolverService, uri, selections, all, progressRunner);
|
||||
|
||||
return model.prepare().then(_ => {
|
||||
|
||||
let concurrentEdits = getConcurrentEdits();
|
||||
if (concurrentEdits) {
|
||||
throw new Error(concurrentEdits);
|
||||
}
|
||||
|
||||
recording.stop();
|
||||
|
||||
const result = model.apply();
|
||||
model.dispose();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function ariaMessage(): string {
|
||||
let editCount = all.length;
|
||||
let resourceCount = size(groupBy(all, edit => edit.resource.toString()));
|
||||
if (editCount === 0) {
|
||||
return nls.localize('summary.0', "Made no edits");
|
||||
} else if (editCount > 1 && resourceCount > 1) {
|
||||
return nls.localize('summary.nm', "Made {0} text edits in {1} files", editCount, resourceCount);
|
||||
} else {
|
||||
return nls.localize('summary.n0', "Made {0} text edits in one file", editCount, resourceCount);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
progress,
|
||||
add,
|
||||
finish,
|
||||
ariaMessage
|
||||
};
|
||||
}
|
||||
80
src/vs/editor/common/services/codeEditorService.ts
Normal file
80
src/vs/editor/common/services/codeEditorService.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event from 'vs/base/common/event';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ICommonCodeEditor, ICommonDiffEditor, isCommonCodeEditor, isCommonDiffEditor, IDecorationRenderOptions, IModelDecorationOptions, IModel } from 'vs/editor/common/editorCommon';
|
||||
import { IEditor } from 'vs/platform/editor/common/editor';
|
||||
|
||||
export var ICodeEditorService = createDecorator<ICodeEditorService>('codeEditorService');
|
||||
|
||||
export interface ICodeEditorService {
|
||||
_serviceBrand: any;
|
||||
|
||||
onCodeEditorAdd: Event<ICommonCodeEditor>;
|
||||
onCodeEditorRemove: Event<ICommonCodeEditor>;
|
||||
|
||||
onDiffEditorAdd: Event<ICommonDiffEditor>;
|
||||
onDiffEditorRemove: Event<ICommonDiffEditor>;
|
||||
|
||||
addCodeEditor(editor: ICommonCodeEditor): void;
|
||||
removeCodeEditor(editor: ICommonCodeEditor): void;
|
||||
getCodeEditor(editorId: string): ICommonCodeEditor;
|
||||
listCodeEditors(): ICommonCodeEditor[];
|
||||
|
||||
addDiffEditor(editor: ICommonDiffEditor): void;
|
||||
removeDiffEditor(editor: ICommonDiffEditor): void;
|
||||
getDiffEditor(editorId: string): ICommonDiffEditor;
|
||||
listDiffEditors(): ICommonDiffEditor[];
|
||||
|
||||
/**
|
||||
* Returns the current focused code editor (if the focus is in the editor or in an editor widget) or null.
|
||||
*/
|
||||
getFocusedCodeEditor(): ICommonCodeEditor;
|
||||
|
||||
registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void;
|
||||
removeDecorationType(key: string): void;
|
||||
resolveDecorationOptions(typeKey: string, writable: boolean): IModelDecorationOptions;
|
||||
|
||||
setTransientModelProperty(model: IModel, key: string, value: any): void;
|
||||
getTransientModelProperty(model: IModel, key: string): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses `editor.getControl()` and returns either a `codeEditor` or a `diffEditor` or nothing.
|
||||
*/
|
||||
export function getCodeOrDiffEditor(editor: IEditor): { codeEditor: ICommonCodeEditor; diffEditor: ICommonDiffEditor } {
|
||||
if (editor) {
|
||||
let control = editor.getControl();
|
||||
if (control) {
|
||||
if (isCommonCodeEditor(control)) {
|
||||
return {
|
||||
codeEditor: control,
|
||||
diffEditor: null
|
||||
};
|
||||
}
|
||||
if (isCommonDiffEditor(control)) {
|
||||
return {
|
||||
codeEditor: null,
|
||||
diffEditor: control
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
codeEditor: null,
|
||||
diffEditor: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses `editor.getControl()` and returns either the code editor, or the modified editor of a diff editor or nothing.
|
||||
*/
|
||||
export function getCodeEditor(editor: IEditor): ICommonCodeEditor {
|
||||
let r = getCodeOrDiffEditor(editor);
|
||||
return r.codeEditor || (r.diffEditor && r.diffEditor.getModifiedEditor()) || null;
|
||||
}
|
||||
575
src/vs/editor/common/services/editorSimpleWorker.ts
Normal file
575
src/vs/editor/common/services/editorSimpleWorker.ts
Normal file
@@ -0,0 +1,575 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IRequestHandler } from 'vs/base/common/worker/simpleWorker';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { DiffComputer } from 'vs/editor/common/diff/diffComputer';
|
||||
import { stringDiff } from 'vs/base/common/diff/diff';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { MirrorModel as BaseMirrorModel, IModelChangedEvent } from 'vs/editor/common/model/mirrorModel';
|
||||
import { IInplaceReplaceSupportResult, ILink, ISuggestResult, ISuggestion, TextEdit } from 'vs/editor/common/modes';
|
||||
import { computeLinks } from 'vs/editor/common/modes/linkComputer';
|
||||
import { BasicInplaceReplace } from 'vs/editor/common/modes/supports/inplaceReplaceSupport';
|
||||
import { getWordAtText, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper';
|
||||
import { createMonacoBaseAPI } from 'vs/editor/common/standalone/standaloneBase';
|
||||
|
||||
export interface IMirrorModel {
|
||||
readonly uri: URI;
|
||||
readonly version: number;
|
||||
getValue(): string;
|
||||
}
|
||||
|
||||
export interface IWorkerContext {
|
||||
/**
|
||||
* Get all available mirror models in this worker.
|
||||
*/
|
||||
getMirrorModels(): IMirrorModel[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface IRawModelData {
|
||||
url: string;
|
||||
versionId: number;
|
||||
lines: string[];
|
||||
EOL: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface ICommonModel {
|
||||
uri: URI;
|
||||
version: number;
|
||||
eol: string;
|
||||
getValue(): string;
|
||||
|
||||
getLinesContent(): string[];
|
||||
getLineCount(): number;
|
||||
getLineContent(lineNumber: number): string;
|
||||
getWordUntilPosition(position: IPosition, wordDefinition: RegExp): editorCommon.IWordAtPosition;
|
||||
getAllUniqueWords(wordDefinition: RegExp, skipWordOnce?: string): string[];
|
||||
getValueInRange(range: IRange): string;
|
||||
getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range;
|
||||
offsetAt(position: IPosition): number;
|
||||
positionAt(offset: number): IPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Range of a word inside a model.
|
||||
* @internal
|
||||
*/
|
||||
interface IWordRange {
|
||||
/**
|
||||
* The index where the word starts.
|
||||
*/
|
||||
readonly start: number;
|
||||
/**
|
||||
* The index where the word ends.
|
||||
*/
|
||||
readonly end: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class MirrorModel extends BaseMirrorModel implements ICommonModel {
|
||||
|
||||
public get uri(): URI {
|
||||
return this._uri;
|
||||
}
|
||||
|
||||
public get version(): number {
|
||||
return this._versionId;
|
||||
}
|
||||
|
||||
public get eol(): string {
|
||||
return this._eol;
|
||||
}
|
||||
|
||||
public getValue(): string {
|
||||
return this.getText();
|
||||
}
|
||||
|
||||
public getLinesContent(): string[] {
|
||||
return this._lines.slice(0);
|
||||
}
|
||||
|
||||
public getLineCount(): number {
|
||||
return this._lines.length;
|
||||
}
|
||||
|
||||
public getLineContent(lineNumber: number): string {
|
||||
return this._lines[lineNumber - 1];
|
||||
}
|
||||
|
||||
public getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range {
|
||||
|
||||
let wordAtText = getWordAtText(
|
||||
position.column,
|
||||
ensureValidWordDefinition(wordDefinition),
|
||||
this._lines[position.lineNumber - 1],
|
||||
0
|
||||
);
|
||||
|
||||
if (wordAtText) {
|
||||
return new Range(position.lineNumber, wordAtText.startColumn, position.lineNumber, wordAtText.endColumn);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public getWordUntilPosition(position: IPosition, wordDefinition: RegExp): editorCommon.IWordAtPosition {
|
||||
var wordAtPosition = this.getWordAtPosition(position, wordDefinition);
|
||||
if (!wordAtPosition) {
|
||||
return {
|
||||
word: '',
|
||||
startColumn: position.column,
|
||||
endColumn: position.column
|
||||
};
|
||||
}
|
||||
return {
|
||||
word: this._lines[position.lineNumber - 1].substring(wordAtPosition.startColumn - 1, position.column - 1),
|
||||
startColumn: wordAtPosition.startColumn,
|
||||
endColumn: position.column
|
||||
};
|
||||
}
|
||||
|
||||
private _getAllWords(wordDefinition: RegExp): string[] {
|
||||
var result: string[] = [];
|
||||
this._lines.forEach((line) => {
|
||||
this._wordenize(line, wordDefinition).forEach((info) => {
|
||||
result.push(line.substring(info.start, info.end));
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public getAllUniqueWords(wordDefinition: RegExp, skipWordOnce?: string): string[] {
|
||||
var foundSkipWord = false;
|
||||
var uniqueWords = Object.create(null);
|
||||
return this._getAllWords(wordDefinition).filter((word) => {
|
||||
if (skipWordOnce && !foundSkipWord && skipWordOnce === word) {
|
||||
foundSkipWord = true;
|
||||
return false;
|
||||
} else if (uniqueWords[word]) {
|
||||
return false;
|
||||
} else {
|
||||
uniqueWords[word] = true;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _wordenize(content: string, wordDefinition: RegExp): IWordRange[] {
|
||||
const result: IWordRange[] = [];
|
||||
let match: RegExpExecArray;
|
||||
|
||||
wordDefinition.lastIndex = 0; // reset lastIndex just to be sure
|
||||
|
||||
while (match = wordDefinition.exec(content)) {
|
||||
if (match[0].length === 0) {
|
||||
// it did match the empty string
|
||||
break;
|
||||
}
|
||||
result.push({ start: match.index, end: match.index + match[0].length });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getValueInRange(range: IRange): string {
|
||||
range = this._validateRange(range);
|
||||
|
||||
if (range.startLineNumber === range.endLineNumber) {
|
||||
return this._lines[range.startLineNumber - 1].substring(range.startColumn - 1, range.endColumn - 1);
|
||||
}
|
||||
|
||||
var lineEnding = this._eol,
|
||||
startLineIndex = range.startLineNumber - 1,
|
||||
endLineIndex = range.endLineNumber - 1,
|
||||
resultLines: string[] = [];
|
||||
|
||||
resultLines.push(this._lines[startLineIndex].substring(range.startColumn - 1));
|
||||
for (var i = startLineIndex + 1; i < endLineIndex; i++) {
|
||||
resultLines.push(this._lines[i]);
|
||||
}
|
||||
resultLines.push(this._lines[endLineIndex].substring(0, range.endColumn - 1));
|
||||
|
||||
return resultLines.join(lineEnding);
|
||||
}
|
||||
|
||||
public offsetAt(position: IPosition): number {
|
||||
position = this._validatePosition(position);
|
||||
this._ensureLineStarts();
|
||||
return this._lineStarts.getAccumulatedValue(position.lineNumber - 2) + (position.column - 1);
|
||||
}
|
||||
|
||||
public positionAt(offset: number): IPosition {
|
||||
offset = Math.floor(offset);
|
||||
offset = Math.max(0, offset);
|
||||
|
||||
this._ensureLineStarts();
|
||||
let out = this._lineStarts.getIndexOf(offset);
|
||||
let lineLength = this._lines[out.index].length;
|
||||
|
||||
// Ensure we return a valid position
|
||||
return {
|
||||
lineNumber: 1 + out.index,
|
||||
column: 1 + Math.min(out.remainder, lineLength)
|
||||
};
|
||||
}
|
||||
|
||||
private _validateRange(range: IRange): IRange {
|
||||
|
||||
const start = this._validatePosition({ lineNumber: range.startLineNumber, column: range.startColumn });
|
||||
const end = this._validatePosition({ lineNumber: range.endLineNumber, column: range.endColumn });
|
||||
|
||||
if (start.lineNumber !== range.startLineNumber
|
||||
|| start.column !== range.startColumn
|
||||
|| end.lineNumber !== range.endLineNumber
|
||||
|| end.column !== range.endColumn) {
|
||||
|
||||
return {
|
||||
startLineNumber: start.lineNumber,
|
||||
startColumn: start.column,
|
||||
endLineNumber: end.lineNumber,
|
||||
endColumn: end.column
|
||||
};
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
private _validatePosition(position: IPosition): IPosition {
|
||||
if (!Position.isIPosition(position)) {
|
||||
throw new Error('bad position');
|
||||
}
|
||||
let { lineNumber, column } = position;
|
||||
let hasChanged = false;
|
||||
|
||||
if (lineNumber < 1) {
|
||||
lineNumber = 1;
|
||||
column = 1;
|
||||
hasChanged = true;
|
||||
|
||||
} else if (lineNumber > this._lines.length) {
|
||||
lineNumber = this._lines.length;
|
||||
column = this._lines[lineNumber - 1].length + 1;
|
||||
hasChanged = true;
|
||||
|
||||
} else {
|
||||
let maxCharacter = this._lines[lineNumber - 1].length + 1;
|
||||
if (column < 1) {
|
||||
column = 1;
|
||||
hasChanged = true;
|
||||
}
|
||||
else if (column > maxCharacter) {
|
||||
column = maxCharacter;
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasChanged) {
|
||||
return position;
|
||||
} else {
|
||||
return { lineNumber, column };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export abstract class BaseEditorSimpleWorker {
|
||||
private _foreignModule: any;
|
||||
|
||||
constructor() {
|
||||
this._foreignModule = null;
|
||||
}
|
||||
|
||||
protected abstract _getModel(uri: string): ICommonModel;
|
||||
protected abstract _getModels(): ICommonModel[];
|
||||
|
||||
// ---- BEGIN diff --------------------------------------------------------------------------
|
||||
|
||||
public computeDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): TPromise<editorCommon.ILineChange[]> {
|
||||
let original = this._getModel(originalUrl);
|
||||
let modified = this._getModel(modifiedUrl);
|
||||
if (!original || !modified) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let originalLines = original.getLinesContent();
|
||||
let modifiedLines = modified.getLinesContent();
|
||||
let diffComputer = new DiffComputer(originalLines, modifiedLines, {
|
||||
shouldPostProcessCharChanges: true,
|
||||
shouldIgnoreTrimWhitespace: ignoreTrimWhitespace,
|
||||
shouldConsiderTrimWhitespaceInEmptyCase: true,
|
||||
shouldMakePrettyDiff: true
|
||||
});
|
||||
return TPromise.as(diffComputer.computeDiff());
|
||||
}
|
||||
|
||||
public computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): TPromise<editorCommon.IChange[]> {
|
||||
let original = this._getModel(originalUrl);
|
||||
let modified = this._getModel(modifiedUrl);
|
||||
if (!original || !modified) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let originalLines = original.getLinesContent();
|
||||
let modifiedLines = modified.getLinesContent();
|
||||
let diffComputer = new DiffComputer(originalLines, modifiedLines, {
|
||||
shouldPostProcessCharChanges: false,
|
||||
shouldIgnoreTrimWhitespace: ignoreTrimWhitespace,
|
||||
shouldConsiderTrimWhitespaceInEmptyCase: false,
|
||||
shouldMakePrettyDiff: true
|
||||
});
|
||||
return TPromise.as(diffComputer.computeDiff());
|
||||
}
|
||||
|
||||
// ---- END diff --------------------------------------------------------------------------
|
||||
|
||||
|
||||
// ---- BEGIN minimal edits ---------------------------------------------------------------
|
||||
|
||||
private static _diffLimit = 10000;
|
||||
|
||||
public computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], ranges: IRange[]): TPromise<TextEdit[]> {
|
||||
const model = this._getModel(modelUrl);
|
||||
if (!model) {
|
||||
return TPromise.as(edits);
|
||||
}
|
||||
|
||||
const result: TextEdit[] = [];
|
||||
let lastEol: editorCommon.EndOfLineSequence;
|
||||
|
||||
for (let { range, text, eol } of edits) {
|
||||
|
||||
if (typeof eol === 'number') {
|
||||
lastEol = eol;
|
||||
}
|
||||
|
||||
if (!range) {
|
||||
// eol-change only
|
||||
continue;
|
||||
}
|
||||
|
||||
const original = model.getValueInRange(range);
|
||||
text = text.replace(/\r\n|\n|\r/g, model.eol);
|
||||
|
||||
if (original === text) {
|
||||
// noop
|
||||
continue;
|
||||
}
|
||||
|
||||
// make sure diff won't take too long
|
||||
if (Math.max(text.length, original.length) > BaseEditorSimpleWorker._diffLimit) {
|
||||
result.push({ range, text });
|
||||
continue;
|
||||
}
|
||||
|
||||
// compute diff between original and edit.text
|
||||
const changes = stringDiff(original, text, false);
|
||||
const editOffset = model.offsetAt(Range.lift(range).getStartPosition());
|
||||
|
||||
for (const change of changes) {
|
||||
const start = model.positionAt(editOffset + change.originalStart);
|
||||
const end = model.positionAt(editOffset + change.originalStart + change.originalLength);
|
||||
const newEdit: TextEdit = {
|
||||
text: text.substr(change.modifiedStart, change.modifiedLength),
|
||||
range: { startLineNumber: start.lineNumber, startColumn: start.column, endLineNumber: end.lineNumber, endColumn: end.column }
|
||||
};
|
||||
|
||||
if (model.getValueInRange(newEdit.range) !== newEdit.text) {
|
||||
result.push(newEdit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof lastEol === 'number') {
|
||||
result.push({ eol: lastEol, text: undefined, range: undefined });
|
||||
}
|
||||
|
||||
return TPromise.as(result);
|
||||
}
|
||||
|
||||
// ---- END minimal edits ---------------------------------------------------------------
|
||||
|
||||
public computeLinks(modelUrl: string): TPromise<ILink[]> {
|
||||
let model = this._getModel(modelUrl);
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return TPromise.as(computeLinks(model));
|
||||
}
|
||||
|
||||
// ---- BEGIN suggest --------------------------------------------------------------------------
|
||||
|
||||
public textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): TPromise<ISuggestResult> {
|
||||
const model = this._getModel(modelUrl);
|
||||
if (model) {
|
||||
const suggestions: ISuggestion[] = [];
|
||||
const wordDefRegExp = new RegExp(wordDef, wordDefFlags);
|
||||
const currentWord = model.getWordUntilPosition(position, wordDefRegExp).word;
|
||||
|
||||
for (const word of model.getAllUniqueWords(wordDefRegExp)) {
|
||||
if (word !== currentWord && isNaN(Number(word))) {
|
||||
suggestions.push({
|
||||
type: 'text',
|
||||
label: word,
|
||||
insertText: word,
|
||||
noAutoAccept: true,
|
||||
overwriteBefore: currentWord.length
|
||||
});
|
||||
}
|
||||
}
|
||||
return TPromise.as({ suggestions });
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
// ---- END suggest --------------------------------------------------------------------------
|
||||
|
||||
public navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): TPromise<IInplaceReplaceSupportResult> {
|
||||
let model = this._getModel(modelUrl);
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let wordDefRegExp = new RegExp(wordDef, wordDefFlags);
|
||||
|
||||
if (range.startColumn === range.endColumn) {
|
||||
range = {
|
||||
startLineNumber: range.startLineNumber,
|
||||
startColumn: range.startColumn,
|
||||
endLineNumber: range.endLineNumber,
|
||||
endColumn: range.endColumn + 1
|
||||
};
|
||||
}
|
||||
|
||||
let selectionText = model.getValueInRange(range);
|
||||
|
||||
let wordRange = model.getWordAtPosition({ lineNumber: range.startLineNumber, column: range.startColumn }, wordDefRegExp);
|
||||
let word: string = null;
|
||||
if (wordRange !== null) {
|
||||
word = model.getValueInRange(wordRange);
|
||||
}
|
||||
|
||||
let result = BasicInplaceReplace.INSTANCE.navigateValueSet(range, selectionText, wordRange, word, up);
|
||||
return TPromise.as(result);
|
||||
}
|
||||
|
||||
// ---- BEGIN foreign module support --------------------------------------------------------------------------
|
||||
|
||||
public loadForeignModule(moduleId: string, createData: any): TPromise<string[]> {
|
||||
return new TPromise<any>((c, e) => {
|
||||
// Use the global require to be sure to get the global config
|
||||
(<any>self).require([moduleId], (foreignModule: { create: (ctx: IWorkerContext, createData: any) => any; }) => {
|
||||
let ctx: IWorkerContext = {
|
||||
getMirrorModels: (): IMirrorModel[] => {
|
||||
return this._getModels();
|
||||
}
|
||||
};
|
||||
this._foreignModule = foreignModule.create(ctx, createData);
|
||||
|
||||
let methods: string[] = [];
|
||||
for (let prop in this._foreignModule) {
|
||||
if (typeof this._foreignModule[prop] === 'function') {
|
||||
methods.push(prop);
|
||||
}
|
||||
}
|
||||
|
||||
c(methods);
|
||||
|
||||
}, e);
|
||||
});
|
||||
}
|
||||
|
||||
// foreign method request
|
||||
public fmr(method: string, args: any[]): TPromise<any> {
|
||||
if (!this._foreignModule || typeof this._foreignModule[method] !== 'function') {
|
||||
return TPromise.wrapError(new Error('Missing requestHandler or method: ' + method));
|
||||
}
|
||||
|
||||
try {
|
||||
return TPromise.as(this._foreignModule[method].apply(this._foreignModule, args));
|
||||
} catch (e) {
|
||||
return TPromise.wrapError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- END foreign module support --------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class EditorSimpleWorkerImpl extends BaseEditorSimpleWorker implements IRequestHandler, IDisposable {
|
||||
_requestHandlerTrait: any;
|
||||
|
||||
private _models: { [uri: string]: MirrorModel; };
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._models = Object.create(null);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._models = Object.create(null);
|
||||
}
|
||||
|
||||
protected _getModel(uri: string): ICommonModel {
|
||||
return this._models[uri];
|
||||
}
|
||||
|
||||
protected _getModels(): ICommonModel[] {
|
||||
let all: MirrorModel[] = [];
|
||||
Object.keys(this._models).forEach((key) => all.push(this._models[key]));
|
||||
return all;
|
||||
}
|
||||
|
||||
public acceptNewModel(data: IRawModelData): void {
|
||||
this._models[data.url] = new MirrorModel(URI.parse(data.url), data.lines, data.EOL, data.versionId);
|
||||
}
|
||||
|
||||
public acceptModelChanged(strURL: string, e: IModelChangedEvent): void {
|
||||
if (!this._models[strURL]) {
|
||||
return;
|
||||
}
|
||||
let model = this._models[strURL];
|
||||
model.onEvents(e);
|
||||
}
|
||||
|
||||
public acceptRemovedModel(strURL: string): void {
|
||||
if (!this._models[strURL]) {
|
||||
return;
|
||||
}
|
||||
delete this._models[strURL];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on the worker side
|
||||
* @internal
|
||||
*/
|
||||
export function create(): IRequestHandler {
|
||||
return new EditorSimpleWorkerImpl();
|
||||
}
|
||||
|
||||
var global: any = self;
|
||||
let isWebWorker = (typeof global.importScripts === 'function');
|
||||
if (isWebWorker) {
|
||||
global.monaco = createMonacoBaseAPI();
|
||||
}
|
||||
30
src/vs/editor/common/services/editorWorkerService.ts
Normal file
30
src/vs/editor/common/services/editorWorkerService.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IChange, ILineChange } from 'vs/editor/common/editorCommon';
|
||||
import { IInplaceReplaceSupportResult, TextEdit } from 'vs/editor/common/modes';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
|
||||
export var ID_EDITOR_WORKER_SERVICE = 'editorWorkerService';
|
||||
export var IEditorWorkerService = createDecorator<IEditorWorkerService>(ID_EDITOR_WORKER_SERVICE);
|
||||
|
||||
export interface IEditorWorkerService {
|
||||
_serviceBrand: any;
|
||||
|
||||
canComputeDiff(original: URI, modified: URI): boolean;
|
||||
computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): TPromise<ILineChange[]>;
|
||||
|
||||
canComputeDirtyDiff(original: URI, modified: URI): boolean;
|
||||
computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): TPromise<IChange[]>;
|
||||
|
||||
computeMoreMinimalEdits(resource: URI, edits: TextEdit[], ranges: IRange[]): TPromise<TextEdit[]>;
|
||||
|
||||
canNavigateValueSet(resource: URI): boolean;
|
||||
navigateValueSet(resource: URI, range: IRange, up: boolean): TPromise<IInplaceReplaceSupportResult>;
|
||||
}
|
||||
435
src/vs/editor/common/services/editorWorkerServiceImpl.ts
Normal file
435
src/vs/editor/common/services/editorWorkerServiceImpl.ts
Normal file
@@ -0,0 +1,435 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IntervalTimer, ShallowCancelThenPromise, wireCancellationToken } from 'vs/base/common/async';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { SimpleWorkerClient, logOnceWebWorkerWarning } from 'vs/base/common/worker/simpleWorker';
|
||||
import { DefaultWorkerFactory } from 'vs/base/worker/defaultWorkerFactory';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { EditorSimpleWorkerImpl } from 'vs/editor/common/services/editorSimpleWorker';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
|
||||
/**
|
||||
* Stop syncing a model to the worker if it was not needed for 1 min.
|
||||
*/
|
||||
const STOP_SYNC_MODEL_DELTA_TIME_MS = 60 * 1000;
|
||||
|
||||
/**
|
||||
* Stop the worker if it was not needed for 5 min.
|
||||
*/
|
||||
const STOP_WORKER_DELTA_TIME_MS = 5 * 60 * 1000;
|
||||
|
||||
function canSyncModel(modelService: IModelService, resource: URI): boolean {
|
||||
let model = modelService.getModel(resource);
|
||||
if (!model) {
|
||||
return false;
|
||||
}
|
||||
if (model.isTooLargeForTokenization()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export class EditorWorkerServiceImpl extends Disposable implements IEditorWorkerService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
private readonly _modelService: IModelService;
|
||||
private readonly _workerManager: WorkerManager;
|
||||
|
||||
constructor(
|
||||
@IModelService modelService: IModelService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IModeService modeService: IModeService
|
||||
) {
|
||||
super();
|
||||
this._modelService = modelService;
|
||||
this._workerManager = this._register(new WorkerManager(this._modelService));
|
||||
|
||||
// todo@joh make sure this happens only once
|
||||
this._register(modes.LinkProviderRegistry.register('*', <modes.LinkProvider>{
|
||||
provideLinks: (model, token) => {
|
||||
if (!canSyncModel(this._modelService, model.uri)) {
|
||||
return TPromise.as([]); // File too large
|
||||
}
|
||||
return wireCancellationToken(token, this._workerManager.withWorker().then(client => client.computeLinks(model.uri)));
|
||||
}
|
||||
}));
|
||||
this._register(modes.SuggestRegistry.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, modeService, this._modelService)));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public canComputeDiff(original: URI, modified: URI): boolean {
|
||||
return (canSyncModel(this._modelService, original) && canSyncModel(this._modelService, modified));
|
||||
}
|
||||
|
||||
public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): TPromise<editorCommon.ILineChange[]> {
|
||||
return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, ignoreTrimWhitespace));
|
||||
}
|
||||
|
||||
public canComputeDirtyDiff(original: URI, modified: URI): boolean {
|
||||
return (canSyncModel(this._modelService, original) && canSyncModel(this._modelService, modified));
|
||||
}
|
||||
|
||||
public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): TPromise<editorCommon.IChange[]> {
|
||||
return this._workerManager.withWorker().then(client => client.computeDirtyDiff(original, modified, ignoreTrimWhitespace));
|
||||
}
|
||||
|
||||
public computeMoreMinimalEdits(resource: URI, edits: modes.TextEdit[], ranges: IRange[]): TPromise<modes.TextEdit[]> {
|
||||
if (!Array.isArray(edits) || edits.length === 0) {
|
||||
return TPromise.as(edits);
|
||||
} else {
|
||||
if (!canSyncModel(this._modelService, resource)) {
|
||||
return TPromise.as(edits); // File too large
|
||||
}
|
||||
return this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits, ranges));
|
||||
}
|
||||
}
|
||||
|
||||
public canNavigateValueSet(resource: URI): boolean {
|
||||
return (canSyncModel(this._modelService, resource));
|
||||
}
|
||||
|
||||
public navigateValueSet(resource: URI, range: IRange, up: boolean): TPromise<modes.IInplaceReplaceSupportResult> {
|
||||
return this._workerManager.withWorker().then(client => client.navigateValueSet(resource, range, up));
|
||||
}
|
||||
}
|
||||
|
||||
class WordBasedCompletionItemProvider implements modes.ISuggestSupport {
|
||||
|
||||
private readonly _workerManager: WorkerManager;
|
||||
private readonly _configurationService: ITextResourceConfigurationService;
|
||||
private readonly _modeService: IModeService;
|
||||
private readonly _modelService: IModelService;
|
||||
|
||||
constructor(
|
||||
workerManager: WorkerManager,
|
||||
configurationService: ITextResourceConfigurationService,
|
||||
modeService: IModeService,
|
||||
modelService: IModelService
|
||||
) {
|
||||
this._workerManager = workerManager;
|
||||
this._configurationService = configurationService;
|
||||
this._modeService = modeService;
|
||||
this._modelService = modelService;
|
||||
}
|
||||
|
||||
provideCompletionItems(model: editorCommon.IModel, position: Position): TPromise<modes.ISuggestResult> {
|
||||
const { wordBasedSuggestions } = this._configurationService.getConfiguration<IEditorOptions>(model.uri, position, 'editor');
|
||||
if (!wordBasedSuggestions) {
|
||||
return undefined;
|
||||
}
|
||||
if (!canSyncModel(this._modelService, model.uri)) {
|
||||
return undefined; // File too large
|
||||
}
|
||||
return this._workerManager.withWorker().then(client => client.textualSuggest(model.uri, position));
|
||||
}
|
||||
}
|
||||
|
||||
class WorkerManager extends Disposable {
|
||||
|
||||
private _modelService: IModelService;
|
||||
private _editorWorkerClient: EditorWorkerClient;
|
||||
private _lastWorkerUsedTime: number;
|
||||
|
||||
constructor(modelService: IModelService) {
|
||||
super();
|
||||
this._modelService = modelService;
|
||||
this._editorWorkerClient = null;
|
||||
|
||||
let stopWorkerInterval = this._register(new IntervalTimer());
|
||||
stopWorkerInterval.cancelAndSet(() => this._checkStopIdleWorker(), Math.round(STOP_WORKER_DELTA_TIME_MS / 2));
|
||||
|
||||
this._register(this._modelService.onModelRemoved(_ => this._checkStopEmptyWorker()));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._editorWorkerClient) {
|
||||
this._editorWorkerClient.dispose();
|
||||
this._editorWorkerClient = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model service has no more models and stop the worker if that is the case.
|
||||
*/
|
||||
private _checkStopEmptyWorker(): void {
|
||||
if (!this._editorWorkerClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
let models = this._modelService.getModels();
|
||||
if (models.length === 0) {
|
||||
// There are no more models => nothing possible for me to do
|
||||
this._editorWorkerClient.dispose();
|
||||
this._editorWorkerClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the worker has been idle for a while and then stop it.
|
||||
*/
|
||||
private _checkStopIdleWorker(): void {
|
||||
if (!this._editorWorkerClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
let timeSinceLastWorkerUsedTime = (new Date()).getTime() - this._lastWorkerUsedTime;
|
||||
if (timeSinceLastWorkerUsedTime > STOP_WORKER_DELTA_TIME_MS) {
|
||||
this._editorWorkerClient.dispose();
|
||||
this._editorWorkerClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
public withWorker(): TPromise<EditorWorkerClient> {
|
||||
this._lastWorkerUsedTime = (new Date()).getTime();
|
||||
if (!this._editorWorkerClient) {
|
||||
this._editorWorkerClient = new EditorWorkerClient(this._modelService, 'editorWorkerService');
|
||||
}
|
||||
return TPromise.as(this._editorWorkerClient);
|
||||
}
|
||||
}
|
||||
|
||||
class EditorModelManager extends Disposable {
|
||||
|
||||
private _proxy: EditorSimpleWorkerImpl;
|
||||
private _modelService: IModelService;
|
||||
private _syncedModels: { [modelUrl: string]: IDisposable[]; } = Object.create(null);
|
||||
private _syncedModelsLastUsedTime: { [modelUrl: string]: number; } = Object.create(null);
|
||||
|
||||
constructor(proxy: EditorSimpleWorkerImpl, modelService: IModelService, keepIdleModels: boolean) {
|
||||
super();
|
||||
this._proxy = proxy;
|
||||
this._modelService = modelService;
|
||||
|
||||
if (!keepIdleModels) {
|
||||
let timer = new IntervalTimer();
|
||||
timer.cancelAndSet(() => this._checkStopModelSync(), Math.round(STOP_SYNC_MODEL_DELTA_TIME_MS / 2));
|
||||
this._register(timer);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
for (let modelUrl in this._syncedModels) {
|
||||
dispose(this._syncedModels[modelUrl]);
|
||||
}
|
||||
this._syncedModels = Object.create(null);
|
||||
this._syncedModelsLastUsedTime = Object.create(null);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public esureSyncedResources(resources: URI[]): void {
|
||||
for (let i = 0; i < resources.length; i++) {
|
||||
let resource = resources[i];
|
||||
let resourceStr = resource.toString();
|
||||
|
||||
if (!this._syncedModels[resourceStr]) {
|
||||
this._beginModelSync(resource);
|
||||
}
|
||||
if (this._syncedModels[resourceStr]) {
|
||||
this._syncedModelsLastUsedTime[resourceStr] = (new Date()).getTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _checkStopModelSync(): void {
|
||||
let currentTime = (new Date()).getTime();
|
||||
|
||||
let toRemove: string[] = [];
|
||||
for (let modelUrl in this._syncedModelsLastUsedTime) {
|
||||
let elapsedTime = currentTime - this._syncedModelsLastUsedTime[modelUrl];
|
||||
if (elapsedTime > STOP_SYNC_MODEL_DELTA_TIME_MS) {
|
||||
toRemove.push(modelUrl);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < toRemove.length; i++) {
|
||||
this._stopModelSync(toRemove[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private _beginModelSync(resource: URI): void {
|
||||
let model = this._modelService.getModel(resource);
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
if (model.isTooLargeForTokenization()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let modelUrl = resource.toString();
|
||||
|
||||
this._proxy.acceptNewModel({
|
||||
url: model.uri.toString(),
|
||||
lines: model.getLinesContent(),
|
||||
EOL: model.getEOL(),
|
||||
versionId: model.getVersionId()
|
||||
});
|
||||
|
||||
let toDispose: IDisposable[] = [];
|
||||
toDispose.push(model.onDidChangeContent((e) => {
|
||||
this._proxy.acceptModelChanged(modelUrl.toString(), e);
|
||||
}));
|
||||
toDispose.push(model.onWillDispose(() => {
|
||||
this._stopModelSync(modelUrl);
|
||||
}));
|
||||
toDispose.push({
|
||||
dispose: () => {
|
||||
this._proxy.acceptRemovedModel(modelUrl);
|
||||
}
|
||||
});
|
||||
|
||||
this._syncedModels[modelUrl] = toDispose;
|
||||
}
|
||||
|
||||
private _stopModelSync(modelUrl: string): void {
|
||||
let toDispose = this._syncedModels[modelUrl];
|
||||
delete this._syncedModels[modelUrl];
|
||||
delete this._syncedModelsLastUsedTime[modelUrl];
|
||||
dispose(toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
interface IWorkerClient<T> {
|
||||
getProxyObject(): TPromise<T>;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
class SynchronousWorkerClient<T extends IDisposable> implements IWorkerClient<T> {
|
||||
private _instance: T;
|
||||
private _proxyObj: TPromise<T>;
|
||||
|
||||
constructor(instance: T) {
|
||||
this._instance = instance;
|
||||
this._proxyObj = TPromise.as(this._instance);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._instance.dispose();
|
||||
this._instance = null;
|
||||
this._proxyObj = null;
|
||||
}
|
||||
|
||||
public getProxyObject(): TPromise<T> {
|
||||
return new ShallowCancelThenPromise(this._proxyObj);
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorWorkerClient extends Disposable {
|
||||
|
||||
private _modelService: IModelService;
|
||||
private _worker: IWorkerClient<EditorSimpleWorkerImpl>;
|
||||
private _workerFactory: DefaultWorkerFactory;
|
||||
private _modelManager: EditorModelManager;
|
||||
|
||||
constructor(modelService: IModelService, label: string) {
|
||||
super();
|
||||
this._modelService = modelService;
|
||||
this._workerFactory = new DefaultWorkerFactory(label);
|
||||
this._worker = null;
|
||||
this._modelManager = null;
|
||||
}
|
||||
|
||||
private _getOrCreateWorker(): IWorkerClient<EditorSimpleWorkerImpl> {
|
||||
if (!this._worker) {
|
||||
try {
|
||||
this._worker = this._register(new SimpleWorkerClient<EditorSimpleWorkerImpl>(
|
||||
this._workerFactory,
|
||||
'vs/editor/common/services/editorSimpleWorker'
|
||||
));
|
||||
} catch (err) {
|
||||
logOnceWebWorkerWarning(err);
|
||||
this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl());
|
||||
}
|
||||
}
|
||||
return this._worker;
|
||||
}
|
||||
|
||||
protected _getProxy(): TPromise<EditorSimpleWorkerImpl> {
|
||||
return new ShallowCancelThenPromise(this._getOrCreateWorker().getProxyObject().then(null, (err) => {
|
||||
logOnceWebWorkerWarning(err);
|
||||
this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl());
|
||||
return this._getOrCreateWorker().getProxyObject();
|
||||
}));
|
||||
}
|
||||
|
||||
private _getOrCreateModelManager(proxy: EditorSimpleWorkerImpl): EditorModelManager {
|
||||
if (!this._modelManager) {
|
||||
this._modelManager = this._register(new EditorModelManager(proxy, this._modelService, false));
|
||||
}
|
||||
return this._modelManager;
|
||||
}
|
||||
|
||||
protected _withSyncedResources(resources: URI[]): TPromise<EditorSimpleWorkerImpl> {
|
||||
return this._getProxy().then((proxy) => {
|
||||
this._getOrCreateModelManager(proxy).esureSyncedResources(resources);
|
||||
return proxy;
|
||||
});
|
||||
}
|
||||
|
||||
public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): TPromise<editorCommon.ILineChange[]> {
|
||||
return this._withSyncedResources([original, modified]).then(proxy => {
|
||||
return proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace);
|
||||
});
|
||||
}
|
||||
|
||||
public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): TPromise<editorCommon.IChange[]> {
|
||||
return this._withSyncedResources([original, modified]).then(proxy => {
|
||||
return proxy.computeDirtyDiff(original.toString(), modified.toString(), ignoreTrimWhitespace);
|
||||
});
|
||||
}
|
||||
|
||||
public computeMoreMinimalEdits(resource: URI, edits: modes.TextEdit[], ranges: IRange[]): TPromise<modes.TextEdit[]> {
|
||||
return this._withSyncedResources([resource]).then(proxy => {
|
||||
return proxy.computeMoreMinimalEdits(resource.toString(), edits, ranges);
|
||||
});
|
||||
}
|
||||
|
||||
public computeLinks(resource: URI): TPromise<modes.ILink[]> {
|
||||
return this._withSyncedResources([resource]).then(proxy => {
|
||||
return proxy.computeLinks(resource.toString());
|
||||
});
|
||||
}
|
||||
|
||||
public textualSuggest(resource: URI, position: IPosition): TPromise<modes.ISuggestResult> {
|
||||
return this._withSyncedResources([resource]).then(proxy => {
|
||||
let model = this._modelService.getModel(resource);
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
let wordDefRegExp = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageIdentifier().id);
|
||||
let wordDef = wordDefRegExp.source;
|
||||
let wordDefFlags = (wordDefRegExp.global ? 'g' : '') + (wordDefRegExp.ignoreCase ? 'i' : '') + (wordDefRegExp.multiline ? 'm' : '');
|
||||
return proxy.textualSuggest(resource.toString(), position, wordDef, wordDefFlags);
|
||||
});
|
||||
}
|
||||
|
||||
public navigateValueSet(resource: URI, range: IRange, up: boolean): TPromise<modes.IInplaceReplaceSupportResult> {
|
||||
return this._withSyncedResources([resource]).then(proxy => {
|
||||
let model = this._modelService.getModel(resource);
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
let wordDefRegExp = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageIdentifier().id);
|
||||
let wordDef = wordDefRegExp.source;
|
||||
let wordDefFlags = (wordDefRegExp.global ? 'g' : '') + (wordDefRegExp.ignoreCase ? 'i' : '') + (wordDefRegExp.multiline ? 'm' : '');
|
||||
return proxy.navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags);
|
||||
});
|
||||
}
|
||||
}
|
||||
318
src/vs/editor/common/services/languagesRegistry.ts
Normal file
318
src/vs/editor/common/services/languagesRegistry.ts
Normal file
@@ -0,0 +1,318 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import * as mime from 'vs/base/common/mime';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
|
||||
import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService';
|
||||
import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { NULL_MODE_ID, NULL_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/nullMode';
|
||||
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
export interface IResolvedLanguage {
|
||||
identifier: LanguageIdentifier;
|
||||
name: string;
|
||||
mimetypes: string[];
|
||||
aliases: string[];
|
||||
extensions: string[];
|
||||
filenames: string[];
|
||||
configurationFiles: string[];
|
||||
}
|
||||
|
||||
export class LanguagesRegistry {
|
||||
|
||||
private _nextLanguageId: number;
|
||||
private _languages: { [id: string]: IResolvedLanguage; };
|
||||
private _languageIds: string[];
|
||||
|
||||
private _mimeTypesMap: { [mimeType: string]: LanguageIdentifier; };
|
||||
private _nameMap: { [name: string]: LanguageIdentifier; };
|
||||
private _lowercaseNameMap: { [name: string]: LanguageIdentifier; };
|
||||
|
||||
constructor(useModesRegistry = true) {
|
||||
this._nextLanguageId = 1;
|
||||
this._languages = {};
|
||||
this._mimeTypesMap = {};
|
||||
this._nameMap = {};
|
||||
this._lowercaseNameMap = {};
|
||||
this._languageIds = [];
|
||||
|
||||
if (useModesRegistry) {
|
||||
this._registerLanguages(ModesRegistry.getLanguages());
|
||||
ModesRegistry.onDidAddLanguages((m) => this._registerLanguages(m));
|
||||
}
|
||||
}
|
||||
|
||||
_registerLanguages(desc: ILanguageExtensionPoint[]): void {
|
||||
if (desc.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < desc.length; i++) {
|
||||
this._registerLanguage(desc[i]);
|
||||
}
|
||||
|
||||
// Rebuild fast path maps
|
||||
this._mimeTypesMap = {};
|
||||
this._nameMap = {};
|
||||
this._lowercaseNameMap = {};
|
||||
Object.keys(this._languages).forEach((langId) => {
|
||||
let language = this._languages[langId];
|
||||
if (language.name) {
|
||||
this._nameMap[language.name] = language.identifier;
|
||||
}
|
||||
language.aliases.forEach((alias) => {
|
||||
this._lowercaseNameMap[alias.toLowerCase()] = language.identifier;
|
||||
});
|
||||
language.mimetypes.forEach((mimetype) => {
|
||||
this._mimeTypesMap[mimetype] = language.identifier;
|
||||
});
|
||||
});
|
||||
|
||||
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerOverrideIdentifiers(ModesRegistry.getLanguages().map(language => language.id));
|
||||
}
|
||||
|
||||
private _registerLanguage(lang: ILanguageExtensionPoint): void {
|
||||
const langId = lang.id;
|
||||
|
||||
let resolvedLanguage: IResolvedLanguage = null;
|
||||
if (hasOwnProperty.call(this._languages, langId)) {
|
||||
resolvedLanguage = this._languages[langId];
|
||||
} else {
|
||||
let languageId = this._nextLanguageId++;
|
||||
resolvedLanguage = {
|
||||
identifier: new LanguageIdentifier(langId, languageId),
|
||||
name: null,
|
||||
mimetypes: [],
|
||||
aliases: [],
|
||||
extensions: [],
|
||||
filenames: [],
|
||||
configurationFiles: []
|
||||
};
|
||||
this._languageIds[languageId] = langId;
|
||||
this._languages[langId] = resolvedLanguage;
|
||||
}
|
||||
|
||||
LanguagesRegistry._mergeLanguage(resolvedLanguage, lang);
|
||||
}
|
||||
|
||||
private static _mergeLanguage(resolvedLanguage: IResolvedLanguage, lang: ILanguageExtensionPoint): void {
|
||||
const langId = lang.id;
|
||||
|
||||
let primaryMime: string = null;
|
||||
|
||||
if (typeof lang.mimetypes !== 'undefined' && Array.isArray(lang.mimetypes)) {
|
||||
for (let i = 0; i < lang.mimetypes.length; i++) {
|
||||
if (!primaryMime) {
|
||||
primaryMime = lang.mimetypes[i];
|
||||
}
|
||||
resolvedLanguage.mimetypes.push(lang.mimetypes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!primaryMime) {
|
||||
primaryMime = `text/x-${langId}`;
|
||||
resolvedLanguage.mimetypes.push(primaryMime);
|
||||
}
|
||||
|
||||
if (Array.isArray(lang.extensions)) {
|
||||
for (let extension of lang.extensions) {
|
||||
mime.registerTextMime({ id: langId, mime: primaryMime, extension: extension });
|
||||
resolvedLanguage.extensions.push(extension);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(lang.filenames)) {
|
||||
for (let filename of lang.filenames) {
|
||||
mime.registerTextMime({ id: langId, mime: primaryMime, filename: filename });
|
||||
resolvedLanguage.filenames.push(filename);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(lang.filenamePatterns)) {
|
||||
for (let filenamePattern of lang.filenamePatterns) {
|
||||
mime.registerTextMime({ id: langId, mime: primaryMime, filepattern: filenamePattern });
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof lang.firstLine === 'string' && lang.firstLine.length > 0) {
|
||||
let firstLineRegexStr = lang.firstLine;
|
||||
if (firstLineRegexStr.charAt(0) !== '^') {
|
||||
firstLineRegexStr = '^' + firstLineRegexStr;
|
||||
}
|
||||
try {
|
||||
let firstLineRegex = new RegExp(firstLineRegexStr);
|
||||
if (!strings.regExpLeadsToEndlessLoop(firstLineRegex)) {
|
||||
mime.registerTextMime({ id: langId, mime: primaryMime, firstline: firstLineRegex });
|
||||
}
|
||||
} catch (err) {
|
||||
// Most likely, the regex was bad
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
}
|
||||
|
||||
resolvedLanguage.aliases.push(langId);
|
||||
|
||||
let langAliases: string[] = null;
|
||||
if (typeof lang.aliases !== 'undefined' && Array.isArray(lang.aliases)) {
|
||||
if (lang.aliases.length === 0) {
|
||||
// signal that this language should not get a name
|
||||
langAliases = [null];
|
||||
} else {
|
||||
langAliases = lang.aliases;
|
||||
}
|
||||
}
|
||||
|
||||
if (langAliases !== null) {
|
||||
for (let i = 0; i < langAliases.length; i++) {
|
||||
if (!langAliases[i] || langAliases[i].length === 0) {
|
||||
continue;
|
||||
}
|
||||
resolvedLanguage.aliases.push(langAliases[i]);
|
||||
}
|
||||
}
|
||||
|
||||
let containsAliases = (langAliases !== null && langAliases.length > 0);
|
||||
if (containsAliases && langAliases[0] === null) {
|
||||
// signal that this language should not get a name
|
||||
} else {
|
||||
let bestName = (containsAliases ? langAliases[0] : null) || langId;
|
||||
if (containsAliases || !resolvedLanguage.name) {
|
||||
resolvedLanguage.name = bestName;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof lang.configuration === 'string') {
|
||||
resolvedLanguage.configurationFiles.push(lang.configuration);
|
||||
}
|
||||
}
|
||||
|
||||
public isRegisteredMode(mimetypeOrModeId: string): boolean {
|
||||
// Is this a known mime type ?
|
||||
if (hasOwnProperty.call(this._mimeTypesMap, mimetypeOrModeId)) {
|
||||
return true;
|
||||
}
|
||||
// Is this a known mode id ?
|
||||
return hasOwnProperty.call(this._languages, mimetypeOrModeId);
|
||||
}
|
||||
|
||||
public getRegisteredModes(): string[] {
|
||||
return Object.keys(this._languages);
|
||||
}
|
||||
|
||||
public getRegisteredLanguageNames(): string[] {
|
||||
return Object.keys(this._nameMap);
|
||||
}
|
||||
|
||||
public getLanguageName(modeId: string): string {
|
||||
if (!hasOwnProperty.call(this._languages, modeId)) {
|
||||
return null;
|
||||
}
|
||||
return this._languages[modeId].name;
|
||||
}
|
||||
|
||||
public getModeIdForLanguageNameLowercase(languageNameLower: string): string {
|
||||
if (!hasOwnProperty.call(this._lowercaseNameMap, languageNameLower)) {
|
||||
return null;
|
||||
}
|
||||
return this._lowercaseNameMap[languageNameLower].language;
|
||||
}
|
||||
|
||||
public getConfigurationFiles(modeId: string): string[] {
|
||||
if (!hasOwnProperty.call(this._languages, modeId)) {
|
||||
return [];
|
||||
}
|
||||
return this._languages[modeId].configurationFiles || [];
|
||||
}
|
||||
|
||||
public getMimeForMode(modeId: string): string {
|
||||
if (!hasOwnProperty.call(this._languages, modeId)) {
|
||||
return null;
|
||||
}
|
||||
const language = this._languages[modeId];
|
||||
return (language.mimetypes[0] || null);
|
||||
}
|
||||
|
||||
public extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds: string): string[] {
|
||||
if (!commaSeparatedMimetypesOrCommaSeparatedIds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (
|
||||
commaSeparatedMimetypesOrCommaSeparatedIds.
|
||||
split(',').
|
||||
map((mimeTypeOrId) => mimeTypeOrId.trim()).
|
||||
map((mimeTypeOrId) => {
|
||||
if (hasOwnProperty.call(this._mimeTypesMap, mimeTypeOrId)) {
|
||||
return this._mimeTypesMap[mimeTypeOrId].language;
|
||||
}
|
||||
return mimeTypeOrId;
|
||||
}).
|
||||
filter((modeId) => {
|
||||
return hasOwnProperty.call(this._languages, modeId);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public getLanguageIdentifier(_modeId: string | LanguageId): LanguageIdentifier {
|
||||
if (_modeId === NULL_MODE_ID || _modeId === LanguageId.Null) {
|
||||
return NULL_LANGUAGE_IDENTIFIER;
|
||||
}
|
||||
|
||||
let modeId: string;
|
||||
if (typeof _modeId === 'string') {
|
||||
modeId = _modeId;
|
||||
} else {
|
||||
modeId = this._languageIds[_modeId];
|
||||
if (!modeId) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasOwnProperty.call(this._languages, modeId)) {
|
||||
return null;
|
||||
}
|
||||
return this._languages[modeId].identifier;
|
||||
}
|
||||
|
||||
public getModeIdsFromLanguageName(languageName: string): string[] {
|
||||
if (!languageName) {
|
||||
return [];
|
||||
}
|
||||
if (hasOwnProperty.call(this._nameMap, languageName)) {
|
||||
return [this._nameMap[languageName].language];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public getModeIdsFromFilenameOrFirstLine(filename: string, firstLine?: string): string[] {
|
||||
if (!filename && !firstLine) {
|
||||
return [];
|
||||
}
|
||||
var mimeTypes = mime.guessMimeTypes(filename, firstLine);
|
||||
return this.extractModeIds(mimeTypes.join(','));
|
||||
}
|
||||
|
||||
public getExtensions(languageName: string): string[] {
|
||||
if (!hasOwnProperty.call(this._nameMap, languageName)) {
|
||||
return [];
|
||||
}
|
||||
const languageId = this._nameMap[languageName];
|
||||
return this._languages[languageId.language].extensions;
|
||||
}
|
||||
|
||||
public getFilenames(languageName: string): string[] {
|
||||
if (!hasOwnProperty.call(this._nameMap, languageName)) {
|
||||
return [];
|
||||
}
|
||||
const languageId = this._nameMap[languageName];
|
||||
return this._languages[languageId.language].filenames;
|
||||
}
|
||||
}
|
||||
66
src/vs/editor/common/services/modeService.ts
Normal file
66
src/vs/editor/common/services/modeService.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
|
||||
export var IModeService = createDecorator<IModeService>('modeService');
|
||||
|
||||
export interface IModeLookupResult {
|
||||
modeId: string;
|
||||
isInstantiated: boolean;
|
||||
}
|
||||
|
||||
export interface ILanguageExtensionPoint {
|
||||
id: string;
|
||||
extensions?: string[];
|
||||
filenames?: string[];
|
||||
filenamePatterns?: string[];
|
||||
firstLine?: string;
|
||||
aliases?: string[];
|
||||
mimetypes?: string[];
|
||||
configuration?: string;
|
||||
}
|
||||
|
||||
export interface IValidLanguageExtensionPoint {
|
||||
id: string;
|
||||
extensions: string[];
|
||||
filenames: string[];
|
||||
filenamePatterns: string[];
|
||||
firstLine: string;
|
||||
aliases: string[];
|
||||
mimetypes: string[];
|
||||
configuration: string;
|
||||
}
|
||||
|
||||
export interface IModeService {
|
||||
_serviceBrand: any;
|
||||
|
||||
onDidCreateMode: Event<IMode>;
|
||||
|
||||
// --- reading
|
||||
isRegisteredMode(mimetypeOrModeId: string): boolean;
|
||||
getRegisteredModes(): string[];
|
||||
getRegisteredLanguageNames(): string[];
|
||||
getExtensions(alias: string): string[];
|
||||
getFilenames(alias: string): string[];
|
||||
getMimeForMode(modeId: string): string;
|
||||
getLanguageName(modeId: string): string;
|
||||
getModeIdForLanguageName(alias: string): string;
|
||||
getModeIdByFilenameOrFirstLine(filename: string, firstLine?: string): string;
|
||||
getModeId(commaSeparatedMimetypesOrCommaSeparatedIds: string): string;
|
||||
getLanguageIdentifier(modeId: string | LanguageId): LanguageIdentifier;
|
||||
getConfigurationFiles(modeId: string): string[];
|
||||
|
||||
// --- instantiation
|
||||
lookup(commaSeparatedMimetypesOrCommaSeparatedIds: string): IModeLookupResult[];
|
||||
getMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): IMode;
|
||||
getOrCreateMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): TPromise<IMode>;
|
||||
getOrCreateModeByLanguageName(languageName: string): TPromise<IMode>;
|
||||
getOrCreateModeByFilenameOrFirstLine(filename: string, firstLine?: string): TPromise<IMode>;
|
||||
}
|
||||
177
src/vs/editor/common/services/modeServiceImpl.ts
Normal file
177
src/vs/editor/common/services/modeServiceImpl.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { FrankensteinMode } from 'vs/editor/common/modes/abstractMode';
|
||||
import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry';
|
||||
import { IModeLookupResult, IModeService } from 'vs/editor/common/services/modeService';
|
||||
|
||||
export class ModeServiceImpl implements IModeService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
private readonly _instantiatedModes: { [modeId: string]: IMode; };
|
||||
private readonly _registry: LanguagesRegistry;
|
||||
|
||||
private readonly _onDidCreateMode: Emitter<IMode> = new Emitter<IMode>();
|
||||
public readonly onDidCreateMode: Event<IMode> = this._onDidCreateMode.event;
|
||||
|
||||
constructor() {
|
||||
this._instantiatedModes = {};
|
||||
|
||||
this._registry = new LanguagesRegistry();
|
||||
}
|
||||
|
||||
protected _onReady(): TPromise<boolean> {
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
public isRegisteredMode(mimetypeOrModeId: string): boolean {
|
||||
return this._registry.isRegisteredMode(mimetypeOrModeId);
|
||||
}
|
||||
|
||||
public getRegisteredModes(): string[] {
|
||||
return this._registry.getRegisteredModes();
|
||||
}
|
||||
|
||||
public getRegisteredLanguageNames(): string[] {
|
||||
return this._registry.getRegisteredLanguageNames();
|
||||
}
|
||||
|
||||
public getExtensions(alias: string): string[] {
|
||||
return this._registry.getExtensions(alias);
|
||||
}
|
||||
|
||||
public getFilenames(alias: string): string[] {
|
||||
return this._registry.getFilenames(alias);
|
||||
}
|
||||
|
||||
public getMimeForMode(modeId: string): string {
|
||||
return this._registry.getMimeForMode(modeId);
|
||||
}
|
||||
|
||||
public getLanguageName(modeId: string): string {
|
||||
return this._registry.getLanguageName(modeId);
|
||||
}
|
||||
|
||||
public getModeIdForLanguageName(alias: string): string {
|
||||
return this._registry.getModeIdForLanguageNameLowercase(alias);
|
||||
}
|
||||
|
||||
public getModeIdByFilenameOrFirstLine(filename: string, firstLine?: string): string {
|
||||
var modeIds = this._registry.getModeIdsFromFilenameOrFirstLine(filename, firstLine);
|
||||
|
||||
if (modeIds.length > 0) {
|
||||
return modeIds[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public getModeId(commaSeparatedMimetypesOrCommaSeparatedIds: string): string {
|
||||
var modeIds = this._registry.extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds);
|
||||
|
||||
if (modeIds.length > 0) {
|
||||
return modeIds[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public getLanguageIdentifier(modeId: string | LanguageId): LanguageIdentifier {
|
||||
return this._registry.getLanguageIdentifier(modeId);
|
||||
}
|
||||
|
||||
public getConfigurationFiles(modeId: string): string[] {
|
||||
return this._registry.getConfigurationFiles(modeId);
|
||||
}
|
||||
|
||||
// --- instantiation
|
||||
|
||||
public lookup(commaSeparatedMimetypesOrCommaSeparatedIds: string): IModeLookupResult[] {
|
||||
var r: IModeLookupResult[] = [];
|
||||
var modeIds = this._registry.extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds);
|
||||
|
||||
for (var i = 0; i < modeIds.length; i++) {
|
||||
var modeId = modeIds[i];
|
||||
|
||||
r.push({
|
||||
modeId: modeId,
|
||||
isInstantiated: this._instantiatedModes.hasOwnProperty(modeId)
|
||||
});
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public getMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): IMode {
|
||||
var modeIds = this._registry.extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds);
|
||||
|
||||
var isPlainText = false;
|
||||
for (var i = 0; i < modeIds.length; i++) {
|
||||
if (this._instantiatedModes.hasOwnProperty(modeIds[i])) {
|
||||
return this._instantiatedModes[modeIds[i]];
|
||||
}
|
||||
isPlainText = isPlainText || (modeIds[i] === 'plaintext');
|
||||
}
|
||||
|
||||
if (isPlainText) {
|
||||
// Try to do it synchronously
|
||||
var r: IMode = null;
|
||||
this.getOrCreateMode(commaSeparatedMimetypesOrCommaSeparatedIds).then((mode) => {
|
||||
r = mode;
|
||||
}).done(null, onUnexpectedError);
|
||||
return r;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getOrCreateMode(commaSeparatedMimetypesOrCommaSeparatedIds: string): TPromise<IMode> {
|
||||
return this._onReady().then(() => {
|
||||
var modeId = this.getModeId(commaSeparatedMimetypesOrCommaSeparatedIds);
|
||||
// Fall back to plain text if no mode was found
|
||||
return this._getOrCreateMode(modeId || 'plaintext');
|
||||
});
|
||||
}
|
||||
|
||||
public getOrCreateModeByLanguageName(languageName: string): TPromise<IMode> {
|
||||
return this._onReady().then(() => {
|
||||
var modeId = this._getModeIdByLanguageName(languageName);
|
||||
// Fall back to plain text if no mode was found
|
||||
return this._getOrCreateMode(modeId || 'plaintext');
|
||||
});
|
||||
}
|
||||
|
||||
private _getModeIdByLanguageName(languageName: string): string {
|
||||
var modeIds = this._registry.getModeIdsFromLanguageName(languageName);
|
||||
|
||||
if (modeIds.length > 0) {
|
||||
return modeIds[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public getOrCreateModeByFilenameOrFirstLine(filename: string, firstLine?: string): TPromise<IMode> {
|
||||
return this._onReady().then(() => {
|
||||
var modeId = this.getModeIdByFilenameOrFirstLine(filename, firstLine);
|
||||
// Fall back to plain text if no mode was found
|
||||
return this._getOrCreateMode(modeId || 'plaintext');
|
||||
});
|
||||
}
|
||||
|
||||
private _getOrCreateMode(modeId: string): IMode {
|
||||
if (!this._instantiatedModes.hasOwnProperty(modeId)) {
|
||||
let languageIdentifier = this.getLanguageIdentifier(modeId);
|
||||
this._instantiatedModes[modeId] = new FrankensteinMode(languageIdentifier);
|
||||
|
||||
this._onDidCreateMode.fire(this._instantiatedModes[modeId]);
|
||||
}
|
||||
return this._instantiatedModes[modeId];
|
||||
}
|
||||
}
|
||||
40
src/vs/editor/common/services/modelService.ts
Normal file
40
src/vs/editor/common/services/modelService.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IModel, ITextModelCreationOptions } from 'vs/editor/common/editorCommon';
|
||||
import { IMode } from 'vs/editor/common/modes';
|
||||
import { IRawTextSource } from 'vs/editor/common/model/textSource';
|
||||
|
||||
export var IModelService = createDecorator<IModelService>('modelService');
|
||||
|
||||
export interface IModelService {
|
||||
_serviceBrand: any;
|
||||
|
||||
createModel(value: string | IRawTextSource, modeOrPromise: TPromise<IMode> | IMode, resource: URI): IModel;
|
||||
|
||||
updateModel(model: IModel, value: string | IRawTextSource): void;
|
||||
|
||||
setMode(model: IModel, modeOrPromise: TPromise<IMode> | IMode): void;
|
||||
|
||||
destroyModel(resource: URI): void;
|
||||
|
||||
getModels(): IModel[];
|
||||
|
||||
getCreationOptions(language: string, resource: URI): ITextModelCreationOptions;
|
||||
|
||||
getModel(resource: URI): IModel;
|
||||
|
||||
onModelAdded: Event<IModel>;
|
||||
|
||||
onModelRemoved: Event<IModel>;
|
||||
|
||||
onModelModeChanged: Event<{ model: IModel; oldModeId: string; }>;
|
||||
}
|
||||
|
||||
595
src/vs/editor/common/services/modelServiceImpl.ts
Normal file
595
src/vs/editor/common/services/modelServiceImpl.ts
Normal file
@@ -0,0 +1,595 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import network = require('vs/base/common/network');
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { EmitterEvent } from 'vs/base/common/eventEmitter';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IMarker, IMarkerService } from 'vs/platform/markers/common/markers';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { Model } from 'vs/editor/common/model/model';
|
||||
import { IMode, LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry';
|
||||
import { IRawTextSource, TextSource, RawTextSource, ITextSource } from 'vs/editor/common/model/textSource';
|
||||
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
|
||||
import { ClassName } from 'vs/editor/common/model/textModelWithDecorations';
|
||||
import { ISequence, LcsDiff } from 'vs/base/common/diff/diff';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { themeColorFromId, ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { overviewRulerWarning, overviewRulerError } from 'vs/editor/common/view/editorColorRegistry';
|
||||
|
||||
function MODEL_ID(resource: URI): string {
|
||||
return resource.toString();
|
||||
}
|
||||
|
||||
class ModelData implements IDisposable {
|
||||
model: editorCommon.IModel;
|
||||
|
||||
private _markerDecorations: string[];
|
||||
private _modelEventsListener: IDisposable;
|
||||
|
||||
constructor(model: editorCommon.IModel, eventsHandler: (modelData: ModelData, events: EmitterEvent[]) => void) {
|
||||
this.model = model;
|
||||
|
||||
this._markerDecorations = [];
|
||||
this._modelEventsListener = model.addBulkListener((events) => eventsHandler(this, events));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._markerDecorations = this.model.deltaDecorations(this._markerDecorations, []);
|
||||
this._modelEventsListener.dispose();
|
||||
this._modelEventsListener = null;
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
public getModelId(): string {
|
||||
return MODEL_ID(this.model.uri);
|
||||
}
|
||||
|
||||
public acceptMarkerDecorations(newDecorations: editorCommon.IModelDeltaDecoration[]): void {
|
||||
this._markerDecorations = this.model.deltaDecorations(this._markerDecorations, newDecorations);
|
||||
}
|
||||
}
|
||||
|
||||
class ModelMarkerHandler {
|
||||
|
||||
public static setMarkers(modelData: ModelData, markerService: IMarkerService): void {
|
||||
|
||||
// Limit to the first 500 errors/warnings
|
||||
const markers = markerService.read({ resource: modelData.model.uri, take: 500 });
|
||||
|
||||
let newModelDecorations: editorCommon.IModelDeltaDecoration[] = markers.map((marker) => {
|
||||
return {
|
||||
range: this._createDecorationRange(modelData.model, marker),
|
||||
options: this._createDecorationOption(marker)
|
||||
};
|
||||
});
|
||||
|
||||
modelData.acceptMarkerDecorations(newModelDecorations);
|
||||
}
|
||||
|
||||
private static _createDecorationRange(model: editorCommon.IModel, rawMarker: IMarker): Range {
|
||||
let marker = model.validateRange(new Range(rawMarker.startLineNumber, rawMarker.startColumn, rawMarker.endLineNumber, rawMarker.endColumn));
|
||||
let ret: Range = new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn);
|
||||
if (ret.isEmpty()) {
|
||||
let word = model.getWordAtPosition(ret.getStartPosition());
|
||||
if (word) {
|
||||
ret = new Range(ret.startLineNumber, word.startColumn, ret.endLineNumber, word.endColumn);
|
||||
} else {
|
||||
let maxColumn = model.getLineLastNonWhitespaceColumn(marker.startLineNumber) ||
|
||||
model.getLineMaxColumn(marker.startLineNumber);
|
||||
|
||||
if (maxColumn === 1) {
|
||||
// empty line
|
||||
// console.warn('marker on empty line:', marker);
|
||||
} else if (ret.endColumn >= maxColumn) {
|
||||
// behind eol
|
||||
ret = new Range(ret.startLineNumber, maxColumn - 1, ret.endLineNumber, maxColumn);
|
||||
} else {
|
||||
// extend marker to width = 1
|
||||
ret = new Range(ret.startLineNumber, ret.startColumn, ret.endLineNumber, ret.endColumn + 1);
|
||||
}
|
||||
}
|
||||
} else if (rawMarker.endColumn === Number.MAX_VALUE && rawMarker.startColumn === 1 && ret.startLineNumber === ret.endLineNumber) {
|
||||
let minColumn = model.getLineFirstNonWhitespaceColumn(rawMarker.startLineNumber);
|
||||
if (minColumn < ret.endColumn) {
|
||||
ret = new Range(ret.startLineNumber, minColumn, ret.endLineNumber, ret.endColumn);
|
||||
rawMarker.startColumn = minColumn;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static _createDecorationOption(marker: IMarker): editorCommon.IModelDecorationOptions {
|
||||
|
||||
let className: string;
|
||||
let color: ThemeColor;
|
||||
let darkColor: ThemeColor;
|
||||
|
||||
switch (marker.severity) {
|
||||
case Severity.Ignore:
|
||||
// do something
|
||||
break;
|
||||
case Severity.Warning:
|
||||
case Severity.Info:
|
||||
className = ClassName.EditorWarningDecoration;
|
||||
color = themeColorFromId(overviewRulerWarning);
|
||||
darkColor = themeColorFromId(overviewRulerWarning);
|
||||
break;
|
||||
case Severity.Error:
|
||||
default:
|
||||
className = ClassName.EditorErrorDecoration;
|
||||
color = themeColorFromId(overviewRulerError);
|
||||
darkColor = themeColorFromId(overviewRulerError);
|
||||
break;
|
||||
}
|
||||
|
||||
let hoverMessage: MarkdownString = null;
|
||||
let { message, source } = marker;
|
||||
|
||||
if (typeof message === 'string') {
|
||||
message = message.trim();
|
||||
|
||||
if (source) {
|
||||
if (/\n/g.test(message)) {
|
||||
message = nls.localize('diagAndSourceMultiline', "[{0}]\n{1}", source, message);
|
||||
} else {
|
||||
message = nls.localize('diagAndSource', "[{0}] {1}", source, message);
|
||||
}
|
||||
}
|
||||
|
||||
hoverMessage = new MarkdownString().appendCodeblock('_', message);
|
||||
}
|
||||
|
||||
return {
|
||||
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className,
|
||||
hoverMessage,
|
||||
showIfCollapsed: true,
|
||||
overviewRuler: {
|
||||
color,
|
||||
darkColor,
|
||||
position: editorCommon.OverviewRulerLane.Right
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface IRawConfig {
|
||||
files?: {
|
||||
eol?: any;
|
||||
};
|
||||
editor?: {
|
||||
tabSize?: any;
|
||||
insertSpaces?: any;
|
||||
detectIndentation?: any;
|
||||
trimAutoWhitespace?: any;
|
||||
};
|
||||
}
|
||||
|
||||
const DEFAULT_EOL = (platform.isLinux || platform.isMacintosh) ? editorCommon.DefaultEndOfLine.LF : editorCommon.DefaultEndOfLine.CRLF;
|
||||
|
||||
export class ModelServiceImpl implements IModelService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
private _markerService: IMarkerService;
|
||||
private _markerServiceSubscription: IDisposable;
|
||||
private _configurationService: IConfigurationService;
|
||||
private _configurationServiceSubscription: IDisposable;
|
||||
|
||||
private _onModelAdded: Emitter<editorCommon.IModel>;
|
||||
private _onModelRemoved: Emitter<editorCommon.IModel>;
|
||||
private _onModelModeChanged: Emitter<{ model: editorCommon.IModel; oldModeId: string; }>;
|
||||
|
||||
private _modelCreationOptionsByLanguageAndResource: {
|
||||
[languageAndResource: string]: editorCommon.ITextModelCreationOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* All the models known in the system.
|
||||
*/
|
||||
private _models: { [modelId: string]: ModelData; };
|
||||
|
||||
constructor(
|
||||
@IMarkerService markerService: IMarkerService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
) {
|
||||
this._markerService = markerService;
|
||||
this._configurationService = configurationService;
|
||||
this._models = {};
|
||||
this._modelCreationOptionsByLanguageAndResource = Object.create(null);
|
||||
this._onModelAdded = new Emitter<editorCommon.IModel>();
|
||||
this._onModelRemoved = new Emitter<editorCommon.IModel>();
|
||||
this._onModelModeChanged = new Emitter<{ model: editorCommon.IModel; oldModeId: string; }>();
|
||||
|
||||
if (this._markerService) {
|
||||
this._markerServiceSubscription = this._markerService.onMarkerChanged(this._handleMarkerChange, this);
|
||||
}
|
||||
|
||||
this._configurationServiceSubscription = this._configurationService.onDidUpdateConfiguration(e => this._updateModelOptions());
|
||||
this._updateModelOptions();
|
||||
}
|
||||
|
||||
private static _readModelOptions(config: IRawConfig): editorCommon.ITextModelCreationOptions {
|
||||
let tabSize = EDITOR_MODEL_DEFAULTS.tabSize;
|
||||
if (config.editor && typeof config.editor.tabSize !== 'undefined') {
|
||||
let parsedTabSize = parseInt(config.editor.tabSize, 10);
|
||||
if (!isNaN(parsedTabSize)) {
|
||||
tabSize = parsedTabSize;
|
||||
}
|
||||
}
|
||||
|
||||
let insertSpaces = EDITOR_MODEL_DEFAULTS.insertSpaces;
|
||||
if (config.editor && typeof config.editor.insertSpaces !== 'undefined') {
|
||||
insertSpaces = (config.editor.insertSpaces === 'false' ? false : Boolean(config.editor.insertSpaces));
|
||||
}
|
||||
|
||||
let newDefaultEOL = DEFAULT_EOL;
|
||||
const eol = config.files && config.files.eol;
|
||||
if (eol === '\r\n') {
|
||||
newDefaultEOL = editorCommon.DefaultEndOfLine.CRLF;
|
||||
} else if (eol === '\n') {
|
||||
newDefaultEOL = editorCommon.DefaultEndOfLine.LF;
|
||||
}
|
||||
|
||||
let trimAutoWhitespace = EDITOR_MODEL_DEFAULTS.trimAutoWhitespace;
|
||||
if (config.editor && typeof config.editor.trimAutoWhitespace !== 'undefined') {
|
||||
trimAutoWhitespace = (config.editor.trimAutoWhitespace === 'false' ? false : Boolean(config.editor.trimAutoWhitespace));
|
||||
}
|
||||
|
||||
let detectIndentation = EDITOR_MODEL_DEFAULTS.detectIndentation;
|
||||
if (config.editor && typeof config.editor.detectIndentation !== 'undefined') {
|
||||
detectIndentation = (config.editor.detectIndentation === 'false' ? false : Boolean(config.editor.detectIndentation));
|
||||
}
|
||||
|
||||
return {
|
||||
tabSize: tabSize,
|
||||
insertSpaces: insertSpaces,
|
||||
detectIndentation: detectIndentation,
|
||||
defaultEOL: newDefaultEOL,
|
||||
trimAutoWhitespace: trimAutoWhitespace
|
||||
};
|
||||
}
|
||||
|
||||
public getCreationOptions(language: string, resource: URI): editorCommon.ITextModelCreationOptions {
|
||||
let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource];
|
||||
if (!creationOptions) {
|
||||
creationOptions = ModelServiceImpl._readModelOptions(this._configurationService.getConfiguration(null, { overrideIdentifier: language, resource }));
|
||||
this._modelCreationOptionsByLanguageAndResource[language + resource] = creationOptions;
|
||||
}
|
||||
return creationOptions;
|
||||
}
|
||||
|
||||
private _updateModelOptions(): void {
|
||||
let oldOptionsByLanguageAndResource = this._modelCreationOptionsByLanguageAndResource;
|
||||
this._modelCreationOptionsByLanguageAndResource = Object.create(null);
|
||||
|
||||
// Update options on all models
|
||||
let keys = Object.keys(this._models);
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
let modelId = keys[i];
|
||||
let modelData = this._models[modelId];
|
||||
const language = modelData.model.getLanguageIdentifier().language;
|
||||
const uri = modelData.model.uri;
|
||||
const oldOptions = oldOptionsByLanguageAndResource[language + uri];
|
||||
const newOptions = this.getCreationOptions(language, uri);
|
||||
ModelServiceImpl._setModelOptionsForModel(modelData.model, newOptions, oldOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private static _setModelOptionsForModel(model: editorCommon.IModel, newOptions: editorCommon.ITextModelCreationOptions, currentOptions: editorCommon.ITextModelCreationOptions): void {
|
||||
if (currentOptions
|
||||
&& (currentOptions.detectIndentation === newOptions.detectIndentation)
|
||||
&& (currentOptions.insertSpaces === newOptions.insertSpaces)
|
||||
&& (currentOptions.tabSize === newOptions.tabSize)
|
||||
&& (currentOptions.trimAutoWhitespace === newOptions.trimAutoWhitespace)
|
||||
) {
|
||||
// Same indent opts, no need to touch the model
|
||||
return;
|
||||
}
|
||||
|
||||
if (newOptions.detectIndentation) {
|
||||
model.detectIndentation(newOptions.insertSpaces, newOptions.tabSize);
|
||||
model.updateOptions({
|
||||
trimAutoWhitespace: newOptions.trimAutoWhitespace
|
||||
});
|
||||
} else {
|
||||
model.updateOptions({
|
||||
insertSpaces: newOptions.insertSpaces,
|
||||
tabSize: newOptions.tabSize,
|
||||
trimAutoWhitespace: newOptions.trimAutoWhitespace
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._markerServiceSubscription) {
|
||||
this._markerServiceSubscription.dispose();
|
||||
}
|
||||
this._configurationServiceSubscription.dispose();
|
||||
}
|
||||
|
||||
private _handleMarkerChange(changedResources: URI[]): void {
|
||||
changedResources.forEach((resource) => {
|
||||
let modelId = MODEL_ID(resource);
|
||||
let modelData = this._models[modelId];
|
||||
if (!modelData) {
|
||||
return;
|
||||
}
|
||||
ModelMarkerHandler.setMarkers(modelData, this._markerService);
|
||||
});
|
||||
}
|
||||
|
||||
private _cleanUp(model: editorCommon.IModel): void {
|
||||
// clean up markers for internal, transient models
|
||||
if (model.uri.scheme === network.Schemas.inMemory
|
||||
|| model.uri.scheme === network.Schemas.internal
|
||||
|| model.uri.scheme === network.Schemas.vscode) {
|
||||
if (this._markerService) {
|
||||
this._markerService.read({ resource: model.uri }).map(marker => marker.owner).forEach(owner => this._markerService.remove(owner, [model.uri]));
|
||||
}
|
||||
}
|
||||
|
||||
// clean up cache
|
||||
delete this._modelCreationOptionsByLanguageAndResource[model.getLanguageIdentifier().language + model.uri];
|
||||
}
|
||||
|
||||
// --- begin IModelService
|
||||
|
||||
private _createModelData(value: string | IRawTextSource, languageIdentifier: LanguageIdentifier, resource: URI): ModelData {
|
||||
// create & save the model
|
||||
const options = this.getCreationOptions(languageIdentifier.language, resource);
|
||||
const rawTextSource = (typeof value === 'string' ? RawTextSource.fromString(value) : value);
|
||||
let model: Model = new Model(rawTextSource, options, languageIdentifier, resource);
|
||||
let modelId = MODEL_ID(model.uri);
|
||||
|
||||
if (this._models[modelId]) {
|
||||
// There already exists a model with this id => this is a programmer error
|
||||
throw new Error('ModelService: Cannot add model because it already exists!');
|
||||
}
|
||||
|
||||
let modelData = new ModelData(model, (modelData, events) => this._onModelEvents(modelData, events));
|
||||
this._models[modelId] = modelData;
|
||||
|
||||
return modelData;
|
||||
}
|
||||
|
||||
public updateModel(model: editorCommon.IModel, value: string | IRawTextSource): void {
|
||||
let options = this.getCreationOptions(model.getLanguageIdentifier().language, model.uri);
|
||||
const textSource = TextSource.create(value, options.defaultEOL);
|
||||
|
||||
// Return early if the text is already set in that form
|
||||
if (model.equals(textSource)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise find a diff between the values and update model
|
||||
model.setEOL(textSource.EOL === '\r\n' ? editorCommon.EndOfLineSequence.CRLF : editorCommon.EndOfLineSequence.LF);
|
||||
model.pushEditOperations(
|
||||
[new Selection(1, 1, 1, 1)],
|
||||
ModelServiceImpl._computeEdits(model, textSource),
|
||||
(inverseEditOperations: editorCommon.IIdentifiedSingleEditOperation[]) => [new Selection(1, 1, 1, 1)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute edits to bring `model` to the state of `textSource`.
|
||||
*/
|
||||
public static _computeEdits(model: editorCommon.IModel, textSource: ITextSource): editorCommon.IIdentifiedSingleEditOperation[] {
|
||||
const modelLineSequence = new class implements ISequence {
|
||||
public getLength(): number {
|
||||
return model.getLineCount();
|
||||
}
|
||||
public getElementHash(index: number): string {
|
||||
return model.getLineContent(index + 1);
|
||||
}
|
||||
};
|
||||
const textSourceLineSequence = new class implements ISequence {
|
||||
public getLength(): number {
|
||||
return textSource.lines.length;
|
||||
}
|
||||
public getElementHash(index: number): string {
|
||||
return textSource.lines[index];
|
||||
}
|
||||
};
|
||||
|
||||
const diffResult = new LcsDiff(modelLineSequence, textSourceLineSequence).ComputeDiff(false);
|
||||
|
||||
let edits: editorCommon.IIdentifiedSingleEditOperation[] = [], editsLen = 0;
|
||||
const modelLineCount = model.getLineCount();
|
||||
for (let i = 0, len = diffResult.length; i < len; i++) {
|
||||
const diff = diffResult[i];
|
||||
const originalStart = diff.originalStart;
|
||||
const originalLength = diff.originalLength;
|
||||
const modifiedStart = diff.modifiedStart;
|
||||
const modifiedLength = diff.modifiedLength;
|
||||
|
||||
let lines: string[] = [];
|
||||
for (let j = 0; j < modifiedLength; j++) {
|
||||
lines[j] = textSource.lines[modifiedStart + j];
|
||||
}
|
||||
let text = lines.join('\n');
|
||||
|
||||
let range: Range;
|
||||
if (originalLength === 0) {
|
||||
// insertion
|
||||
|
||||
if (originalStart === modelLineCount) {
|
||||
// insert at the end
|
||||
const maxLineColumn = model.getLineMaxColumn(modelLineCount);
|
||||
range = new Range(
|
||||
modelLineCount, maxLineColumn,
|
||||
modelLineCount, maxLineColumn
|
||||
);
|
||||
text = '\n' + text;
|
||||
} else {
|
||||
// insert
|
||||
range = new Range(
|
||||
originalStart + 1, 1,
|
||||
originalStart + 1, 1
|
||||
);
|
||||
text = text + '\n';
|
||||
}
|
||||
|
||||
} else if (modifiedLength === 0) {
|
||||
// deletion
|
||||
|
||||
if (originalStart + originalLength >= modelLineCount) {
|
||||
// delete at the end
|
||||
range = new Range(
|
||||
originalStart, model.getLineMaxColumn(originalStart),
|
||||
originalStart + originalLength, model.getLineMaxColumn(originalStart + originalLength)
|
||||
);
|
||||
} else {
|
||||
// delete
|
||||
range = new Range(
|
||||
originalStart + 1, 1,
|
||||
originalStart + originalLength + 1, 1
|
||||
);
|
||||
}
|
||||
|
||||
} else {
|
||||
// modification
|
||||
range = new Range(
|
||||
originalStart + 1, 1,
|
||||
originalStart + originalLength, model.getLineMaxColumn(originalStart + originalLength)
|
||||
);
|
||||
}
|
||||
|
||||
edits[editsLen++] = EditOperation.replace(range, text);
|
||||
}
|
||||
|
||||
return edits;
|
||||
}
|
||||
|
||||
public createModel(value: string | IRawTextSource, modeOrPromise: TPromise<IMode> | IMode, resource: URI): editorCommon.IModel {
|
||||
let modelData: ModelData;
|
||||
|
||||
if (!modeOrPromise || TPromise.is(modeOrPromise)) {
|
||||
modelData = this._createModelData(value, PLAINTEXT_LANGUAGE_IDENTIFIER, resource);
|
||||
this.setMode(modelData.model, modeOrPromise);
|
||||
} else {
|
||||
modelData = this._createModelData(value, modeOrPromise.getLanguageIdentifier(), resource);
|
||||
}
|
||||
|
||||
// handle markers (marker service => model)
|
||||
if (this._markerService) {
|
||||
ModelMarkerHandler.setMarkers(modelData, this._markerService);
|
||||
}
|
||||
|
||||
this._onModelAdded.fire(modelData.model);
|
||||
|
||||
return modelData.model;
|
||||
}
|
||||
|
||||
public setMode(model: editorCommon.IModel, modeOrPromise: TPromise<IMode> | IMode): void {
|
||||
if (!modeOrPromise) {
|
||||
return;
|
||||
}
|
||||
if (TPromise.is(modeOrPromise)) {
|
||||
modeOrPromise.then((mode) => {
|
||||
if (!model.isDisposed()) {
|
||||
model.setMode(mode.getLanguageIdentifier());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
model.setMode(modeOrPromise.getLanguageIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
public destroyModel(resource: URI): void {
|
||||
// We need to support that not all models get disposed through this service (i.e. model.dispose() should work!)
|
||||
let modelData = this._models[MODEL_ID(resource)];
|
||||
if (!modelData) {
|
||||
return;
|
||||
}
|
||||
modelData.model.dispose();
|
||||
}
|
||||
|
||||
public getModels(): editorCommon.IModel[] {
|
||||
let ret: editorCommon.IModel[] = [];
|
||||
|
||||
let keys = Object.keys(this._models);
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
let modelId = keys[i];
|
||||
ret.push(this._models[modelId].model);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public getModel(resource: URI): editorCommon.IModel {
|
||||
let modelId = MODEL_ID(resource);
|
||||
let modelData = this._models[modelId];
|
||||
if (!modelData) {
|
||||
return null;
|
||||
}
|
||||
return modelData.model;
|
||||
}
|
||||
|
||||
public get onModelAdded(): Event<editorCommon.IModel> {
|
||||
return this._onModelAdded ? this._onModelAdded.event : null;
|
||||
}
|
||||
|
||||
public get onModelRemoved(): Event<editorCommon.IModel> {
|
||||
return this._onModelRemoved ? this._onModelRemoved.event : null;
|
||||
}
|
||||
|
||||
public get onModelModeChanged(): Event<{ model: editorCommon.IModel; oldModeId: string; }> {
|
||||
return this._onModelModeChanged ? this._onModelModeChanged.event : null;
|
||||
}
|
||||
|
||||
// --- end IModelService
|
||||
|
||||
private _onModelDisposing(model: editorCommon.IModel): void {
|
||||
let modelId = MODEL_ID(model.uri);
|
||||
let modelData = this._models[modelId];
|
||||
|
||||
delete this._models[modelId];
|
||||
modelData.dispose();
|
||||
|
||||
this._cleanUp(model);
|
||||
this._onModelRemoved.fire(model);
|
||||
}
|
||||
|
||||
private _onModelEvents(modelData: ModelData, events: EmitterEvent[]): void {
|
||||
|
||||
// First look for dispose
|
||||
for (let i = 0, len = events.length; i < len; i++) {
|
||||
let e = events[i];
|
||||
if (e.type === textModelEvents.TextModelEventType.ModelDispose) {
|
||||
this._onModelDisposing(modelData.model);
|
||||
// no more processing since model got disposed
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Second, look for mode change
|
||||
for (let i = 0, len = events.length; i < len; i++) {
|
||||
let e = events[i];
|
||||
if (e.type === textModelEvents.TextModelEventType.ModelLanguageChanged) {
|
||||
const model = modelData.model;
|
||||
const oldModeId = (<textModelEvents.IModelLanguageChangedEvent>e.data).oldLanguage;
|
||||
const newModeId = model.getLanguageIdentifier().language;
|
||||
const oldOptions = this.getCreationOptions(oldModeId, model.uri);
|
||||
const newOptions = this.getCreationOptions(newModeId, model.uri);
|
||||
ModelServiceImpl._setModelOptionsForModel(model, newOptions, oldOptions);
|
||||
this._onModelModeChanged.fire({ model, oldModeId });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/vs/editor/common/services/resolverService.ts
Normal file
45
src/vs/editor/common/services/resolverService.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IModel } from 'vs/editor/common/editorCommon';
|
||||
import { IEditorModel } from 'vs/platform/editor/common/editor';
|
||||
import { IDisposable, IReference } from 'vs/base/common/lifecycle';
|
||||
|
||||
export const ITextModelService = createDecorator<ITextModelService>('textModelService');
|
||||
|
||||
export interface ITextModelService {
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* Provided a resource URI, it will return a model reference
|
||||
* which should be disposed once not needed anymore.
|
||||
*/
|
||||
createModelReference(resource: URI): TPromise<IReference<ITextEditorModel>>;
|
||||
|
||||
/**
|
||||
* Registers a specific `scheme` content provider.
|
||||
*/
|
||||
registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable;
|
||||
}
|
||||
|
||||
export interface ITextModelContentProvider {
|
||||
|
||||
/**
|
||||
* Given a resource, return the content of the resource as IModel.
|
||||
*/
|
||||
provideTextContent(resource: URI): TPromise<IModel>;
|
||||
}
|
||||
|
||||
export interface ITextEditorModel extends IEditorModel {
|
||||
|
||||
/**
|
||||
* Provides access to the underlying IModel.
|
||||
*/
|
||||
textEditorModel: IModel;
|
||||
}
|
||||
34
src/vs/editor/common/services/resourceConfiguration.ts
Normal file
34
src/vs/editor/common/services/resourceConfiguration.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Event from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
|
||||
export const ITextResourceConfigurationService = createDecorator<ITextResourceConfigurationService>('textResourceConfigurationService');
|
||||
|
||||
export interface ITextResourceConfigurationService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* Event that fires when the configuration changes.
|
||||
*/
|
||||
onDidUpdateConfiguration: Event<void>;
|
||||
|
||||
/**
|
||||
* Fetches the appropriate section of the for the given resource with appropriate overrides (e.g. language).
|
||||
* This will be an object keyed off the section name.
|
||||
*
|
||||
* @param resource - Resource for which the configuration has to be fetched. Can be `null` or `undefined`.
|
||||
* @param postion - Position in the resource for which configuration has to be fetched. Can be `null` or `undefined`.
|
||||
* @param section - Section of the configuraion. Can be `null` or `undefined`.
|
||||
*
|
||||
*/
|
||||
getConfiguration<T>(resource: URI, section?: string): T;
|
||||
getConfiguration<T>(resource: URI, position?: IPosition, section?: string): T;
|
||||
|
||||
}
|
||||
47
src/vs/editor/common/services/resourceConfigurationImpl.ts
Normal file
47
src/vs/editor/common/services/resourceConfigurationImpl.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
|
||||
export class TextResourceConfigurationService extends Disposable implements ITextResourceConfigurationService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private readonly _onDidUpdateConfiguration: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidUpdateConfiguration: Event<void> = this._onDidUpdateConfiguration.event;
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IModelService private modelService: IModelService,
|
||||
@IModeService private modeService: IModeService,
|
||||
) {
|
||||
super();
|
||||
this._register(this.configurationService.onDidUpdateConfiguration(() => this._onDidUpdateConfiguration.fire()));
|
||||
}
|
||||
|
||||
getConfiguration<T>(resource: URI, section?: string): T
|
||||
getConfiguration<T>(resource: URI, at?: IPosition, section?: string): T
|
||||
getConfiguration<T>(resource: URI, arg2?: any, arg3?: any): T {
|
||||
const position: IPosition = Position.isIPosition(arg2) ? arg2 : null;
|
||||
const section: string = position ? (typeof arg3 === 'string' ? arg3 : void 0) : (typeof arg2 === 'string' ? arg2 : void 0);
|
||||
const language = resource ? this.getLanguage(resource, position) : void 0;
|
||||
return this.configurationService.getConfiguration<T>(section, { resource, overrideIdentifier: language });
|
||||
}
|
||||
|
||||
private getLanguage(resource: URI, position: IPosition): string {
|
||||
const model = this.modelService.getModel(resource);
|
||||
if (model) {
|
||||
return position ? this.modeService.getLanguageIdentifier(model.getLanguageIdAtPosition(position.lineNumber, position.column)).language : model.getLanguageIdentifier().language;
|
||||
}
|
||||
return this.modeService.getModeIdByFilenameOrFirstLine(resource.fsPath);
|
||||
}
|
||||
}
|
||||
106
src/vs/editor/common/services/webWorker.ts
Normal file
106
src/vs/editor/common/services/webWorker.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ShallowCancelThenPromise } from 'vs/base/common/async';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { EditorWorkerClient } from 'vs/editor/common/services/editorWorkerServiceImpl';
|
||||
|
||||
/**
|
||||
* Create a new web worker that has model syncing capabilities built in.
|
||||
* Specify an AMD module to load that will `create` an object that will be proxied.
|
||||
*/
|
||||
export function createWebWorker<T>(modelService: IModelService, opts: IWebWorkerOptions): MonacoWebWorker<T> {
|
||||
return new MonacoWebWorkerImpl<T>(modelService, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* A web worker that can provide a proxy to an arbitrary file.
|
||||
*/
|
||||
export interface MonacoWebWorker<T> {
|
||||
/**
|
||||
* Terminate the web worker, thus invalidating the returned proxy.
|
||||
*/
|
||||
dispose(): void;
|
||||
/**
|
||||
* Get a proxy to the arbitrary loaded code.
|
||||
*/
|
||||
getProxy(): TPromise<T>;
|
||||
/**
|
||||
* Synchronize (send) the models at `resources` to the web worker,
|
||||
* making them available in the monaco.worker.getMirrorModels().
|
||||
*/
|
||||
withSyncedResources(resources: URI[]): TPromise<T>;
|
||||
}
|
||||
|
||||
export interface IWebWorkerOptions {
|
||||
/**
|
||||
* The AMD moduleId to load.
|
||||
* It should export a function `create` that should return the exported proxy.
|
||||
*/
|
||||
moduleId: string;
|
||||
/**
|
||||
* The data to send over when calling create on the module.
|
||||
*/
|
||||
createData?: any;
|
||||
/**
|
||||
* A label to be used to identify the web worker for debugging purposes.
|
||||
*/
|
||||
label?: string;
|
||||
}
|
||||
|
||||
class MonacoWebWorkerImpl<T> extends EditorWorkerClient implements MonacoWebWorker<T> {
|
||||
|
||||
private _foreignModuleId: string;
|
||||
private _foreignModuleCreateData: any;
|
||||
private _foreignProxy: TPromise<T>;
|
||||
|
||||
constructor(modelService: IModelService, opts: IWebWorkerOptions) {
|
||||
super(modelService, opts.label);
|
||||
this._foreignModuleId = opts.moduleId;
|
||||
this._foreignModuleCreateData = opts.createData || null;
|
||||
this._foreignProxy = null;
|
||||
}
|
||||
|
||||
private _getForeignProxy(): TPromise<T> {
|
||||
if (!this._foreignProxy) {
|
||||
this._foreignProxy = new ShallowCancelThenPromise(this._getProxy().then((proxy) => {
|
||||
return proxy.loadForeignModule(this._foreignModuleId, this._foreignModuleCreateData).then((foreignMethods) => {
|
||||
this._foreignModuleId = null;
|
||||
this._foreignModuleCreateData = null;
|
||||
|
||||
let proxyMethodRequest = (method: string, args: any[]): TPromise<any> => {
|
||||
return proxy.fmr(method, args);
|
||||
};
|
||||
|
||||
let createProxyMethod = (method: string, proxyMethodRequest: (method: string, args: any[]) => TPromise<any>): Function => {
|
||||
return function () {
|
||||
let args = Array.prototype.slice.call(arguments, 0);
|
||||
return proxyMethodRequest(method, args);
|
||||
};
|
||||
};
|
||||
|
||||
let foreignProxy = <T><any>{};
|
||||
for (let i = 0; i < foreignMethods.length; i++) {
|
||||
foreignProxy[foreignMethods[i]] = createProxyMethod(foreignMethods[i], proxyMethodRequest);
|
||||
}
|
||||
|
||||
return foreignProxy;
|
||||
});
|
||||
}));
|
||||
}
|
||||
return this._foreignProxy;
|
||||
}
|
||||
|
||||
public getProxy(): TPromise<T> {
|
||||
return this._getForeignProxy();
|
||||
}
|
||||
|
||||
public withSyncedResources(resources: URI[]): TPromise<T> {
|
||||
return this._withSyncedResources(resources).then(_ => this.getProxy());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user