diff --git a/build/monaco/CHANGELOG.md b/build/monaco/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..038552915e8cb6e098764b25ef085f7deeb5f28b --- /dev/null +++ b/build/monaco/CHANGELOG.md @@ -0,0 +1,7 @@ +# Monaco Editor Change log + +## [0.5.0] + +### Breaking changes +- `createWebWorker` now loads the AMD module and calls `create` and passes in as first argument a context of type `monaco.worker.IWorkerContext` and as second argument the `initData`. This breaking change was needed to allow handling the case of misconfigured web workers (running of a file protocol or a cross-domain case) +- the `CodeActionProvider.provideCodeActions` now gets passed in a `CodeActionContext` that contains the markers at the relevant range. \ No newline at end of file diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 5b4e516d4c6bc18b1143e0f9e3dd447a6c929b26..013d00a0b1f64cd386052bc2103c3d2fe495d921 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -165,9 +165,21 @@ export class SimpleWorkerClient extends Disposable { constructor(workerFactory:IWorkerFactory, moduleId:string) { super(); - this._worker = this._register(workerFactory.create('vs/base/common/worker/simpleWorker', (msg:string) => { - this._protocol.handleMessage(msg); - })); + + let lazyProxyFulfill : (v:T)=>void = null; + let lazyProxyReject: (err:any)=>void = null; + + this._worker = this._register(workerFactory.create( + 'vs/base/common/worker/simpleWorker', + (msg:string) => { + this._protocol.handleMessage(msg); + }, + (err:any) => { + // in Firefox, web workers fail lazily :( + // we will reject the proxy + lazyProxyReject(err); + } + )); this._protocol = new SimpleWorkerProtocol({ sendMessage: (msg:string): void => { @@ -191,9 +203,6 @@ export class SimpleWorkerClient extends Disposable { loaderConfiguration = (window).requirejs.s.contexts._.config; } - let lazyProxyFulfill : (v:T)=>void = null; - let lazyProxyReject: (err:any)=>void = null; - this._lazyProxy = new TPromise((c, e, p) => { lazyProxyFulfill = c; lazyProxyReject = e; diff --git a/src/vs/base/common/worker/workerClient.ts b/src/vs/base/common/worker/workerClient.ts index 450c95c452ec0d6f1c0781f0d4912a3ce42e95ac..cbe5d3ef216a9ee8ae144a235ca7930ae8fb90aa 100644 --- a/src/vs/base/common/worker/workerClient.ts +++ b/src/vs/base/common/worker/workerClient.ts @@ -17,12 +17,21 @@ export interface IWorker { dispose():void; } +let webWorkerWarningLogged = false; +export function logOnceWebWorkerWarning(err:any): void { + if (!webWorkerWarningLogged) { + webWorkerWarningLogged = true; + console.warn('Could not create web worker(s). Falling back to loading web worker code in main thread, which might cause UI freezes. Please see https://github.com/Microsoft/monaco-editor#faq'); + } + console.warn(err.message); +} + export interface IWorkerCallback { (message:string):void; } export interface IWorkerFactory { - create(moduleId:string, callback:IWorkerCallback, onCrashCallback?:()=>void):IWorker; + create(moduleId:string, callback:IWorkerCallback, onErrorCallback:(err:any)=>void):IWorker; } interface IActiveRequest { @@ -59,7 +68,15 @@ export class WorkerClient { this._waitingForWorkerReply = false; this._lastTimerEvent = null; - this._worker = workerFactory.create('vs/base/common/worker/workerServer', (msg) => this._onSerializedMessage(msg)); + this._worker = workerFactory.create( + 'vs/base/common/worker/workerServer', + (msg) => this._onSerializedMessage(msg), + (err) => { + // reject the onModuleLoaded promise, this signals that things are bad + let promiseEntry:IActiveRequest = this._promises[1]; + delete this._promises[1]; + promiseEntry.error(err); + }); let loaderConfiguration:any = null; @@ -80,7 +97,6 @@ export class WorkerClient { loaderConfiguration: loaderConfiguration, GlobalEnvironment: GlobalEnvironment }); - this.onModuleLoaded.then(null, (e) => this._onError('Worker failed to load ' + moduleId, e)); this._remoteCom = new workerProtocol.RemoteCom(this); } diff --git a/src/vs/base/worker/defaultWorkerFactory.ts b/src/vs/base/worker/defaultWorkerFactory.ts index c905f31cfab0964136c6e628fc255d4e0de16e5e..abfa5b9935c02f0826d96b31558b1a94dc0a774b 100644 --- a/src/vs/base/worker/defaultWorkerFactory.ts +++ b/src/vs/base/worker/defaultWorkerFactory.ts @@ -6,7 +6,7 @@ import * as flags from 'vs/base/common/flags'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; -import {IWorker, IWorkerCallback, IWorkerFactory} from 'vs/base/common/worker/workerClient'; +import {logOnceWebWorkerWarning, IWorker, IWorkerCallback, IWorkerFactory} from 'vs/base/common/worker/workerClient'; import * as dom from 'vs/base/browser/dom'; function defaultGetWorkerUrl(workerId:string, label:string): string { @@ -24,13 +24,16 @@ class WebWorker implements IWorker { private id:number; private worker:Worker; - constructor(moduleId:string, id:number, label:string, onMessageCallback:IWorkerCallback) { + constructor(moduleId:string, id:number, label:string, onMessageCallback:IWorkerCallback, onErrorCallback:(err:any)=>void) { this.id = id; this.worker = new Worker(getWorkerUrl('workerMain.js', label)); this.postMessage(moduleId); this.worker.onmessage = function (ev:any) { onMessageCallback(ev.data); }; + if (typeof this.worker.addEventListener === 'function') { + this.worker.addEventListener('error', onErrorCallback); + } } public getId(): number { @@ -125,11 +128,41 @@ export class DefaultWorkerFactory implements IWorkerFactory { private static LAST_WORKER_ID = 0; - public create(moduleId:string, onMessageCallback:IWorkerCallback):IWorker { - var workerId = (++DefaultWorkerFactory.LAST_WORKER_ID); - if (typeof WebWorker !== 'undefined') { - return new WebWorker(moduleId, workerId, 'service' + workerId, onMessageCallback); + private _fallbackToIframe:boolean; + private _webWorkerFailedBeforeError:any; + + constructor(fallbackToIframe:boolean) { + this._fallbackToIframe = fallbackToIframe; + this._webWorkerFailedBeforeError = false; + } + + public create(moduleId:string, onMessageCallback:IWorkerCallback, onErrorCallback:(err:any)=>void):IWorker { + let workerId = (++DefaultWorkerFactory.LAST_WORKER_ID); + if (this._fallbackToIframe) { + if (this._webWorkerFailedBeforeError) { + // Avoid always trying to create web workers if they would just fail... + return new FrameWorker(moduleId, workerId, onMessageCallback); + } + + try { + return new WebWorker(moduleId, workerId, 'service' + workerId, onMessageCallback, (err) => { + logOnceWebWorkerWarning(err); + this._webWorkerFailedBeforeError = err; + onErrorCallback(err); + }); + } catch(err) { + logOnceWebWorkerWarning(err); + return new FrameWorker(moduleId, workerId, onMessageCallback); + } } - return new FrameWorker(moduleId, workerId, onMessageCallback); + + if (this._webWorkerFailedBeforeError) { + throw this._webWorkerFailedBeforeError; + } + return new WebWorker(moduleId, workerId, 'service' + workerId, onMessageCallback, (err) => { + logOnceWebWorkerWarning(err); + this._webWorkerFailedBeforeError = err; + onErrorCallback(err); + }); } -} \ No newline at end of file +} diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index eec9760163f578298528001243f68d49b27fd29f..a517118a1cfb7cfd38109509d01cf3e99730b8de 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -7,6 +7,7 @@ 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} from 'vs/editor/common/core/range'; import {fuzzyContiguousFilter} from 'vs/base/common/filters'; @@ -23,13 +24,37 @@ import {createMonacoBaseAPI} from 'vs/editor/common/standalone/standaloneBase'; export interface IMirrorModel { uri: URI; version: number; - getText(): string; + getValue(): string; +} + +export interface IWorkerContext { + /** + * Get all available mirror models in this worker. + */ + getMirrorModels(): IMirrorModel[]; +} + +/** + * @internal + */ +export interface ICommonModel { + uri: URI; + version: number; + getValue(): string; + + getLinesContent(): string[]; + getLineCount(): number; + getLineContent(lineNumber:number): string; + getWordUntilPosition(position: editorCommon.IPosition, wordDefinition:RegExp): editorCommon.IWordAtPosition; + getAllUniqueWords(wordDefinition:RegExp, skipWordOnce?:string) : string[]; + getValueInRange(range:editorCommon.IRange): string; + getWordAtPosition(position:editorCommon.IPosition, wordDefinition:RegExp): Range; } /** * @internal */ -export class MirrorModel extends MirrorModel2 { +export class MirrorModel extends MirrorModel2 implements ICommonModel { public get uri(): URI { return this._uri; @@ -39,6 +64,10 @@ export class MirrorModel extends MirrorModel2 { return this._versionId; } + public getValue(): string { + return this.getText(); + } + public getLinesContent(): string[] { return this._lines.slice(0); } @@ -146,47 +175,21 @@ export class MirrorModel extends MirrorModel2 { /** * @internal */ -export class EditorSimpleWorkerImpl implements IRequestHandler { - _requestHandlerTrait: any; - - private _models:{[uri:string]:MirrorModel;}; +export abstract class BaseEditorSimpleWorker { private _foreignModule: any; constructor() { - this._models = Object.create(null); this._foreignModule = null; } - public getModels(): MirrorModel[] { - 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.value.lines, data.value.EOL, data.versionId); - } - - public acceptModelChanged(strURL: string, events: editorCommon.IModelContentChangedEvent2[]): void { - if (!this._models[strURL]) { - return; - } - let model = this._models[strURL]; - model.onEvents(events); - } - - public acceptRemovedModel(strURL: string): void { - if (!this._models[strURL]) { - return; - } - delete this._models[strURL]; - } + protected abstract _getModel(uri:string): ICommonModel; + protected abstract _getModels(): ICommonModel[]; // ---- BEGIN diff -------------------------------------------------------------------------- public computeDiff(originalUrl:string, modifiedUrl:string, ignoreTrimWhitespace:boolean): TPromise { - let original = this._models[originalUrl]; - let modified = this._models[modifiedUrl]; + let original = this._getModel(originalUrl); + let modified = this._getModel(modifiedUrl); if (!original || !modified) { return null; } @@ -202,8 +205,8 @@ export class EditorSimpleWorkerImpl implements IRequestHandler { } public computeDirtyDiff(originalUrl:string, modifiedUrl:string, ignoreTrimWhitespace:boolean):TPromise { - let original = this._models[originalUrl]; - let modified = this._models[modifiedUrl]; + let original = this._getModel(originalUrl); + let modified = this._getModel(modifiedUrl); if (!original || !modified) { return null; } @@ -221,7 +224,7 @@ export class EditorSimpleWorkerImpl implements IRequestHandler { // ---- END diff -------------------------------------------------------------------------- public computeLinks(modelUrl:string):TPromise { - let model = this._models[modelUrl]; + let model = this._getModel(modelUrl); if (!model) { return null; } @@ -232,7 +235,7 @@ export class EditorSimpleWorkerImpl implements IRequestHandler { // ---- BEGIN suggest -------------------------------------------------------------------------- public textualSuggest(modelUrl:string, position: editorCommon.IPosition, wordDef:string, wordDefFlags:string): TPromise { - let model = this._models[modelUrl]; + let model = this._getModel(modelUrl); if (!model) { return null; } @@ -240,7 +243,7 @@ export class EditorSimpleWorkerImpl implements IRequestHandler { return TPromise.as(this._suggestFiltered(model, position, new RegExp(wordDef, wordDefFlags))); } - private _suggestFiltered(model:MirrorModel, position: editorCommon.IPosition, wordDefRegExp: RegExp): ISuggestResult[] { + private _suggestFiltered(model:ICommonModel, position: editorCommon.IPosition, wordDefRegExp: RegExp): ISuggestResult[] { let value = this._suggestUnfiltered(model, position, wordDefRegExp); // filter suggestions @@ -251,7 +254,7 @@ export class EditorSimpleWorkerImpl implements IRequestHandler { }]; } - private _suggestUnfiltered(model:MirrorModel, position:editorCommon.IPosition, wordDefRegExp: RegExp): ISuggestResult { + private _suggestUnfiltered(model:ICommonModel, position:editorCommon.IPosition, wordDefRegExp: RegExp): ISuggestResult { let currentWord = model.getWordUntilPosition(position, wordDefRegExp).word; let allWords = model.getAllUniqueWords(wordDefRegExp, currentWord); @@ -275,7 +278,7 @@ export class EditorSimpleWorkerImpl implements IRequestHandler { // ---- END suggest -------------------------------------------------------------------------- public navigateValueSet(modelUrl:string, range:editorCommon.IRange, up:boolean, wordDef:string, wordDefFlags:string): TPromise { - let model = this._models[modelUrl]; + let model = this._getModel(modelUrl); if (!model) { return null; } @@ -304,7 +307,12 @@ export class EditorSimpleWorkerImpl implements IRequestHandler { return new TPromise((c, e) => { // Use the global require to be sure to get the global config (self).require([moduleId], (foreignModule) => { - this._foreignModule = foreignModule.create(createData); + let ctx: IWorkerContext = { + getMirrorModels: ():IMirrorModel[] => { + return this._getModels(); + } + }; + this._foreignModule = foreignModule.create(ctx, createData); let methods: string[] = []; for (let prop in this._foreignModule) { @@ -335,13 +343,51 @@ export class EditorSimpleWorkerImpl implements IRequestHandler { // ---- END foreign module support -------------------------------------------------------------------------- } -const instance = new EditorSimpleWorkerImpl(); - /** - * Get all available mirror models in this worker. + * @internal */ -export function getMirrorModels(): IMirrorModel[] { - return instance.getModels(); +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.value.lines, data.value.EOL, data.versionId); + } + + public acceptModelChanged(strURL: string, events: editorCommon.IModelContentChangedEvent2[]): void { + if (!this._models[strURL]) { + return; + } + let model = this._models[strURL]; + model.onEvents(events); + } + + public acceptRemovedModel(strURL: string): void { + if (!this._models[strURL]) { + return; + } + delete this._models[strURL]; + } } /** @@ -349,15 +395,11 @@ export function getMirrorModels(): IMirrorModel[] { * @internal */ export function create(): IRequestHandler { - return instance; -} - -function createMonacoWorkerAPI(): typeof monaco.worker { - return { - getMirrorModels: getMirrorModels - }; + return new EditorSimpleWorkerImpl(); } var global:any = self; -global.monaco = createMonacoBaseAPI(); -global.monaco.worker = createMonacoWorkerAPI(); +let isWebWorker = (typeof global.importScripts === 'function'); +if (isWebWorker) { + global.monaco = createMonacoBaseAPI(); +} diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index ab2d17e5df33d4d3f46121be103f2106a3dfbc25..f0f799f2015c4f4c6015fce4a1891fde5979b9ff 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {IntervalTimer} from 'vs/base/common/async'; +import {IntervalTimer, ShallowCancelThenPromise} 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'; @@ -16,6 +16,7 @@ import {IInplaceReplaceSupportResult, ILink, ISuggestResult} from 'vs/editor/com 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 {logOnceWebWorkerWarning} from 'vs/base/common/worker/workerClient'; /** * Stop syncing a model to the worker if it was not needed for 1 min. @@ -211,31 +212,67 @@ class EditorModelManager extends Disposable { } } +interface IWorkerClient { + getProxyObject(): TPromise; + dispose(): void; +} + +class SynchronousWorkerClient implements IWorkerClient { + private _instance: T; + private _proxyObj: TPromise; + + 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 { + return new ShallowCancelThenPromise(this._proxyObj); + } +} + export class EditorWorkerClient extends Disposable { private _modelService: IModelService; - private _worker: SimpleWorkerClient; + private _worker: IWorkerClient; + private _workerFactory: DefaultWorkerFactory; private _modelManager: EditorModelManager; constructor(modelService: IModelService) { super(); this._modelService = modelService; + this._workerFactory = new DefaultWorkerFactory(/*do not use iframe*/false); this._worker = null; this._modelManager = null; } - private _getOrCreateWorker(): SimpleWorkerClient { + private _getOrCreateWorker(): IWorkerClient { if (!this._worker) { - this._worker = this._register(new SimpleWorkerClient( - new DefaultWorkerFactory(), - 'vs/editor/common/services/editorSimpleWorker' - )); + try { + this._worker = this._register(new SimpleWorkerClient( + this._workerFactory, + 'vs/editor/common/services/editorSimpleWorker' + )); + } catch (err) { + logOnceWebWorkerWarning(err); + this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl()); + } } return this._worker; } protected _getProxy(): TPromise { - return this._getOrCreateWorker().getProxyObject(); + return this._getOrCreateWorker().getProxyObject().then(null, (err) => { + logOnceWebWorkerWarning(err); + this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl()); + return this._getOrCreateWorker().getProxyObject(); + }); } private _getOrCreateModelManager(proxy: EditorSimpleWorkerImpl): EditorModelManager { diff --git a/src/vs/editor/common/standalone/standaloneBase.ts b/src/vs/editor/common/standalone/standaloneBase.ts index 1ad7a86cdeb5b5989235167d16433fb354d67f53..8827d4a8e83d98659b38b0292149d349ec1fc7c4 100644 --- a/src/vs/editor/common/standalone/standaloneBase.ts +++ b/src/vs/editor/common/standalone/standaloneBase.ts @@ -17,7 +17,6 @@ import URI from 'vs/base/common/uri'; export function createMonacoBaseAPI(): typeof monaco { return { editor: undefined, - worker: undefined, languages: undefined, CancellationTokenSource: CancellationTokenSource, Emitter: Emitter, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index cf69cd9308959c325be4f502410cba76fccfcf3f..02b877a5d5327e62b5697cb1d3a3b7962dda3e6f 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4725,12 +4725,14 @@ declare module monaco.worker { export interface IMirrorModel { uri: Uri; version: number; - getText(): string; + getValue(): string; } - /** - * Get all available mirror models in this worker. - */ - export function getMirrorModels(): IMirrorModel[]; + export interface IWorkerContext { + /** + * Get all available mirror models in this worker. + */ + getMirrorModels(): IMirrorModel[]; + } } \ No newline at end of file diff --git a/src/vs/platform/thread/common/mainThreadService.ts b/src/vs/platform/thread/common/mainThreadService.ts index 8b403bf41cc0c7d9aeb1a37d765850cea705b8a3..c86c927c12442023a2dc15d13a8c7b337a45de65 100644 --- a/src/vs/platform/thread/common/mainThreadService.ts +++ b/src/vs/platform/thread/common/mainThreadService.ts @@ -47,7 +47,7 @@ export class MainThreadService extends abstractThreadService.AbstractThreadServi this._contextService = contextService; this._workerModuleId = workerModuleId; this._defaultWorkerCount = defaultWorkerCount; - this._workerFactory = new DefaultWorkerFactory(); + this._workerFactory = new DefaultWorkerFactory(true); if (!this.isInMainThread) { throw new Error('Incorrect Service usage: this service must be used only in the main thread'); @@ -105,15 +105,11 @@ export class MainThreadService extends abstractThreadService.AbstractThreadServi }); } - private _createWorker(): void { - this._workerPool.push(this._doCreateWorker()); - } - private _shortName(major: string, minor: string): string { return major.substring(major.length - 14) + '.' + minor.substr(0, 14); } - private _doCreateWorker(): Worker.WorkerClient { + private _createWorker(isRetry:boolean = false): void { let worker = new Worker.WorkerClient( this._workerFactory, this._workerModuleId, @@ -131,9 +127,22 @@ export class MainThreadService extends abstractThreadService.AbstractThreadServi configuration: this._contextService.getConfiguration(), options: this._contextService.getOptions() } + }).then(null, (err) => { + for (var i = 0; i < this._workerPool.length; i++) { + if (this._workerPool[i] === worker) { + this._workerPool.splice(i, 1); + break; + } + } + worker.dispose(); + if (isRetry) { + console.warn('Creating the web worker already failed twice. Giving up!'); + } else { + this._createWorker(true); + } }); - return worker; + this._workerPool.push(worker); } private _getWorkerIndex(obj: IThreadSynchronizableObject, affinity: ThreadAffinity): number {