diff --git a/src/vs/editor/common/services/editorSimpleWorkerCommon.ts b/src/vs/editor/common/services/editorSimpleWorkerCommon.ts index 05ea2e8499b17de44ebf6f8d9bcd730a51a703e6..c4c88e2be0a57606f613f3b82ea5af17e965132d 100644 --- a/src/vs/editor/common/services/editorSimpleWorkerCommon.ts +++ b/src/vs/editor/common/services/editorSimpleWorkerCommon.ts @@ -14,7 +14,13 @@ export interface IRawModelData { value:editorCommon.IRawText; } -export abstract class EditorSimpleWorker { +export interface IEditorModelWorker { + acceptNewModel(data: IRawModelData): void; + acceptModelChanged(modelUrl: string, events: editorCommon.IModelContentChangedEvent2[]); + acceptRemovedModel(modelUrl: string): void; +} + +export abstract class EditorSimpleWorker implements IEditorModelWorker { public acceptNewModel(data:IRawModelData): void { throw new Error('Not implemented!'); diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index 79bdd250deb2401738eea839f043f3027c28fa0b..dd2ac39dedc6dc915468352a9d790bb28c04b56d 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -13,7 +13,7 @@ import {DefaultWorkerFactory} from 'vs/base/worker/defaultWorkerFactory'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {WordHelper} from 'vs/editor/common/model/textModelWithTokensHelpers'; import {IInplaceReplaceSupportResult, ILink, ISuggestResult} from 'vs/editor/common/modes'; -import {EditorSimpleWorker} from 'vs/editor/common/services/editorSimpleWorkerCommon'; +import {IEditorModelWorker, EditorSimpleWorker} from 'vs/editor/common/services/editorSimpleWorkerCommon'; import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService'; import {IModelService} from 'vs/editor/common/services/modelService'; @@ -102,28 +102,23 @@ class WorkerManager extends Disposable { } } -class EditorWorkerClient extends Disposable { +export class EditorModelManager extends Disposable { - private _worker: SimpleWorkerClient; - private _proxy: EditorSimpleWorker; - private _modelService:IModelService; - private _syncedModels: {[modelUrl:string]:IDisposable[];}; - private _syncedModelsLastUsedTime: {[modelUrl:string]:number;}; + private _proxy: IEditorModelWorker; + private _modelService: IModelService; + private _syncedModels: { [modelUrl: string]: IDisposable[]; } = Object.create(null); + private _syncedModelsLastUsedTime: { [modelUrl: string]: number; } = Object.create(null); - constructor(modelService:IModelService) { + constructor(proxy: IEditorModelWorker, modelService: IModelService, keepIdleModels: boolean) { super(); + this._proxy = proxy; this._modelService = modelService; - this._syncedModels = Object.create(null); - this._syncedModelsLastUsedTime = Object.create(null); - this._worker = this._register(new SimpleWorkerClient( - new DefaultWorkerFactory(), - 'vs/editor/common/services/editorSimpleWorker', - EditorSimpleWorker - )); - this._proxy = this._worker.get(); - let stopModelSyncInterval = this._register(new IntervalTimer()); - stopModelSyncInterval.cancelAndSet(() => this._checkStopModelSync(), Math.round(STOP_SYNC_MODEL_DELTA_TIME_MS / 2)); + 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 { @@ -135,67 +130,7 @@ class EditorWorkerClient extends Disposable { super.dispose(); } - 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]); - } - } - - public computeDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise { - return this._withSyncedResources([original, modified]).then(_ => { - return this._proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace); - }); - } - - public computeDirtyDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise { - return this._withSyncedResources([original, modified]).then(_ => { - return this._proxy.computeDirtyDiff(original.toString(), modified.toString(), ignoreTrimWhitespace); - }); - } - - public computeLinks(resource:URI):TPromise { - return this._withSyncedResources([resource]).then(_ => { - return this._proxy.computeLinks(resource.toString()); - }); - } - - public textualSuggest(resource: URI, position: editorCommon.IPosition): TPromise { - return this._withSyncedResources([resource]).then(_ => { - let model = this._modelService.getModel(resource); - if (!model) { - return null; - } - let wordDefRegExp = WordHelper.massageWordDefinitionOf(model.getMode()); - let wordDef = wordDefRegExp.source; - let wordDefFlags = (wordDefRegExp.global ? 'g' : '') + (wordDefRegExp.ignoreCase ? 'i' : '') + (wordDefRegExp.multiline ? 'm' : ''); - return this._proxy.textualSuggest(resource.toString(), position, wordDef, wordDefFlags); - }); - } - - public navigateValueSet(resource: URI, range:editorCommon.IRange, up:boolean): TPromise { - return this._withSyncedResources([resource]).then(_ => { - let model = this._modelService.getModel(resource); - if (!model) { - return null; - } - let wordDefRegExp = WordHelper.massageWordDefinitionOf(model.getMode()); - let wordDef = wordDefRegExp.source; - let wordDefFlags = (wordDefRegExp.global ? 'g' : '') + (wordDefRegExp.ignoreCase ? 'i' : '') + (wordDefRegExp.multiline ? 'm' : ''); - return this._proxy.navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags); - }); - } - - private _withSyncedResources(resources:URI[]): TPromise { + public withSyncedResources(resources:URI[]): TPromise { for (let i = 0; i < resources.length; i++) { let resource = resources[i]; @@ -212,6 +147,22 @@ class EditorWorkerClient extends Disposable { return TPromise.as(null); } + 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 modelUrl = resource.toString(); let model = this._modelService.getModel(resource); @@ -262,3 +213,67 @@ class EditorWorkerClient extends Disposable { disposeAll(toDispose); } } + +class EditorWorkerClient extends Disposable { + + private _modelService: IModelService; + private _worker: SimpleWorkerClient; + private _proxy: EditorSimpleWorker; + private _modelManager: EditorModelManager; + + constructor(modelService: IModelService) { + super(); + this._modelService = modelService; + this._worker = this._register(new SimpleWorkerClient( + new DefaultWorkerFactory(), + 'vs/editor/common/services/editorSimpleWorker', + EditorSimpleWorker + )); + this._proxy = this._worker.get(); + this._modelManager = new EditorModelManager(this._proxy, this._modelService, false); + } + + public computeDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise { + return this._modelManager.withSyncedResources([original, modified]).then(_ => { + return this._proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace); + }); + } + + public computeDirtyDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise { + return this._modelManager.withSyncedResources([original, modified]).then(_ => { + return this._proxy.computeDirtyDiff(original.toString(), modified.toString(), ignoreTrimWhitespace); + }); + } + + public computeLinks(resource:URI):TPromise { + return this._modelManager.withSyncedResources([resource]).then(_ => { + return this._proxy.computeLinks(resource.toString()); + }); + } + + public textualSuggest(resource: URI, position: editorCommon.IPosition): TPromise { + return this._modelManager.withSyncedResources([resource]).then(_ => { + let model = this._modelService.getModel(resource); + if (!model) { + return null; + } + let wordDefRegExp = WordHelper.massageWordDefinitionOf(model.getMode()); + let wordDef = wordDefRegExp.source; + let wordDefFlags = (wordDefRegExp.global ? 'g' : '') + (wordDefRegExp.ignoreCase ? 'i' : '') + (wordDefRegExp.multiline ? 'm' : ''); + return this._proxy.textualSuggest(resource.toString(), position, wordDef, wordDefFlags); + }); + } + + public navigateValueSet(resource: URI, range:editorCommon.IRange, up:boolean): TPromise { + return this._modelManager.withSyncedResources([resource]).then(_ => { + let model = this._modelService.getModel(resource); + if (!model) { + return null; + } + let wordDefRegExp = WordHelper.massageWordDefinitionOf(model.getMode()); + let wordDef = wordDefRegExp.source; + let wordDefFlags = (wordDefRegExp.global ? 'g' : '') + (wordDefRegExp.ignoreCase ? 'i' : '') + (wordDefRegExp.multiline ? 'm' : ''); + return this._proxy.navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags); + }); + } +} diff --git a/src/vs/languages/typescript/common/languageFeatures.ts b/src/vs/languages/typescript/common/languageFeatures.ts index 92ee233d8068e819b51a4e68e40f891e70e6d4d8..1e4b00a7715b4ebcd2f6bbe7bfab9fc456e12ca3 100644 --- a/src/vs/languages/typescript/common/languageFeatures.ts +++ b/src/vs/languages/typescript/common/languageFeatures.ts @@ -19,7 +19,7 @@ import {ExtraInfoRegistry} from 'vs/editor/contrib/hover/common/hover'; import {ReferenceRegistry} from 'vs/editor/contrib/referenceSearch/common/referenceSearch'; import {DeclarationRegistry} from 'vs/editor/contrib/goToDeclaration/common/goToDeclaration'; -export default function registerLanguageFeatures(selector: string, modelService: IModelService, worker: () => TPromise): lifecycle.IDisposable { +export default function registerLanguageFeatures(selector: string, modelService: IModelService, worker: (first:URI, ...more:URI[]) => TPromise): lifecycle.IDisposable { const disposables: lifecycle.IDisposable[] = []; disposables.push(SuggestRegistry.register(selector, new SuggestAdapter(modelService, worker))); disposables.push(ParameterHintsRegistry.register(selector, new ParameterHintsAdapter(modelService, worker))); @@ -32,7 +32,7 @@ export default function registerLanguageFeatures(selector: string, modelService: abstract class Adapter { - constructor(protected _modelService: IModelService, protected _worker: () => TPromise) { + constructor(protected _modelService: IModelService, protected _worker: (first:URI, ...more:URI[]) => TPromise) { } @@ -78,7 +78,7 @@ class SuggestAdapter extends Adapter implements modes.ISuggestSupport { const wordInfo = model.getWordUntilPosition(position); const offset = this._positionToOffset(resource, position); - return this._worker().then(worker => { + return this._worker(resource).then(worker => { return worker.getCompletionsAtPosition(resource.toString(), offset); }).then(info => { if (!info) { @@ -101,7 +101,7 @@ class SuggestAdapter extends Adapter implements modes.ISuggestSupport { getSuggestionDetails(resource: URI, position: editor.IPosition, suggestion: modes.ISuggestion) { - return this._worker().then(worker => { + return this._worker(resource).then(worker => { return worker.getCompletionEntryDetails(resource.toString(), this._positionToOffset(resource, position), suggestion.label); @@ -145,7 +145,7 @@ class ParameterHintsAdapter extends Adapter implements modes.IParameterHintsSupp return true; } getParameterHints(resource: URI, position: editor.IPosition, triggerCharacter?: string): TPromise { - return this._worker().then(worker => worker.getSignatureHelpItems(resource.toString(), this._positionToOffset(resource, position))).then(info => { + return this._worker(resource).then(worker => worker.getSignatureHelpItems(resource.toString(), this._positionToOffset(resource, position))).then(info => { if (!info) { return; @@ -195,7 +195,7 @@ class ParameterHintsAdapter extends Adapter implements modes.IParameterHintsSupp class QuickInfoAdapter extends Adapter implements modes.IExtraInfoSupport { computeInfo(resource: URI, position: editor.IPosition): TPromise { - return this._worker().then(worker => { + return this._worker(resource).then(worker => { return worker.getQuickInfoAtPosition(resource.toString(), this._positionToOffset(resource, position)); }).then(info => { if (!info) { @@ -214,7 +214,7 @@ class QuickInfoAdapter extends Adapter implements modes.IExtraInfoSupport { class OccurrencesAdapter extends Adapter implements modes.IOccurrencesSupport { findOccurrences(resource: URI, position: editor.IPosition, strict?: boolean): TPromise { - return this._worker().then(worker => { + return this._worker(resource).then(worker => { return worker.getOccurrencesAtPosition(resource.toString(), this._positionToOffset(resource, position)); }).then(entries => { if (!entries) { @@ -239,7 +239,7 @@ class DeclarationAdapter extends Adapter implements modes.IDeclarationSupport { } findDeclaration(resource: URI, position: editor.IPosition): TPromise { - return this._worker().then(worker => { + return this._worker(resource).then(worker => { return worker.getDefinitionAtPosition(resource.toString(), this._positionToOffset(resource, position)); }).then(entries => { if (!entries) { @@ -268,12 +268,8 @@ class ReferenceAdapter extends Adapter implements modes.IReferenceSupport { return true; } - /** - * @returns a list of reference of the symbol at the position in the - * given resource. - */ findReferences(resource: URI, position: editor.IPosition, includeDeclaration: boolean): TPromise { - return this._worker().then(worker => { + return this._worker(resource).then(worker => { return worker.getReferencesAtPosition(resource.toString(), this._positionToOffset(resource, position)); }).then(entries => { if (!entries) { diff --git a/src/vs/languages/typescript/common/worker/workerManager.ts b/src/vs/languages/typescript/common/worker/workerManager.ts index 632d920ce94cf5d912a236dab352a40ad6dcce58..6ae2092268928fcf0fd54d030cab252c384ade58 100644 --- a/src/vs/languages/typescript/common/worker/workerManager.ts +++ b/src/vs/languages/typescript/common/worker/workerManager.ts @@ -4,20 +4,21 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import URI from 'vs/base/common/uri'; import {TPromise} from 'vs/base/common/winjs.base'; import {IDisposable, disposeAll} from 'vs/base/common/lifecycle'; import {DefaultWorkerFactory} from 'vs/base/worker/defaultWorkerFactory'; import {SimpleWorkerClient} from 'vs/base/common/worker/simpleWorker'; import {IModelService} from 'vs/editor/common/services/modelService'; +import {EditorModelManager} from 'vs/editor/common/services/editorWorkerServiceImpl'; import {Defaults} from '../typescript'; import registerLanguageFeatures from '../languageFeatures'; import AbstractWorker from './worker'; - class Client { - private _client: TPromise = null; - private _clientDispose: IDisposable[]; + private _client: TPromise<{ worker: AbstractWorker; manager: EditorModelManager }> = null; + private _clientDispose: IDisposable[] = []; private _factory = new DefaultWorkerFactory(); private _modelService: IModelService; @@ -25,18 +26,17 @@ class Client { this._modelService = modelService; } - private _createClient(): TPromise { - this._clientDispose = []; + private _createClient(): TPromise<{ worker: AbstractWorker; manager: EditorModelManager; }> { + + const client = new SimpleWorkerClient(this._factory, 'vs/languages/typescript/common/worker/workerImpl', AbstractWorker); + const manager = new EditorModelManager(client.get(), this._modelService, true); - let client = new SimpleWorkerClient(this._factory, - 'vs/languages/typescript/common/worker/workerImpl', - AbstractWorker); + this._clientDispose.push(manager); this._clientDispose.push(client); const stopWorker = () => { this._clientDispose = disposeAll(this._clientDispose); this._client = null; - client = null; }; // stop worker after being idle @@ -50,47 +50,34 @@ class Client { // stop worker when defaults change this._clientDispose.push(Defaults.onDidChange(() => stopWorker())); - // send default to worker right away const worker = client.get(); const {compilerOptions, extraLibs} = Defaults; - return worker.acceptDefaults(compilerOptions, extraLibs).then(() => worker); + return worker.acceptDefaults(compilerOptions, extraLibs).then(() =>({ worker, manager })); } dispose(): void { this._clientDispose = disposeAll(this._clientDispose); + this._client = null; } - get(): TPromise { + get(resources:URI[]): TPromise { if (!this._client) { this._client = this._createClient(); } - // TODO@joh use proper model sync - return this._client.then(worker => { - let promises = this._modelService.getModels().map(model => { - return TPromise.as(worker.acceptNewModel({ - url: model.getAssociatedResource().toString(), - versionId: model.getVersionId(), - value: { - EOL: model.getEOL(), - lines: model.getLinesContent(), - length: model.getValueLength(), - BOM: undefined, - options: undefined - } - })); - }); - - return TPromise.join(promises).then(() => worker); - }); + return this._client + .then(data => data.manager.withSyncedResources(resources) + .then(_ => data.worker)); } } export function create(selector: string, modelService: IModelService) { const client = new Client(modelService); - const registration = registerLanguageFeatures(selector, modelService, () => client.get()); + const registration = registerLanguageFeatures(selector, + modelService, + (first: URI, ...more: URI[]) => client.get([first].concat(more))); return { dispose() {