SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View 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];
}
}

View 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
};
}

View 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;
}

View 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();
}

View 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>;
}

View 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);
});
}
}

View 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;
}
}

View 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>;
}

View 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];
}
}

View 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; }>;
}

View 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 });
}
}
}
}

View 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;
}

View 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;
}

View 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);
}
}

View 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());
}
}