diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index a6eb3b7128f..ef5c2e39ae5 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -92,7 +92,7 @@ declare namespace monaco.editor { #includeAll(vs/editor/standalone/browser/standaloneEditor;languages.Token=>Token): #include(vs/editor/standalone/common/standaloneTheme): BuiltinTheme, IStandaloneThemeData, IColors #include(vs/editor/common/languages/supports/tokenization): ITokenThemeRule -#include(vs/editor/browser/services/webWorker): MonacoWebWorker, IWebWorkerOptions +#include(vs/editor/standalone/browser/standaloneWebWorker): MonacoWebWorker, IWebWorkerOptions #include(vs/editor/standalone/browser/standaloneCodeEditor): IActionDescriptor, IGlobalEditorOptions, IStandaloneEditorConstructionOptions, IStandaloneDiffEditorConstructionOptions, IStandaloneCodeEditor, IStandaloneDiffEditor export interface ICommandHandler { (...args: any[]): void; diff --git a/src/buildfile.js b/src/buildfile.js index f3341b51c66..a9d1f4c9a1b 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -26,17 +26,14 @@ function createModuleDescription(name, exclude) { /** * @param {string} name - * @param {boolean?} noEsmSuffix */ -function createEditorWorkerModuleDescription(name, noEsmSuffix) { +function createEditorWorkerModuleDescription(name) { const amdVariant = createModuleDescription(name, ['vs/base/common/worker/simpleWorker', 'vs/editor/common/services/editorSimpleWorker']); amdVariant.target = 'amd'; const esmVariant = { ...amdVariant, dest: undefined }; esmVariant.target = 'esm'; - if (!noEsmSuffix) { - esmVariant.name = `${esmVariant.name}.esm`; - } + esmVariant.name = `${esmVariant.name}.esm`; return [amdVariant, esmVariant]; } @@ -68,8 +65,8 @@ exports.workerNotebook = createEditorWorkerModuleDescription('vs/workbench/contr exports.workerLanguageDetection = createEditorWorkerModuleDescription('vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker'); exports.workerLocalFileSearch = createEditorWorkerModuleDescription('vs/workbench/services/search/worker/localFileSearch'); exports.workerProfileAnalysis = createEditorWorkerModuleDescription('vs/platform/profiling/electron-sandbox/profileAnalysisWorker'); -exports.workerOutputLinks = createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer', true); -exports.workerBackgroundTokenization = createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker', true); +exports.workerOutputLinks = createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'); +exports.workerBackgroundTokenization = createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'); exports.workbenchDesktop = function () { return isESM() ? [ @@ -80,8 +77,8 @@ exports.workbenchDesktop = function () { createModuleDescription('vs/workbench/contrib/issue/electron-sandbox/issueReporterMain'), createModuleDescription('vs/workbench/workbench.desktop.main') ] : [ - ...createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer', true), - ...createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker', true), + ...createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'), + ...createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'), createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp'), createModuleDescription('vs/platform/files/node/watcher/watcherMain'), createModuleDescription('vs/platform/terminal/node/ptyHostMain'), @@ -94,8 +91,8 @@ exports.workbenchWeb = function () { return isESM() ? [ createModuleDescription('vs/workbench/workbench.web.main') ] : [ - ...createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer', true), - ...createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker', true), + ...createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'), + ...createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'), createModuleDescription('vs/code/browser/workbench/workbench', ['vs/workbench/workbench.web.main']) ]; }; diff --git a/src/vs/base/browser/defaultWorkerFactory.ts b/src/vs/base/browser/defaultWorkerFactory.ts index 3b9bed26884..ea1a5bfcecd 100644 --- a/src/vs/base/browser/defaultWorkerFactory.ts +++ b/src/vs/base/browser/defaultWorkerFactory.ts @@ -7,7 +7,7 @@ import { createTrustedTypesPolicy } from 'vs/base/browser/trustedTypes'; import { onUnexpectedError } from 'vs/base/common/errors'; import { AppResourcePath, COI, FileAccess } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { IWorker, IWorkerCallback, IWorkerFactory, logOnceWebWorkerWarning } from 'vs/base/common/worker/simpleWorker'; +import { IWorker, IWorkerCallback, IWorkerClient, IWorkerDescriptor, IWorkerFactory, logOnceWebWorkerWarning, SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { coalesce } from 'vs/base/common/arrays'; @@ -35,7 +35,7 @@ export function createBlobWorker(blobUrl: string, options?: WorkerOptions): Work return new Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) as unknown as string : blobUrl, { ...options, type: isESM ? 'module' : undefined }); } -function getWorker(workerMainLocation: URI | undefined, label: string): Worker | Promise { +function getWorker(esmWorkerLocation: URI | undefined, label: string): Worker | Promise { // Option for hosts to overwrite the worker script (used in the standalone editor) interface IMonacoEnvironment { getWorker?(moduleId: string, label: string): Worker | Promise; @@ -60,8 +60,8 @@ function getWorker(workerMainLocation: URI | undefined, label: string): Worker | return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: isESM ? 'module' : undefined }); } // ESM-comment-end - if (workerMainLocation) { - const workerUrl = getWorkerBootstrapUrl(label, workerMainLocation.toString(true)); + if (esmWorkerLocation) { + const workerUrl = getWorkerBootstrapUrl(label, esmWorkerLocation.toString(true)); const worker = new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: isESM ? 'module' : undefined }); if (isESM) { return whenESMWorkerReady(worker); @@ -136,17 +136,17 @@ class WebWorker extends Disposable implements IWorker { private readonly label: string; private worker: Promise | null; - constructor(workerMainLocation: URI | undefined, moduleId: string, id: number, label: string, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void) { + constructor(esmWorkerLocation: URI | undefined, amdModuleId: string, id: number, label: string, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void) { super(); this.id = id; this.label = label; - const workerOrPromise = getWorker(workerMainLocation, label); + const workerOrPromise = getWorker(esmWorkerLocation, label); if (isPromiseLike(workerOrPromise)) { this.worker = workerOrPromise; } else { this.worker = Promise.resolve(workerOrPromise); } - this.postMessage(moduleId, []); + this.postMessage(amdModuleId, []); this.worker.then((w) => { w.onmessage = function (ev) { onMessageCallback(ev.data); @@ -183,36 +183,45 @@ class WebWorker extends Disposable implements IWorker { } } -export class DefaultWorkerFactory implements IWorkerFactory { +export class WorkerDescriptor implements IWorkerDescriptor { + + public readonly esmModuleLocation: URI | undefined; + + constructor( + public readonly amdModuleId: string, + readonly label: string | undefined, + ) { + this.esmModuleLocation = (isESM ? FileAccess.asBrowserUri(`${amdModuleId}.esm.js` as AppResourcePath) : undefined); + } +} + +class DefaultWorkerFactory implements IWorkerFactory { private static LAST_WORKER_ID = 0; - - private _label: string | undefined; private _webWorkerFailedBeforeError: any; - constructor(private readonly workerMainLocation: URI | undefined, label: string | undefined) { - this._label = label; + constructor() { this._webWorkerFailedBeforeError = false; } - public create(modules: { moduleId: string; esmModuleId: string }, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker { + public create(desc: IWorkerDescriptor, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker { const workerId = (++DefaultWorkerFactory.LAST_WORKER_ID); if (this._webWorkerFailedBeforeError) { throw this._webWorkerFailedBeforeError; } - let workerMainLocation = this.workerMainLocation; - const moduleId = modules.moduleId; - - if (isESM) { - workerMainLocation = FileAccess.asBrowserUri(`${modules.esmModuleId}.esm.js` as AppResourcePath); - } - - return new WebWorker(workerMainLocation, moduleId, workerId, this._label || 'anonymous' + workerId, onMessageCallback, (err) => { + return new WebWorker(desc.esmModuleLocation, desc.amdModuleId, workerId, desc.label || 'anonymous' + workerId, onMessageCallback, (err) => { logOnceWebWorkerWarning(err); this._webWorkerFailedBeforeError = err; onErrorCallback(err); }); } } + +export function createWebWorker(amdModuleId: string, label: string | undefined): IWorkerClient; +export function createWebWorker(workerDescriptor: IWorkerDescriptor): IWorkerClient; +export function createWebWorker(arg0: string | IWorkerDescriptor, arg1?: string | undefined): IWorkerClient { + const workerDescriptor = (typeof arg0 === 'string' ? new WorkerDescriptor(arg0, arg1) : arg0); + return new SimpleWorkerClient(new DefaultWorkerFactory(), workerDescriptor); +} diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 81d4e55e6d5..cf19e69171a 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { transformErrorForSerialization } from 'vs/base/common/errors'; +import { CharCode } from 'vs/base/common/charCode'; +import { onUnexpectedError, transformErrorForSerialization } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { AppResourcePath, FileAccess } from 'vs/base/common/network'; -import { getAllMethodNames } from 'vs/base/common/objects'; import { isWeb } from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; +import { URI } from 'vs/base/common/uri'; // ESM-comment-begin const isESM = false; @@ -18,6 +19,7 @@ const isESM = false; // const isESM = true; // ESM-uncomment-end +const DEFAULT_CHANNEL = 'default'; const INITIALIZE = '$initialize'; export interface IWorker extends IDisposable { @@ -30,7 +32,13 @@ export interface IWorkerCallback { } export interface IWorkerFactory { - create(modules: { moduleId: string; esmModuleId: string }, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker; + create(modules: IWorkerDescriptor, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker; +} + +export interface IWorkerDescriptor { + readonly amdModuleId: string; + readonly esmModuleLocation: URI | undefined; + readonly label: string | undefined; } let webWorkerWarningLogged = false; @@ -58,6 +66,7 @@ class RequestMessage { constructor( public readonly vsWorker: number, public readonly req: string, + public readonly channel: string, public readonly method: string, public readonly args: any[] ) { } @@ -76,6 +85,7 @@ class SubscribeEventMessage { constructor( public readonly vsWorker: number, public readonly req: string, + public readonly channel: string, public readonly eventName: string, public readonly arg: any ) { } @@ -104,8 +114,8 @@ interface IMessageReply { interface IMessageHandler { sendMessage(msg: any, transfer?: ArrayBuffer[]): void; - handleMessage(method: string, args: any[]): Promise; - handleEvent(eventName: string, arg: any): Event; + handleMessage(channel: string, method: string, args: any[]): Promise; + handleEvent(channel: string, eventName: string, arg: any): Event; } class SimpleWorkerProtocol { @@ -130,24 +140,24 @@ class SimpleWorkerProtocol { this._workerId = workerId; } - public sendMessage(method: string, args: any[]): Promise { + public sendMessage(channel: string, method: string, args: any[]): Promise { const req = String(++this._lastSentReq); return new Promise((resolve, reject) => { this._pendingReplies[req] = { resolve: resolve, reject: reject }; - this._send(new RequestMessage(this._workerId, req, method, args)); + this._send(new RequestMessage(this._workerId, req, channel, method, args)); }); } - public listen(eventName: string, arg: any): Event { + public listen(channel: string, eventName: string, arg: any): Event { let req: string | null = null; const emitter = new Emitter({ onWillAddFirstListener: () => { req = String(++this._lastSentReq); this._pendingEmitters.set(req, emitter); - this._send(new SubscribeEventMessage(this._workerId, req, eventName, arg)); + this._send(new SubscribeEventMessage(this._workerId, req, channel, eventName, arg)); }, onDidRemoveLastListener: () => { this._pendingEmitters.delete(req!); @@ -168,6 +178,29 @@ class SimpleWorkerProtocol { this._handleMessage(message); } + public createProxyToRemoteChannel(channel: string, sendMessageBarrier?: () => Promise): T { + const handler = { + get: (target: any, name: PropertyKey) => { + if (typeof name === 'string' && !target[name]) { + if (propertyIsDynamicEvent(name)) { // onDynamic... + target[name] = (arg: any): Event => { + return this.listen(channel, name, arg); + }; + } else if (propertyIsEvent(name)) { // on... + target[name] = this.listen(channel, name, undefined); + } else if (name.charCodeAt(0) === CharCode.DollarSign) { // $... + target[name] = async (...myArgs: any[]) => { + await sendMessageBarrier?.(); + return this.sendMessage(channel, name, myArgs); + }; + } + } + return target[name]; + } + }; + return new Proxy(Object.create(null), handler); + } + private _handleMessage(msg: Message): void { switch (msg.type) { case MessageType.Reply: @@ -209,7 +242,7 @@ class SimpleWorkerProtocol { private _handleRequestMessage(requestMessage: RequestMessage): void { const req = requestMessage.req; - const result = this._handler.handleMessage(requestMessage.method, requestMessage.args); + const result = this._handler.handleMessage(requestMessage.channel, requestMessage.method, requestMessage.args); result.then((r) => { this._send(new ReplyMessage(this._workerId, req, r, undefined)); }, (e) => { @@ -223,7 +256,7 @@ class SimpleWorkerProtocol { private _handleSubscribeEventMessage(msg: SubscribeEventMessage): void { const req = msg.req; - const disposable = this._handler.handleEvent(msg.eventName, msg.arg)((event) => { + const disposable = this._handler.handleEvent(msg.channel, msg.eventName, msg.arg)((event) => { this._send(new EventMessage(this._workerId, req, event)); }); this._pendingEvents.set(req, disposable); @@ -263,35 +296,60 @@ class SimpleWorkerProtocol { } } +type ProxiedMethodName = (`$${string}` | `on${string}`); + +export type Proxied = { [K in keyof T]: T[K] extends (...args: infer A) => infer R + ? ( + K extends ProxiedMethodName + ? (...args: A) => Promise> + : never + ) + : never +}; + export interface IWorkerClient { - getProxyObject(): Promise; + proxy: Proxied; dispose(): void; + setChannel(channel: string, handler: T): void; + getChannel(channel: string): Proxied; +} + +export interface IWorkerServer { + setChannel(channel: string, handler: T): void; + getChannel(channel: string): Proxied; } /** * Main thread side */ -export class SimpleWorkerClient extends Disposable implements IWorkerClient { +export class SimpleWorkerClient extends Disposable implements IWorkerClient { private readonly _worker: IWorker; - private readonly _onModuleLoaded: Promise; + private readonly _onModuleLoaded: Promise; private readonly _protocol: SimpleWorkerProtocol; - private readonly _lazyProxy: Promise; + public readonly proxy: Proxied; + private readonly _localChannels: Map = new Map(); + private readonly _remoteChannels: Map = new Map(); - constructor(workerFactory: IWorkerFactory, moduleId: string, host: H) { + constructor( + workerFactory: IWorkerFactory, + workerDescriptor: IWorkerDescriptor, + ) { super(); - let lazyProxyReject: ((err: any) => void) | null = null; - this._worker = this._register(workerFactory.create( - { moduleId: 'vs/base/common/worker/simpleWorker', esmModuleId: moduleId }, + { + amdModuleId: 'vs/base/common/worker/simpleWorker', + esmModuleLocation: workerDescriptor.esmModuleLocation, + label: workerDescriptor.label + }, (msg: Message) => { this._protocol.handleMessage(msg); }, (err: any) => { // in Firefox, web workers fail lazily :( // we will reject the proxy - lazyProxyReject?.(err); + onUnexpectedError(err); } )); @@ -299,33 +357,11 @@ export class SimpleWorkerClient extends Disp sendMessage: (msg: any, transfer: ArrayBuffer[]): void => { this._worker.postMessage(msg, transfer); }, - handleMessage: (method: string, args: any[]): Promise => { - if (typeof (host as any)[method] !== 'function') { - return Promise.reject(new Error('Missing method ' + method + ' on main thread host.')); - } - - try { - return Promise.resolve((host as any)[method].apply(host, args)); - } catch (e) { - return Promise.reject(e); - } + handleMessage: (channel: string, method: string, args: any[]): Promise => { + return this._handleMessage(channel, method, args); }, - handleEvent: (eventName: string, arg: any): Event => { - if (propertyIsDynamicEvent(eventName)) { - const event = (host as any)[eventName].call(host, arg); - if (typeof event !== 'function') { - throw new Error(`Missing dynamic event ${eventName} on main thread host.`); - } - return event; - } - if (propertyIsEvent(eventName)) { - const event = (host as any)[eventName]; - if (typeof event !== 'function') { - throw new Error(`Missing event ${eventName} on main thread host.`); - } - return event; - } - throw new Error(`Malformed event name ${eventName}`); + handleEvent: (channel: string, eventName: string, arg: any): Event => { + return this._handleEvent(channel, eventName, arg); } }); this._protocol.setWorkerId(this._worker.getId()); @@ -342,45 +378,67 @@ export class SimpleWorkerClient extends Disp loaderConfiguration = (globalThis as any).requirejs.s.contexts._.config; } - const hostMethods = getAllMethodNames(host); - // Send initialize message - this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [ + this._onModuleLoaded = this._protocol.sendMessage(DEFAULT_CHANNEL, INITIALIZE, [ this._worker.getId(), JSON.parse(JSON.stringify(loaderConfiguration)), - moduleId, - hostMethods, + workerDescriptor.amdModuleId, ]); - // Create proxy to loaded code - const proxyMethodRequest = (method: string, args: any[]): Promise => { - return this._request(method, args); - }; - const proxyListen = (eventName: string, arg: any): Event => { - return this._protocol.listen(eventName, arg); - }; - - this._lazyProxy = new Promise((resolve, reject) => { - lazyProxyReject = reject; - this._onModuleLoaded.then((availableMethods: string[]) => { - resolve(createProxyObject(availableMethods, proxyMethodRequest, proxyListen)); - }, (e) => { - reject(e); - this._onError('Worker failed to load ' + moduleId, e); - }); + this.proxy = this._protocol.createProxyToRemoteChannel(DEFAULT_CHANNEL, async () => { await this._onModuleLoaded; }); + this._onModuleLoaded.catch((e) => { + this._onError('Worker failed to load ' + workerDescriptor.amdModuleId, e); }); } - public getProxyObject(): Promise { - return this._lazyProxy; + private _handleMessage(channelName: string, method: string, args: any[]): Promise { + const channel: object | undefined = this._localChannels.get(channelName); + if (!channel) { + return Promise.reject(new Error(`Missing channel ${channelName} on main thread`)); + } + if (typeof (channel as any)[method] !== 'function') { + return Promise.reject(new Error(`Missing method ${method} on main thread channel ${channelName}`)); + } + + try { + return Promise.resolve((channel as any)[method].apply(channel, args)); + } catch (e) { + return Promise.reject(e); + } } - private _request(method: string, args: any[]): Promise { - return new Promise((resolve, reject) => { - this._onModuleLoaded.then(() => { - this._protocol.sendMessage(method, args).then(resolve, reject); - }, reject); - }); + private _handleEvent(channelName: string, eventName: string, arg: any): Event { + const channel: object | undefined = this._localChannels.get(channelName); + if (!channel) { + throw new Error(`Missing channel ${channelName} on main thread`); + } + if (propertyIsDynamicEvent(eventName)) { + const event = (channel as any)[eventName].call(channel, arg); + if (typeof event !== 'function') { + throw new Error(`Missing dynamic event ${eventName} on main thread channel ${channelName}.`); + } + return event; + } + if (propertyIsEvent(eventName)) { + const event = (channel as any)[eventName]; + if (typeof event !== 'function') { + throw new Error(`Missing event ${eventName} on main thread channel ${channelName}.`); + } + return event; + } + throw new Error(`Malformed event name ${eventName}`); + } + + public setChannel(channel: string, handler: T): void { + this._localChannels.set(channel, handler); + } + + public getChannel(channel: string): Proxied { + if (!this._remoteChannels.has(channel)) { + const inst = this._protocol.createProxyToRemoteChannel(channel, async () => { await this._onModuleLoaded; }); + this._remoteChannels.set(channel, inst); + } + return this._remoteChannels.get(channel) as Proxied; } private _onError(message: string, error?: any): void { @@ -399,65 +457,35 @@ function propertyIsDynamicEvent(name: string): boolean { return /^onDynamic/.test(name) && strings.isUpperAsciiLetter(name.charCodeAt(9)); } -function createProxyObject( - methodNames: string[], - invoke: (method: string, args: unknown[]) => unknown, - proxyListen: (eventName: string, arg: any) => Event -): T { - const createProxyMethod = (method: string): () => unknown => { - return function () { - const args = Array.prototype.slice.call(arguments, 0); - return invoke(method, args); - }; - }; - const createProxyDynamicEvent = (eventName: string): (arg: any) => Event => { - return function (arg) { - return proxyListen(eventName, arg); - }; - }; - - const result = {} as T; - for (const methodName of methodNames) { - if (propertyIsDynamicEvent(methodName)) { - (result)[methodName] = createProxyDynamicEvent(methodName); - continue; - } - if (propertyIsEvent(methodName)) { - (result)[methodName] = proxyListen(methodName, undefined); - continue; - } - (result)[methodName] = createProxyMethod(methodName); - } - return result; -} - export interface IRequestHandler { _requestHandlerBrand: any; [prop: string]: any; } -export interface IRequestHandlerFactory { - (host: H): IRequestHandler; +export interface IRequestHandlerFactory { + (workerServer: IWorkerServer): IRequestHandler; } /** * Worker side */ -export class SimpleWorkerServer { +export class SimpleWorkerServer implements IWorkerServer { - private _requestHandlerFactory: IRequestHandlerFactory | null; + private _requestHandlerFactory: IRequestHandlerFactory | null; private _requestHandler: IRequestHandler | null; private _protocol: SimpleWorkerProtocol; + private readonly _localChannels: Map = new Map(); + private readonly _remoteChannels: Map = new Map(); - constructor(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void, requestHandlerFactory: IRequestHandlerFactory | null) { + constructor(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void, requestHandlerFactory: IRequestHandlerFactory | null) { this._requestHandlerFactory = requestHandlerFactory; this._requestHandler = null; this._protocol = new SimpleWorkerProtocol({ sendMessage: (msg: any, transfer: ArrayBuffer[]): void => { postMessage(msg, transfer); }, - handleMessage: (method: string, args: any[]): Promise => this._handleMessage(method, args), - handleEvent: (eventName: string, arg: any): Event => this._handleEvent(eventName, arg) + handleMessage: (channel: string, method: string, args: any[]): Promise => this._handleMessage(channel, method, args), + handleEvent: (channel: string, eventName: string, arg: any): Event => this._handleEvent(channel, eventName, arg) }); } @@ -465,35 +493,40 @@ export class SimpleWorkerServer { this._protocol.handleMessage(msg); } - private _handleMessage(method: string, args: any[]): Promise { - if (method === INITIALIZE) { - return this.initialize(args[0], args[1], args[2], args[3]); + private _handleMessage(channel: string, method: string, args: any[]): Promise { + if (channel === DEFAULT_CHANNEL && method === INITIALIZE) { + return this.initialize(args[0], args[1], args[2]); } - if (!this._requestHandler || typeof this._requestHandler[method] !== 'function') { - return Promise.reject(new Error('Missing requestHandler or method: ' + method)); + const requestHandler: object | null | undefined = (channel === DEFAULT_CHANNEL ? this._requestHandler : this._localChannels.get(channel)); + if (!requestHandler) { + return Promise.reject(new Error(`Missing channel ${channel} on worker thread`)); + } + if (typeof (requestHandler as any)[method] !== 'function') { + return Promise.reject(new Error(`Missing method ${method} on worker thread channel ${channel}`)); } try { - return Promise.resolve(this._requestHandler[method].apply(this._requestHandler, args)); + return Promise.resolve((requestHandler as any)[method].apply(requestHandler, args)); } catch (e) { return Promise.reject(e); } } - private _handleEvent(eventName: string, arg: any): Event { - if (!this._requestHandler) { - throw new Error(`Missing requestHandler`); + private _handleEvent(channel: string, eventName: string, arg: any): Event { + const requestHandler: object | null | undefined = (channel === DEFAULT_CHANNEL ? this._requestHandler : this._localChannels.get(channel)); + if (!requestHandler) { + throw new Error(`Missing channel ${channel} on worker thread`); } if (propertyIsDynamicEvent(eventName)) { - const event = (this._requestHandler as any)[eventName].call(this._requestHandler, arg); + const event = (requestHandler as any)[eventName].call(requestHandler, arg); if (typeof event !== 'function') { throw new Error(`Missing dynamic event ${eventName} on request handler.`); } return event; } if (propertyIsEvent(eventName)) { - const event = (this._requestHandler as any)[eventName]; + const event = (requestHandler as any)[eventName]; if (typeof event !== 'function') { throw new Error(`Missing event ${eventName} on request handler.`); } @@ -502,22 +535,25 @@ export class SimpleWorkerServer { throw new Error(`Malformed event name ${eventName}`); } - private initialize(workerId: number, loaderConfig: any, moduleId: string, hostMethods: string[]): Promise { + public setChannel(channel: string, handler: T): void { + this._localChannels.set(channel, handler); + } + + public getChannel(channel: string): Proxied { + if (!this._remoteChannels.has(channel)) { + const inst = this._protocol.createProxyToRemoteChannel(channel); + this._remoteChannels.set(channel, inst); + } + return this._remoteChannels.get(channel) as Proxied; + } + + private async initialize(workerId: number, loaderConfig: any, moduleId: string): Promise { this._protocol.setWorkerId(workerId); - const proxyMethodRequest = (method: string, args: any[]): Promise => { - return this._protocol.sendMessage(method, args); - }; - const proxyListen = (eventName: string, arg: any): Event => { - return this._protocol.listen(eventName, arg); - }; - - const hostProxy = createProxyObject(hostMethods, proxyMethodRequest, proxyListen); - if (this._requestHandlerFactory) { // static request handler - this._requestHandler = this._requestHandlerFactory(hostProxy); - return Promise.resolve(getAllMethodNames(this._requestHandler)); + this._requestHandler = this._requestHandlerFactory(this); + return; } if (loaderConfig) { @@ -542,18 +578,16 @@ export class SimpleWorkerServer { if (isESM) { const url = FileAccess.asBrowserUri(`${moduleId}.js` as AppResourcePath).toString(true); - return import(`${url}`).then((module: { create: IRequestHandlerFactory }) => { - this._requestHandler = module.create(hostProxy); + return import(`${url}`).then((module: { create: IRequestHandlerFactory }) => { + this._requestHandler = module.create(this); if (!this._requestHandler) { throw new Error(`No RequestHandler!`); } - - return getAllMethodNames(this._requestHandler); }); } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { // Use the global require to be sure to get the global config // ESM-comment-begin @@ -563,24 +597,24 @@ export class SimpleWorkerServer { // const req = globalThis.require; // ESM-uncomment-end - req([moduleId], (module: { create: IRequestHandlerFactory }) => { - this._requestHandler = module.create(hostProxy); + req([moduleId], (module: { create: IRequestHandlerFactory }) => { + this._requestHandler = module.create(this); if (!this._requestHandler) { reject(new Error(`No RequestHandler!`)); return; } - resolve(getAllMethodNames(this._requestHandler)); + resolve(); }, reject); }); } } /** - * Called on the worker side + * Defines the worker entry point. Must be exported and named `create`. * @skipMangle */ -export function create(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void): SimpleWorkerServer { +export function create(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void): SimpleWorkerServer { return new SimpleWorkerServer(postMessage, null); } diff --git a/src/vs/base/common/worker/simpleWorkerBootstrap.ts b/src/vs/base/common/worker/simpleWorkerBootstrap.ts index 01e71dc4f2f..c776f7331d0 100644 --- a/src/vs/base/common/worker/simpleWorkerBootstrap.ts +++ b/src/vs/base/common/worker/simpleWorkerBootstrap.ts @@ -16,15 +16,15 @@ declare const globalThis: { let initialized = false; -function initialize(factory: IRequestHandlerFactory) { +function initialize(factory: IRequestHandlerFactory) { if (initialized) { return; } initialized = true; - const simpleWorker = new SimpleWorkerServer( + const simpleWorker = new SimpleWorkerServer( msg => globalThis.postMessage(msg), - host => factory(host) + (workerServer) => factory(workerServer) ); globalThis.onmessage = (e: MessageEvent) => { @@ -32,7 +32,7 @@ function initialize(factory: IRequestHandlerFactory) { }; } -export function bootstrapSimpleWorker(factory: IRequestHandlerFactory) { +export function bootstrapSimpleWorker(factory: IRequestHandlerFactory) { globalThis.onmessage = (_e: MessageEvent) => { // Ignore first message in this case and initialize if not yet initialized if (!initialized) { diff --git a/src/vs/editor/browser/services/editorWorkerService.ts b/src/vs/editor/browser/services/editorWorkerService.ts index 6a950ada9e1..56204107724 100644 --- a/src/vs/editor/browser/services/editorWorkerService.ts +++ b/src/vs/editor/browser/services/editorWorkerService.ts @@ -3,18 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IntervalTimer, timeout } from 'vs/base/common/async'; -import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { timeout } from 'vs/base/common/async'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { SimpleWorkerClient, logOnceWebWorkerWarning, IWorkerClient } from 'vs/base/common/worker/simpleWorker'; -import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory'; +import { logOnceWebWorkerWarning, IWorkerClient, Proxied, IWorkerDescriptor } from 'vs/base/common/worker/simpleWorker'; +import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import * as languages from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; -import { DiffAlgorithmName, IDiffComputationResult, IEditorWorkerService, ILineChange, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; +import { DiffAlgorithmName, IEditorWorkerService, ILineChange, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { isNonEmptyArray } from 'vs/base/common/arrays'; @@ -22,7 +22,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { StopWatch } from 'vs/base/common/stopwatch'; import { canceled, onUnexpectedError } from 'vs/base/common/errors'; import { UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; @@ -32,11 +31,8 @@ import { LineRange } from 'vs/editor/common/core/lineRange'; import { SectionHeader, FindSectionHeaderOptions } from 'vs/editor/common/services/findSectionHeaders'; import { mainWindow } from 'vs/base/browser/window'; import { WindowIntervalTimer } from 'vs/base/browser/dom'; - -/** - * Stop syncing a model to the worker if it was not needed for 1 min. - */ -const STOP_SYNC_MODEL_DELTA_TIME_MS = 60 * 1000; +import { WorkerTextModelSyncClient } from 'vs/editor/common/services/textModelSync/textModelSync.impl'; +import { EditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; /** * Stop the worker if it was not needed for 5 min. @@ -54,7 +50,7 @@ function canSyncModel(modelService: IModelService, resource: URI): boolean { return true; } -export class EditorWorkerService extends Disposable implements IEditorWorkerService { +export abstract class EditorWorkerService extends Disposable implements IEditorWorkerService { declare readonly _serviceBrand: undefined; @@ -63,30 +59,30 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ private readonly _logService: ILogService; constructor( - workerMainLocation: URI | undefined, + workerDescriptor: IWorkerDescriptor, @IModelService modelService: IModelService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @ILogService logService: ILogService, - @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, + @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, ) { super(); this._modelService = modelService; - this._workerManager = this._register(new WorkerManager(workerMainLocation, this._modelService, languageConfigurationService)); + this._workerManager = this._register(new WorkerManager(workerDescriptor, this._modelService)); this._logService = logService; // register default link-provider and default completions-provider this._register(languageFeaturesService.linkProvider.register({ language: '*', hasAccessToAllModels: true }, { - provideLinks: (model, token) => { + provideLinks: async (model, token) => { if (!canSyncModel(this._modelService, model.uri)) { return Promise.resolve({ links: [] }); // File too large } - return this._workerManager.withWorker().then(client => client.computeLinks(model.uri)).then(links => { - return links && { links }; - }); + const worker = await this._workerWithResources([model.uri]); + const links = await worker.$computeLinks(model.uri.toString()); + return links && { links }; } })); - this._register(languageFeaturesService.completionProvider.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService, languageConfigurationService))); + this._register(languageFeaturesService.completionProvider.register('*', new WordBasedCompletionItemProvider(this._workerManager, configurationService, this._modelService, this._languageConfigurationService))); } public override dispose(): void { @@ -97,12 +93,14 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ return canSyncModel(this._modelService, uri); } - public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise { - return this._workerManager.withWorker().then(client => client.computedUnicodeHighlights(uri, options, range)); + public async computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise { + const worker = await this._workerWithResources([uri]); + return worker.$computeUnicodeHighlights(uri.toString(), options, range); } public async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { - const result = await this._workerManager.withWorker().then(client => client.computeDiff(original, modified, options, algorithm)); + const worker = await this._workerWithResources([original, modified], /* forceLargeModels */true); + const result = await worker.$computeDiff(original.toString(), modified.toString(), options, algorithm); if (!result) { return null; } @@ -138,17 +136,18 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ return (canSyncModel(this._modelService, original) && canSyncModel(this._modelService, modified)); } - public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { - return this._workerManager.withWorker().then(client => client.computeDirtyDiff(original, modified, ignoreTrimWhitespace)); + public async computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { + const worker = await this._workerWithResources([original, modified]); + return worker.$computeDirtyDiff(original.toString(), modified.toString(), ignoreTrimWhitespace); } - public computeMoreMinimalEdits(resource: URI, edits: languages.TextEdit[] | null | undefined, pretty: boolean = false): Promise { + public async computeMoreMinimalEdits(resource: URI, edits: languages.TextEdit[] | null | undefined, pretty: boolean = false): Promise { if (isNonEmptyArray(edits)) { if (!canSyncModel(this._modelService, resource)) { return Promise.resolve(edits); // File too large } const sw = StopWatch.create(); - const result = this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits, pretty)); + const result = this._workerWithResources([resource]).then(worker => worker.$computeMoreMinimalEdits(resource.toString(), edits, pretty)); result.finally(() => this._logService.trace('FORMAT#computeMoreMinimalEdits', resource.toString(true), sw.elapsed())); return Promise.race([result, timeout(1000).then(() => edits)]); @@ -163,12 +162,16 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ return Promise.resolve(edits); // File too large } const sw = StopWatch.create(); - const result = this._workerManager.withWorker().then(client => client.computeHumanReadableDiff(resource, edits, - { ignoreTrimWhitespace: false, maxComputationTimeMs: 1000, computeMoves: false, })).catch((err) => { - onUnexpectedError(err); - // In case of an exception, fall back to computeMoreMinimalEdits - return this.computeMoreMinimalEdits(resource, edits, true); - }); + const opts: ILinesDiffComputerOptions = { ignoreTrimWhitespace: false, maxComputationTimeMs: 1000, computeMoves: false }; + const result = ( + this._workerWithResources([resource]) + .then(worker => worker.$computeHumanReadableDiff(resource.toString(), edits, opts)) + .catch((err) => { + onUnexpectedError(err); + // In case of an exception, fall back to computeMoreMinimalEdits + return this.computeMoreMinimalEdits(resource, edits, true); + }) + ); result.finally(() => this._logService.trace('FORMAT#computeHumanReadableDiff', resource.toString(true), sw.elapsed())); return result; @@ -181,20 +184,47 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ return (canSyncModel(this._modelService, resource)); } - public navigateValueSet(resource: URI, range: IRange, up: boolean): Promise { - return this._workerManager.withWorker().then(client => client.navigateValueSet(resource, range, up)); + public async navigateValueSet(resource: URI, range: IRange, up: boolean): Promise { + const model = this._modelService.getModel(resource); + if (!model) { + return null; + } + const wordDefRegExp = this._languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition(); + const wordDef = wordDefRegExp.source; + const wordDefFlags = wordDefRegExp.flags; + const worker = await this._workerWithResources([resource]); + return worker.$navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags); } - canComputeWordRanges(resource: URI): boolean { + public canComputeWordRanges(resource: URI): boolean { return canSyncModel(this._modelService, resource); } - computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { - return this._workerManager.withWorker().then(client => client.computeWordRanges(resource, range)); + public async computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { + const model = this._modelService.getModel(resource); + if (!model) { + return Promise.resolve(null); + } + const wordDefRegExp = this._languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition(); + const wordDef = wordDefRegExp.source; + const wordDefFlags = wordDefRegExp.flags; + const worker = await this._workerWithResources([resource]); + return worker.$computeWordRanges(resource.toString(), range, wordDef, wordDefFlags); } - public findSectionHeaders(uri: URI, options: FindSectionHeaderOptions): Promise { - return this._workerManager.withWorker().then(client => client.findSectionHeaders(uri, options)); + public async findSectionHeaders(uri: URI, options: FindSectionHeaderOptions): Promise { + const worker = await this._workerWithResources([uri]); + return worker.$findSectionHeaders(uri.toString(), options); + } + + public async computeDefaultDocumentColors(uri: URI): Promise { + const worker = await this._workerWithResources([uri]); + return worker.$computeDefaultDocumentColors(uri.toString()); + } + + private async _workerWithResources(resources: URI[], forceLargeModels: boolean = false): Promise> { + const worker = await this._workerManager.withWorker(); + return await worker.workerWithSyncedResources(resources, forceLargeModels); } } @@ -282,7 +312,10 @@ class WorkerManager extends Disposable { private _editorWorkerClient: EditorWorkerClient | null; private _lastWorkerUsedTime: number; - constructor(private readonly workerMainLocation: URI | undefined, modelService: IModelService, private readonly languageConfigurationService: ILanguageConfigurationService) { + constructor( + private readonly _workerDescriptor: IWorkerDescriptor, + @IModelService modelService: IModelService + ) { super(); this._modelService = modelService; this._editorWorkerClient = null; @@ -336,124 +369,31 @@ class WorkerManager extends Disposable { public withWorker(): Promise { this._lastWorkerUsedTime = (new Date()).getTime(); if (!this._editorWorkerClient) { - this._editorWorkerClient = new EditorWorkerClient(this.workerMainLocation, this._modelService, false, 'editorWorkerService', this.languageConfigurationService); + this._editorWorkerClient = new EditorWorkerClient(this._workerDescriptor, false, this._modelService); } return Promise.resolve(this._editorWorkerClient); } } -class EditorModelManager extends Disposable { - - private readonly _proxy: EditorSimpleWorker; - private readonly _modelService: IModelService; - private _syncedModels: { [modelUrl: string]: IDisposable } = Object.create(null); - private _syncedModelsLastUsedTime: { [modelUrl: string]: number } = Object.create(null); - - constructor(proxy: EditorSimpleWorker, modelService: IModelService, keepIdleModels: boolean) { - super(); - this._proxy = proxy; - this._modelService = modelService; - - if (!keepIdleModels) { - const timer = new IntervalTimer(); - timer.cancelAndSet(() => this._checkStopModelSync(), Math.round(STOP_SYNC_MODEL_DELTA_TIME_MS / 2)); - this._register(timer); - } - } - - public override dispose(): void { - for (const modelUrl in this._syncedModels) { - dispose(this._syncedModels[modelUrl]); - } - this._syncedModels = Object.create(null); - this._syncedModelsLastUsedTime = Object.create(null); - super.dispose(); - } - - public ensureSyncedResources(resources: URI[], forceLargeModels: boolean): void { - for (const resource of resources) { - const resourceStr = resource.toString(); - - if (!this._syncedModels[resourceStr]) { - this._beginModelSync(resource, forceLargeModels); - } - if (this._syncedModels[resourceStr]) { - this._syncedModelsLastUsedTime[resourceStr] = (new Date()).getTime(); - } - } - } - - private _checkStopModelSync(): void { - const currentTime = (new Date()).getTime(); - - const toRemove: string[] = []; - for (const modelUrl in this._syncedModelsLastUsedTime) { - const elapsedTime = currentTime - this._syncedModelsLastUsedTime[modelUrl]; - if (elapsedTime > STOP_SYNC_MODEL_DELTA_TIME_MS) { - toRemove.push(modelUrl); - } - } - - for (const e of toRemove) { - this._stopModelSync(e); - } - } - - private _beginModelSync(resource: URI, forceLargeModels: boolean): void { - const model = this._modelService.getModel(resource); - if (!model) { - return; - } - if (!forceLargeModels && model.isTooLargeForSyncing()) { - return; - } - - const modelUrl = resource.toString(); - - this._proxy.acceptNewModel({ - url: model.uri.toString(), - lines: model.getLinesContent(), - EOL: model.getEOL(), - versionId: model.getVersionId() - }); - - const toDispose = new DisposableStore(); - toDispose.add(model.onDidChangeContent((e) => { - this._proxy.acceptModelChanged(modelUrl.toString(), e); - })); - toDispose.add(model.onWillDispose(() => { - this._stopModelSync(modelUrl); - })); - toDispose.add(toDisposable(() => { - this._proxy.acceptRemovedModel(modelUrl); - })); - - this._syncedModels[modelUrl] = toDispose; - } - - private _stopModelSync(modelUrl: string): void { - const toDispose = this._syncedModels[modelUrl]; - delete this._syncedModels[modelUrl]; - delete this._syncedModelsLastUsedTime[modelUrl]; - dispose(toDispose); - } -} - class SynchronousWorkerClient implements IWorkerClient { private readonly _instance: T; - private readonly _proxyObj: Promise; + public readonly proxy: Proxied; constructor(instance: T) { this._instance = instance; - this._proxyObj = Promise.resolve(this._instance); + this.proxy = this._instance as Proxied; } public dispose(): void { this._instance.dispose(); } - public getProxyObject(): Promise { - return this._proxyObj; + public setChannel(channel: string, handler: T): void { + throw new Error(`Not supported`); + } + + public getChannel(channel: string): Proxied { + throw new Error(`Not supported`); } } @@ -461,40 +401,22 @@ export interface IEditorWorkerClient { fhr(method: string, args: any[]): Promise; } -export class EditorWorkerHost implements IEditorWorkerHost { - - private readonly _workerClient: IEditorWorkerClient; - - constructor(workerClient: IEditorWorkerClient) { - this._workerClient = workerClient; - } - - // foreign host request - public fhr(method: string, args: any[]): Promise { - return this._workerClient.fhr(method, args); - } -} - export class EditorWorkerClient extends Disposable implements IEditorWorkerClient { private readonly _modelService: IModelService; private readonly _keepIdleModels: boolean; - protected _worker: IWorkerClient | null; - protected readonly _workerFactory: DefaultWorkerFactory; - private _modelManager: EditorModelManager | null; + private _worker: IWorkerClient | null; + private _modelManager: WorkerTextModelSyncClient | null; private _disposed = false; constructor( - workerMainLocation: URI | undefined, - modelService: IModelService, + private readonly _workerDescriptor: IWorkerDescriptor, keepIdleModels: boolean, - label: string | undefined, - private readonly languageConfigurationService: ILanguageConfigurationService + @IModelService modelService: IModelService, ) { super(); this._modelService = modelService; this._keepIdleModels = keepIdleModels; - this._workerFactory = new DefaultWorkerFactory(workerMainLocation, label); this._worker = null; this._modelManager = null; } @@ -507,123 +429,59 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien private _getOrCreateWorker(): IWorkerClient { if (!this._worker) { try { - this._worker = this._register(new SimpleWorkerClient( - this._workerFactory, - 'vs/editor/common/services/editorSimpleWorker', - new EditorWorkerHost(this) - )); + this._worker = this._register(createWebWorker(this._workerDescriptor)); + EditorWorkerHost.setChannel(this._worker, this._createEditorWorkerHost()); } catch (err) { logOnceWebWorkerWarning(err); - this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null)); + this._worker = this._createFallbackLocalWorker(); } } return this._worker; } - protected _getProxy(): Promise { - return this._getOrCreateWorker().getProxyObject().then(undefined, (err) => { + protected async _getProxy(): Promise> { + try { + const proxy = this._getOrCreateWorker().proxy; + await proxy.$ping(); + return proxy; + } catch (err) { logOnceWebWorkerWarning(err); - this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null)); - return this._getOrCreateWorker().getProxyObject(); - }); + this._worker = this._createFallbackLocalWorker(); + return this._worker.proxy; + } } - private _getOrCreateModelManager(proxy: EditorSimpleWorker): EditorModelManager { + private _createFallbackLocalWorker(): SynchronousWorkerClient { + return new SynchronousWorkerClient(new EditorSimpleWorker(this._createEditorWorkerHost(), null)); + } + + private _createEditorWorkerHost(): EditorWorkerHost { + return { + $fhr: (method, args) => this.fhr(method, args) + }; + } + + private _getOrCreateModelManager(proxy: Proxied): WorkerTextModelSyncClient { if (!this._modelManager) { - this._modelManager = this._register(new EditorModelManager(proxy, this._modelService, this._keepIdleModels)); + this._modelManager = this._register(new WorkerTextModelSyncClient(proxy, this._modelService, this._keepIdleModels)); } return this._modelManager; } - protected async _withSyncedResources(resources: URI[], forceLargeModels: boolean = false): Promise { + public async workerWithSyncedResources(resources: URI[], forceLargeModels: boolean = false): Promise> { if (this._disposed) { return Promise.reject(canceled()); } - return this._getProxy().then((proxy) => { - this._getOrCreateModelManager(proxy).ensureSyncedResources(resources, forceLargeModels); - return proxy; - }); - } - - public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise { - return this._withSyncedResources([uri]).then(proxy => { - return proxy.computeUnicodeHighlights(uri.toString(), options, range); - }); - } - - public computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { - return this._withSyncedResources([original, modified], /* forceLargeModels */true).then(proxy => { - return proxy.computeDiff(original.toString(), modified.toString(), options, algorithm); - }); - } - - public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { - return this._withSyncedResources([original, modified]).then(proxy => { - return proxy.computeDirtyDiff(original.toString(), modified.toString(), ignoreTrimWhitespace); - }); - } - - public computeMoreMinimalEdits(resource: URI, edits: languages.TextEdit[], pretty: boolean): Promise { - return this._withSyncedResources([resource]).then(proxy => { - return proxy.computeMoreMinimalEdits(resource.toString(), edits, pretty); - }); - } - - public computeHumanReadableDiff(resource: URI, edits: languages.TextEdit[], options: ILinesDiffComputerOptions): Promise { - return this._withSyncedResources([resource]).then(proxy => { - return proxy.computeHumanReadableDiff(resource.toString(), edits, options); - }); - } - - public computeLinks(resource: URI): Promise { - return this._withSyncedResources([resource]).then(proxy => { - return proxy.computeLinks(resource.toString()); - }); - } - - public computeDefaultDocumentColors(resource: URI): Promise { - return this._withSyncedResources([resource]).then(proxy => { - return proxy.computeDefaultDocumentColors(resource.toString()); - }); + const proxy = await this._getProxy(); + this._getOrCreateModelManager(proxy).ensureSyncedResources(resources, forceLargeModels); + return proxy; } public async textualSuggest(resources: URI[], leadingWord: string | undefined, wordDefRegExp: RegExp): Promise<{ words: string[]; duration: number } | null> { - const proxy = await this._withSyncedResources(resources); + const proxy = await this.workerWithSyncedResources(resources); const wordDef = wordDefRegExp.source; const wordDefFlags = wordDefRegExp.flags; - return proxy.textualSuggest(resources.map(r => r.toString()), leadingWord, wordDef, wordDefFlags); - } - - computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { - return this._withSyncedResources([resource]).then(proxy => { - const model = this._modelService.getModel(resource); - if (!model) { - return Promise.resolve(null); - } - const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition(); - const wordDef = wordDefRegExp.source; - const wordDefFlags = wordDefRegExp.flags; - return proxy.computeWordRanges(resource.toString(), range, wordDef, wordDefFlags); - }); - } - - public navigateValueSet(resource: URI, range: IRange, up: boolean): Promise { - return this._withSyncedResources([resource]).then(proxy => { - const model = this._modelService.getModel(resource); - if (!model) { - return null; - } - const wordDefRegExp = this.languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).getWordDefinition(); - const wordDef = wordDefRegExp.source; - const wordDefFlags = wordDefRegExp.flags; - return proxy.navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags); - }); - } - - public findSectionHeaders(uri: URI, options: FindSectionHeaderOptions): Promise { - return this._withSyncedResources([uri]).then(proxy => { - return proxy.findSectionHeaders(uri.toString(), options); - }); + return proxy.$textualSuggest(resources.map(r => r.toString()), leadingWord, wordDef, wordDefFlags); } override dispose(): void { diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 42c128690c0..4bf19b0d25e 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -6,18 +6,17 @@ import { stringDiff } from 'vs/base/common/diff/diff'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; -import { IPosition, Position } from 'vs/editor/common/core/position'; +import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; +import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; -import { IMirrorTextModel, IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel'; -import { ensureValidWordDefinition, getWordAtText, IWordAtPosition } from 'vs/editor/common/core/wordHelper'; +import { IMirrorTextModel, IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; import { IColorInformation, IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/languages'; -import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/languages/linkComputer'; +import { computeLinks } from 'vs/editor/common/languages/linkComputer'; import { BasicInplaceReplace } from 'vs/editor/common/languages/supports/inplaceReplaceSupport'; import { DiffAlgorithmName, IDiffComputationResult, ILineChange, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; import { createMonacoBaseAPI } from 'vs/editor/common/services/editorBaseApi'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; +import { EditorWorkerHost } from './editorWorkerHost'; import { StopWatch } from 'vs/base/common/stopwatch'; import { UnicodeTextModelHighlighter, UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter'; import { DiffComputer, IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; @@ -28,8 +27,10 @@ import { createProxyObject, getAllMethodNames } from 'vs/base/common/objects'; import { IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; import { AppResourcePath, FileAccess } from 'vs/base/common/network'; import { BugIndicatingError } from 'vs/base/common/errors'; -import { IDocumentColorComputerTarget, computeDefaultDocumentColors } from 'vs/editor/common/languages/defaultDocumentColorsComputer'; +import { computeDefaultDocumentColors } from 'vs/editor/common/languages/defaultDocumentColorsComputer'; import { FindSectionHeaderOptions, SectionHeader, findSectionHeaders } from 'vs/editor/common/services/findSectionHeaders'; +import { IRawModelData, IWorkerTextModelSyncChannelServer } from './textModelSync/textModelSync.protocol'; +import { ICommonModel, WorkerTextModelSyncServer } from 'vs/editor/common/services/textModelSync/textModelSync.impl'; // ESM-comment-begin const isESM = false; @@ -55,43 +56,11 @@ export interface IWorkerContext { getMirrorModels(): IMirrorModel[]; } -/** - * @internal - */ -export interface IRawModelData { - url: string; - versionId: number; - lines: string[]; - EOL: string; -} - -/** - * @internal - */ -export interface ICommonModel extends ILinkComputerTarget, IDocumentColorComputerTarget, IMirrorModel { - uri: URI; - version: number; - eol: string; - getValue(): string; - - getLinesContent(): string[]; - getLineCount(): number; - getLineContent(lineNumber: number): string; - getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[]; - words(wordDefinition: RegExp): Iterable; - getWordUntilPosition(position: IPosition, wordDefinition: RegExp): IWordAtPosition; - getValueInRange(range: IRange): string; - getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range | null; - offsetAt(position: IPosition): number; - positionAt(offset: number): IPosition; - findMatches(regex: RegExp): RegExpMatchArray[]; -} - /** * Range of a word inside a model. * @internal */ -interface IWordRange { +export interface IWordRange { /** * The index where the word starts. */ @@ -102,246 +71,6 @@ interface IWordRange { readonly end: number; } -/** - * @internal - */ -class MirrorModel extends BaseMirrorModel implements ICommonModel { - - public get uri(): URI { - return this._uri; - } - - public get eol(): string { - return this._eol; - } - - public getValue(): string { - return this.getText(); - } - - public findMatches(regex: RegExp): RegExpMatchArray[] { - const matches = []; - for (let i = 0; i < this._lines.length; i++) { - const line = this._lines[i]; - const offsetToAdd = this.offsetAt(new Position(i + 1, 1)); - const iteratorOverMatches = line.matchAll(regex); - for (const match of iteratorOverMatches) { - if (match.index || match.index === 0) { - match.index = match.index + offsetToAdd; - } - matches.push(match); - } - } - return matches; - } - - 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 | null { - - const 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): IWordAtPosition { - const 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 - }; - } - - - public words(wordDefinition: RegExp): Iterable { - - const lines = this._lines; - const wordenize = this._wordenize.bind(this); - - let lineNumber = 0; - let lineText = ''; - let wordRangesIdx = 0; - let wordRanges: IWordRange[] = []; - - return { - *[Symbol.iterator]() { - while (true) { - if (wordRangesIdx < wordRanges.length) { - const value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end); - wordRangesIdx += 1; - yield value; - } else { - if (lineNumber < lines.length) { - lineText = lines[lineNumber]; - wordRanges = wordenize(lineText, wordDefinition); - wordRangesIdx = 0; - lineNumber += 1; - } else { - break; - } - } - } - } - }; - } - - public getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[] { - const content = this._lines[lineNumber - 1]; - const ranges = this._wordenize(content, wordDefinition); - const words: IWordAtPosition[] = []; - for (const range of ranges) { - words.push({ - word: content.substring(range.start, range.end), - startColumn: range.start + 1, - endColumn: range.end + 1 - }); - } - return words; - } - - private _wordenize(content: string, wordDefinition: RegExp): IWordRange[] { - const result: IWordRange[] = []; - let match: RegExpExecArray | null; - - 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); - } - - const lineEnding = this._eol; - const startLineIndex = range.startLineNumber - 1; - const endLineIndex = range.endLineNumber - 1; - const resultLines: string[] = []; - - resultLines.push(this._lines[startLineIndex].substring(range.startColumn - 1)); - for (let 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!.getPrefixSum(position.lineNumber - 2) + (position.column - 1); - } - - public positionAt(offset: number): IPosition { - offset = Math.floor(offset); - offset = Math.max(0, offset); - - this._ensureLineStarts(); - const out = this._lineStarts!.getIndexOf(offset); - const 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 { - const 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 */ @@ -354,55 +83,38 @@ declare const require: any; /** * @internal */ -export class EditorSimpleWorker implements IRequestHandler, IDisposable { +export class BaseEditorSimpleWorker implements IDisposable, IWorkerTextModelSyncChannelServer, IRequestHandler { _requestHandlerBrand: any; - protected readonly _host: IEditorWorkerHost; - private _models: { [uri: string]: MirrorModel }; - private readonly _foreignModuleFactory: IForeignModuleFactory | null; - private _foreignModule: any; + private readonly _workerTextModelSyncServer = new WorkerTextModelSyncServer(); - constructor(host: IEditorWorkerHost, foreignModuleFactory: IForeignModuleFactory | null) { - this._host = host; - this._models = Object.create(null); - this._foreignModuleFactory = foreignModuleFactory; - this._foreignModule = null; + constructor() { } - public dispose(): void { - this._models = Object.create(null); + dispose(): void { } - protected _getModel(uri: string): ICommonModel { - return this._models[uri]; + protected _getModel(uri: string): ICommonModel | undefined { + return this._workerTextModelSyncServer.getModel(uri); } - private _getModels(): ICommonModel[] { - const all: MirrorModel[] = []; - Object.keys(this._models).forEach((key) => all.push(this._models[key])); - return all; + protected _getModels(): ICommonModel[] { + return this._workerTextModelSyncServer.getModels(); } - public acceptNewModel(data: IRawModelData): void { - this._models[data.url] = new MirrorModel(URI.parse(data.url), data.lines, data.EOL, data.versionId); + public $acceptNewModel(data: IRawModelData): void { + this._workerTextModelSyncServer.$acceptNewModel(data); } - public acceptModelChanged(strURL: string, e: IModelChangedEvent): void { - if (!this._models[strURL]) { - return; - } - const model = this._models[strURL]; - model.onEvents(e); + public $acceptModelChanged(uri: string, e: IModelChangedEvent): void { + this._workerTextModelSyncServer.$acceptModelChanged(uri, e); } - public acceptRemovedModel(strURL: string): void { - if (!this._models[strURL]) { - return; - } - delete this._models[strURL]; + public $acceptRemovedModel(uri: string): void { + this._workerTextModelSyncServer.$acceptRemovedModel(uri); } - public async computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise { + public async $computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise { const model = this._getModel(url); if (!model) { return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 }; @@ -410,7 +122,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return UnicodeTextModelHighlighter.computeUnicodeHighlights(model, options, range); } - public async findSectionHeaders(url: string, options: FindSectionHeaderOptions): Promise { + public async $findSectionHeaders(url: string, options: FindSectionHeaderOptions): Promise { const model = this._getModel(url); if (!model) { return []; @@ -420,7 +132,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { // ---- BEGIN diff -------------------------------------------------------------------------- - public async computeDiff(originalUrl: string, modifiedUrl: string, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { + public async $computeDiff(originalUrl: string, modifiedUrl: string, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { const original = this._getModel(originalUrl); const modified = this._getModel(modifiedUrl); if (!original || !modified) { @@ -484,7 +196,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return true; } - public async computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise { + public async $computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise { const original = this._getModel(originalUrl); const modified = this._getModel(modifiedUrl); if (!original || !modified) { @@ -510,7 +222,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { private static readonly _diffLimit = 100000; - public async computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): Promise { + public async $computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): Promise { const model = this._getModel(modelUrl); if (!model) { return edits; @@ -592,7 +304,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return result; } - public computeHumanReadableDiff(modelUrl: string, edits: TextEdit[], options: ILinesDiffComputerOptions): TextEdit[] { + public $computeHumanReadableDiff(modelUrl: string, edits: TextEdit[], options: ILinesDiffComputerOptions): TextEdit[] { const model = this._getModel(modelUrl); if (!model) { return edits; @@ -692,7 +404,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { // ---- END minimal edits --------------------------------------------------------------- - public async computeLinks(modelUrl: string): Promise { + public async $computeLinks(modelUrl: string): Promise { const model = this._getModel(modelUrl); if (!model) { return null; @@ -703,7 +415,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { // --- BEGIN default document colors ----------------------------------------------------------- - public async computeDefaultDocumentColors(modelUrl: string): Promise { + public async $computeDefaultDocumentColors(modelUrl: string): Promise { const model = this._getModel(modelUrl); if (!model) { return null; @@ -715,7 +427,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { private static readonly _suggestionsLimit = 10000; - public async textualSuggest(modelUrls: string[], leadingWord: string | undefined, wordDef: string, wordDefFlags: string): Promise<{ words: string[]; duration: number } | null> { + public async $textualSuggest(modelUrls: string[], leadingWord: string | undefined, wordDef: string, wordDefFlags: string): Promise<{ words: string[]; duration: number } | null> { const sw = new StopWatch(); const wordDefRegExp = new RegExp(wordDef, wordDefFlags); @@ -746,7 +458,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { //#region -- word ranges -- - public async computeWordRanges(modelUrl: string, range: IRange, wordDef: string, wordDefFlags: string): Promise<{ [word: string]: IRange[] }> { + public async $computeWordRanges(modelUrl: string, range: IRange, wordDef: string, wordDefFlags: string): Promise<{ [word: string]: IRange[] }> { const model = this._getModel(modelUrl); if (!model) { return Object.create(null); @@ -777,7 +489,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { //#endregion - public async navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): Promise { + public async $navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): Promise { const model = this._getModel(modelUrl); if (!model) { return null; @@ -804,12 +516,31 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { const result = BasicInplaceReplace.INSTANCE.navigateValueSet(range, selectionText, wordRange, word, up); return result; } +} + +/** + * @internal + */ +export class EditorSimpleWorker extends BaseEditorSimpleWorker { + + private _foreignModule: any = null; + + constructor( + private readonly _host: EditorWorkerHost, + private readonly _foreignModuleFactory: IForeignModuleFactory | null + ) { + super(); + } + + public async $ping() { + return 'pong'; + } // ---- BEGIN foreign module support -------------------------------------------------------------------------- - public loadForeignModule(moduleId: string, createData: any, foreignHostMethods: string[]): Promise { + public $loadForeignModule(moduleId: string, createData: any, foreignHostMethods: string[]): Promise { const proxyMethodRequest = (method: string, args: any[]): Promise => { - return this._host.fhr(method, args); + return this._host.$fhr(method, args); }; const foreignHost = createProxyObject(foreignHostMethods, proxyMethodRequest); @@ -844,7 +575,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { } // foreign method request - public fmr(method: string, args: any[]): Promise { + public $fmr(method: string, args: any[]): Promise { if (!this._foreignModule || typeof this._foreignModule[method] !== 'function') { return Promise.reject(new Error('Missing requestHandler or method: ' + method)); } @@ -860,11 +591,12 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { } /** - * Called on the worker side + * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle * @internal */ -export function create(host: IEditorWorkerHost): IRequestHandler { - return new EditorSimpleWorker(host, null); +export function create(workerServer: IWorkerServer): IRequestHandler { + return new EditorSimpleWorker(EditorWorkerHost.getChannel(workerServer), null); } // This is only available in a Web Worker diff --git a/src/vs/editor/common/services/editorWorker.ts b/src/vs/editor/common/services/editorWorker.ts index 7e87024cafc..74fe752bb8c 100644 --- a/src/vs/editor/common/services/editorWorker.ts +++ b/src/vs/editor/common/services/editorWorker.ts @@ -7,10 +7,10 @@ import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; -import { IInplaceReplaceSupportResult, TextEdit } from 'vs/editor/common/languages'; +import { IColorInformation, IInplaceReplaceSupportResult, TextEdit } from 'vs/editor/common/languages'; import { UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import type { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import type { BaseEditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; import { SectionHeader, FindSectionHeaderOptions } from 'vs/editor/common/services/findSectionHeaders'; export const IEditorWorkerService = createDecorator('editorWorkerService'); @@ -23,7 +23,7 @@ export interface IEditorWorkerService { canComputeUnicodeHighlights(uri: URI): boolean; computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise; - /** Implementation in {@link EditorSimpleWorker.computeDiff} */ + /** Implementation in {@link BaseEditorSimpleWorker.computeDiff} */ computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise; canComputeDirtyDiff(original: URI, modified: URI): boolean; @@ -39,6 +39,9 @@ export interface IEditorWorkerService { navigateValueSet(resource: URI, range: IRange, up: boolean): Promise; findSectionHeaders(uri: URI, options: FindSectionHeaderOptions): Promise; + + computeDefaultDocumentColors(uri: URI): Promise; + } export interface IDiffComputationResult { diff --git a/src/vs/editor/common/services/editorWorkerBootstrap.ts b/src/vs/editor/common/services/editorWorkerBootstrap.ts index 4902f7d48ff..160942a1d07 100644 --- a/src/vs/editor/common/services/editorWorkerBootstrap.ts +++ b/src/vs/editor/common/services/editorWorkerBootstrap.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SimpleWorkerServer } from 'vs/base/common/worker/simpleWorker'; +import { IWorkerServer, SimpleWorkerServer } from 'vs/base/common/worker/simpleWorker'; import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; +import { EditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; type MessageEvent = { data: any; @@ -26,7 +26,7 @@ export function initialize(factory: any) { const simpleWorker = new SimpleWorkerServer((msg) => { globalThis.postMessage(msg); - }, (host: IEditorWorkerHost) => new EditorSimpleWorker(host, null)); + }, (workerServer: IWorkerServer) => new EditorSimpleWorker(EditorWorkerHost.getChannel(workerServer), null)); globalThis.onmessage = (e: MessageEvent) => { simpleWorker.onmessage(e.data); diff --git a/src/vs/editor/common/services/editorWorkerHost.ts b/src/vs/editor/common/services/editorWorkerHost.ts index df0a6aa1c35..30ceed1833c 100644 --- a/src/vs/editor/common/services/editorWorkerHost.ts +++ b/src/vs/editor/common/services/editorWorkerHost.ts @@ -3,7 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export interface IEditorWorkerHost { +import { IWorkerServer, IWorkerClient } from 'vs/base/common/worker/simpleWorker'; + +export abstract class EditorWorkerHost { + public static CHANNEL_NAME = 'editorWorkerHost'; + public static getChannel(workerServer: IWorkerServer): EditorWorkerHost { + return workerServer.getChannel(EditorWorkerHost.CHANNEL_NAME); + } + public static setChannel(workerClient: IWorkerClient, obj: EditorWorkerHost): void { + workerClient.setChannel(EditorWorkerHost.CHANNEL_NAME, obj); + } + // foreign host request - fhr(method: string, args: any[]): Promise; + abstract $fhr(method: string, args: any[]): Promise; } diff --git a/src/vs/editor/common/services/textModelSync/textModelSync.impl.ts b/src/vs/editor/common/services/textModelSync/textModelSync.impl.ts new file mode 100644 index 00000000000..dc68d666453 --- /dev/null +++ b/src/vs/editor/common/services/textModelSync/textModelSync.impl.ts @@ -0,0 +1,427 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IntervalTimer } from 'vs/base/common/async'; +import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IWorkerClient, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; +import { IPosition, Position } from 'vs/editor/common/core/position'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { ensureValidWordDefinition, getWordAtText, IWordAtPosition } from 'vs/editor/common/core/wordHelper'; +import { IDocumentColorComputerTarget } from 'vs/editor/common/languages/defaultDocumentColorsComputer'; +import { ILinkComputerTarget } from 'vs/editor/common/languages/linkComputer'; +import { MirrorTextModel as BaseMirrorModel, IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; +import { IMirrorModel, IWordRange } from 'vs/editor/common/services/editorSimpleWorker'; +import { IModelService } from 'vs/editor/common/services/model'; +import { IRawModelData, IWorkerTextModelSyncChannelServer } from 'vs/editor/common/services/textModelSync/textModelSync.protocol'; + +/** + * Stop syncing a model to the worker if it was not needed for 1 min. + */ +export const STOP_SYNC_MODEL_DELTA_TIME_MS = 60 * 1000; + +export const WORKER_TEXT_MODEL_SYNC_CHANNEL = 'workerTextModelSync'; + +export class WorkerTextModelSyncClient extends Disposable { + + public static create(workerClient: IWorkerClient, modelService: IModelService): WorkerTextModelSyncClient { + return new WorkerTextModelSyncClient( + workerClient.getChannel(WORKER_TEXT_MODEL_SYNC_CHANNEL), + modelService + ); + } + + private readonly _proxy: IWorkerTextModelSyncChannelServer; + private readonly _modelService: IModelService; + private _syncedModels: { [modelUrl: string]: IDisposable } = Object.create(null); + private _syncedModelsLastUsedTime: { [modelUrl: string]: number } = Object.create(null); + + constructor(proxy: IWorkerTextModelSyncChannelServer, modelService: IModelService, keepIdleModels: boolean = false) { + super(); + this._proxy = proxy; + this._modelService = modelService; + + if (!keepIdleModels) { + const timer = new IntervalTimer(); + timer.cancelAndSet(() => this._checkStopModelSync(), Math.round(STOP_SYNC_MODEL_DELTA_TIME_MS / 2)); + this._register(timer); + } + } + + public override dispose(): void { + for (const modelUrl in this._syncedModels) { + dispose(this._syncedModels[modelUrl]); + } + this._syncedModels = Object.create(null); + this._syncedModelsLastUsedTime = Object.create(null); + super.dispose(); + } + + public ensureSyncedResources(resources: URI[], forceLargeModels: boolean = false): void { + for (const resource of resources) { + const resourceStr = resource.toString(); + + if (!this._syncedModels[resourceStr]) { + this._beginModelSync(resource, forceLargeModels); + } + if (this._syncedModels[resourceStr]) { + this._syncedModelsLastUsedTime[resourceStr] = (new Date()).getTime(); + } + } + } + + private _checkStopModelSync(): void { + const currentTime = (new Date()).getTime(); + + const toRemove: string[] = []; + for (const modelUrl in this._syncedModelsLastUsedTime) { + const elapsedTime = currentTime - this._syncedModelsLastUsedTime[modelUrl]; + if (elapsedTime > STOP_SYNC_MODEL_DELTA_TIME_MS) { + toRemove.push(modelUrl); + } + } + + for (const e of toRemove) { + this._stopModelSync(e); + } + } + + private _beginModelSync(resource: URI, forceLargeModels: boolean): void { + const model = this._modelService.getModel(resource); + if (!model) { + return; + } + if (!forceLargeModels && model.isTooLargeForSyncing()) { + return; + } + + const modelUrl = resource.toString(); + + this._proxy.$acceptNewModel({ + url: model.uri.toString(), + lines: model.getLinesContent(), + EOL: model.getEOL(), + versionId: model.getVersionId() + }); + + const toDispose = new DisposableStore(); + toDispose.add(model.onDidChangeContent((e) => { + this._proxy.$acceptModelChanged(modelUrl.toString(), e); + })); + toDispose.add(model.onWillDispose(() => { + this._stopModelSync(modelUrl); + })); + toDispose.add(toDisposable(() => { + this._proxy.$acceptRemovedModel(modelUrl); + })); + + this._syncedModels[modelUrl] = toDispose; + } + + private _stopModelSync(modelUrl: string): void { + const toDispose = this._syncedModels[modelUrl]; + delete this._syncedModels[modelUrl]; + delete this._syncedModelsLastUsedTime[modelUrl]; + dispose(toDispose); + } +} + +export class WorkerTextModelSyncServer implements IWorkerTextModelSyncChannelServer { + + private readonly _models: { [uri: string]: MirrorModel }; + + constructor() { + this._models = Object.create(null); + } + + public bindToServer(workerServer: IWorkerServer): void { + workerServer.setChannel(WORKER_TEXT_MODEL_SYNC_CHANNEL, this); + } + + public getModel(uri: string): ICommonModel | undefined { + return this._models[uri]; + } + + public getModels(): ICommonModel[] { + const all: MirrorModel[] = []; + Object.keys(this._models).forEach((key) => all.push(this._models[key])); + return all; + } + + $acceptNewModel(data: IRawModelData): void { + this._models[data.url] = new MirrorModel(URI.parse(data.url), data.lines, data.EOL, data.versionId); + } + + $acceptModelChanged(uri: string, e: IModelChangedEvent): void { + if (!this._models[uri]) { + return; + } + const model = this._models[uri]; + model.onEvents(e); + } + + $acceptRemovedModel(uri: string): void { + if (!this._models[uri]) { + return; + } + delete this._models[uri]; + } +} + +export class MirrorModel extends BaseMirrorModel implements ICommonModel { + + public get uri(): URI { + return this._uri; + } + + public get eol(): string { + return this._eol; + } + + public getValue(): string { + return this.getText(); + } + + public findMatches(regex: RegExp): RegExpMatchArray[] { + const matches = []; + for (let i = 0; i < this._lines.length; i++) { + const line = this._lines[i]; + const offsetToAdd = this.offsetAt(new Position(i + 1, 1)); + const iteratorOverMatches = line.matchAll(regex); + for (const match of iteratorOverMatches) { + if (match.index || match.index === 0) { + match.index = match.index + offsetToAdd; + } + matches.push(match); + } + } + return matches; + } + + 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 | null { + + const 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): IWordAtPosition { + const 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 + }; + } + + + public words(wordDefinition: RegExp): Iterable { + + const lines = this._lines; + const wordenize = this._wordenize.bind(this); + + let lineNumber = 0; + let lineText = ''; + let wordRangesIdx = 0; + let wordRanges: IWordRange[] = []; + + return { + *[Symbol.iterator]() { + while (true) { + if (wordRangesIdx < wordRanges.length) { + const value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end); + wordRangesIdx += 1; + yield value; + } else { + if (lineNumber < lines.length) { + lineText = lines[lineNumber]; + wordRanges = wordenize(lineText, wordDefinition); + wordRangesIdx = 0; + lineNumber += 1; + } else { + break; + } + } + } + } + }; + } + + public getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[] { + const content = this._lines[lineNumber - 1]; + const ranges = this._wordenize(content, wordDefinition); + const words: IWordAtPosition[] = []; + for (const range of ranges) { + words.push({ + word: content.substring(range.start, range.end), + startColumn: range.start + 1, + endColumn: range.end + 1 + }); + } + return words; + } + + private _wordenize(content: string, wordDefinition: RegExp): IWordRange[] { + const result: IWordRange[] = []; + let match: RegExpExecArray | null; + + 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); + } + + const lineEnding = this._eol; + const startLineIndex = range.startLineNumber - 1; + const endLineIndex = range.endLineNumber - 1; + const resultLines: string[] = []; + + resultLines.push(this._lines[startLineIndex].substring(range.startColumn - 1)); + for (let 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!.getPrefixSum(position.lineNumber - 2) + (position.column - 1); + } + + public positionAt(offset: number): IPosition { + offset = Math.floor(offset); + offset = Math.max(0, offset); + + this._ensureLineStarts(); + const out = this._lineStarts!.getIndexOf(offset); + const 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 { + const 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 }; + } + } +} + +export interface ICommonModel extends ILinkComputerTarget, IDocumentColorComputerTarget, IMirrorModel { + uri: URI; + version: number; + eol: string; + getValue(): string; + + getLinesContent(): string[]; + getLineCount(): number; + getLineContent(lineNumber: number): string; + getLineWords(lineNumber: number, wordDefinition: RegExp): IWordAtPosition[]; + words(wordDefinition: RegExp): Iterable; + getWordUntilPosition(position: IPosition, wordDefinition: RegExp): IWordAtPosition; + getValueInRange(range: IRange): string; + getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range | null; + offsetAt(position: IPosition): number; + positionAt(offset: number): IPosition; + findMatches(regex: RegExp): RegExpMatchArray[]; +} diff --git a/src/vs/editor/common/services/textModelSync/textModelSync.protocol.ts b/src/vs/editor/common/services/textModelSync/textModelSync.protocol.ts new file mode 100644 index 00000000000..5ffbf6a0c8c --- /dev/null +++ b/src/vs/editor/common/services/textModelSync/textModelSync.protocol.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; + +export interface IWorkerTextModelSyncChannelServer { + $acceptNewModel(data: IRawModelData): void; + + $acceptModelChanged(strURL: string, e: IModelChangedEvent): void; + + $acceptRemovedModel(strURL: string): void; +} + +export interface IRawModelData { + url: string; + versionId: number; + lines: string[]; + EOL: string; +} diff --git a/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts b/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts index 6e3c6f235d2..3bd7b848530 100644 --- a/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts +++ b/src/vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider.ts @@ -7,27 +7,19 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; import { ITextModel } from 'vs/editor/common/model'; import { DocumentColorProvider, IColor, IColorInformation, IColorPresentation } from 'vs/editor/common/languages'; -import { EditorWorkerClient } from 'vs/editor/browser/services/editorWorkerService'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { Disposable } from 'vs/base/common/lifecycle'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { registerEditorFeature } from 'vs/editor/common/editorFeatures'; -import { FileAccess } from 'vs/base/common/network'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; export class DefaultDocumentColorProvider implements DocumentColorProvider { - private _editorWorkerClient: EditorWorkerClient; - constructor( - modelService: IModelService, - languageConfigurationService: ILanguageConfigurationService, - ) { - this._editorWorkerClient = new EditorWorkerClient(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, false, 'editorWorkerService', languageConfigurationService); - } + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, + ) { } async provideDocumentColors(model: ITextModel, _token: CancellationToken): Promise { - return this._editorWorkerClient.computeDefaultDocumentColors(model.uri); + return this._editorWorkerService.computeDefaultDocumentColors(model.uri); } provideColorPresentations(_model: ITextModel, colorInfo: IColorInformation, _token: CancellationToken): IColorPresentation[] { @@ -50,12 +42,11 @@ export class DefaultDocumentColorProvider implements DocumentColorProvider { class DefaultDocumentColorProviderFeature extends Disposable { constructor( - @IModelService _modelService: IModelService, - @ILanguageConfigurationService _languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService _languageFeaturesService: ILanguageFeaturesService, + @IEditorWorkerService editorWorkerService: IEditorWorkerService, ) { super(); - this._register(_languageFeaturesService.colorProvider.register('*', new DefaultDocumentColorProvider(_modelService, _languageConfigurationService))); + this._register(_languageFeaturesService.colorProvider.register('*', new DefaultDocumentColorProvider(editorWorkerService))); } } diff --git a/src/vs/editor/contrib/colorPicker/browser/standaloneColorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/standaloneColorPickerWidget.ts index 424448611b1..b5fc34cf6e6 100644 --- a/src/vs/editor/contrib/colorPicker/browser/standaloneColorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/standaloneColorPickerWidget.ts @@ -22,11 +22,10 @@ import { EditorContributionInstantiation, registerEditorContribution } from 'vs/ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IRange } from 'vs/editor/common/core/range'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { DefaultDocumentColorProvider } from 'vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider'; import * as dom from 'vs/base/browser/dom'; import 'vs/css!./colorPicker'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; export class StandaloneColorPickerController extends Disposable implements IEditorContribution { @@ -38,11 +37,7 @@ export class StandaloneColorPickerController extends Disposable implements IEdit constructor( private readonly _editor: ICodeEditor, @IContextKeyService _contextKeyService: IContextKeyService, - @IModelService private readonly _modelService: IModelService, - @IKeybindingService private readonly _keybindingService: IKeybindingService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ILanguageFeaturesService private readonly _languageFeatureService: ILanguageFeaturesService, - @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService ) { super(); this._standaloneColorPickerVisible = EditorContextKeys.standaloneColorPickerVisible.bindTo(_contextKeyService); @@ -54,7 +49,12 @@ export class StandaloneColorPickerController extends Disposable implements IEdit return; } if (!this._standaloneColorPickerVisible.get()) { - this._standaloneColorPickerWidget = new StandaloneColorPickerWidget(this._editor, this._standaloneColorPickerVisible, this._standaloneColorPickerFocused, this._instantiationService, this._modelService, this._keybindingService, this._languageFeatureService, this._languageConfigurationService); + this._standaloneColorPickerWidget = this._instantiationService.createInstance( + StandaloneColorPickerWidget, + this._editor, + this._standaloneColorPickerVisible, + this._standaloneColorPickerFocused + ); } else if (!this._standaloneColorPickerFocused.get()) { this._standaloneColorPickerWidget?.focus(); } @@ -102,10 +102,9 @@ export class StandaloneColorPickerWidget extends Disposable implements IContentW private readonly _standaloneColorPickerVisible: IContextKey, private readonly _standaloneColorPickerFocused: IContextKey, @IInstantiationService _instantiationService: IInstantiationService, - @IModelService private readonly _modelService: IModelService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, - @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, ) { super(); this._standaloneColorPickerVisible.set(true); @@ -205,7 +204,7 @@ export class StandaloneColorPickerWidget extends Disposable implements IContentW range: range, color: { red: 0, green: 0, blue: 0, alpha: 1 } }; - const colorHoverResult: { colorHover: StandaloneColorPickerHover; foundInEditor: boolean } | null = await this._standaloneColorPickerParticipant.createColorHover(colorInfo, new DefaultDocumentColorProvider(this._modelService, this._languageConfigurationService), this._languageFeaturesService.colorProvider); + const colorHoverResult: { colorHover: StandaloneColorPickerHover; foundInEditor: boolean } | null = await this._standaloneColorPickerParticipant.createColorHover(colorInfo, new DefaultDocumentColorProvider(this._editorWorkerService), this._languageFeaturesService.colorProvider); if (!colorHoverResult) { return null; } diff --git a/src/vs/editor/contrib/suggest/test/browser/wordDistance.test.ts b/src/vs/editor/contrib/suggest/test/browser/wordDistance.test.ts index 65e3b8a4594..4fe23f5a66d 100644 --- a/src/vs/editor/contrib/suggest/test/browser/wordDistance.test.ts +++ b/src/vs/editor/contrib/suggest/test/browser/wordDistance.test.ts @@ -13,9 +13,8 @@ import { IRange } from 'vs/editor/common/core/range'; import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/core/wordHelper'; import * as languages from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { BaseEditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; import { EditorWorkerService } from 'vs/editor/browser/services/editorWorkerService'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { CompletionItem } from 'vs/editor/contrib/suggest/browser/suggest'; @@ -63,20 +62,20 @@ suite('suggest, word distance', function () { const service = new class extends EditorWorkerService { - private _worker = new EditorSimpleWorker(new class extends mock() { }, null); + private _worker = new BaseEditorSimpleWorker(); constructor() { - super(undefined, modelService, new class extends mock() { }, new NullLogService(), new TestLanguageConfigurationService(), new LanguageFeaturesService()); - this._worker.acceptNewModel({ + super(null!, modelService, new class extends mock() { }, new NullLogService(), new TestLanguageConfigurationService(), new LanguageFeaturesService()); + this._worker.$acceptNewModel({ url: model.uri.toString(), lines: model.getLinesContent(), EOL: model.getEOL(), versionId: model.getVersionId() }); - model.onDidChangeContent(e => this._worker.acceptModelChanged(model.uri.toString(), e)); + model.onDidChangeContent(e => this._worker.$acceptModelChanged(model.uri.toString(), e)); } override computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { - return this._worker.computeWordRanges(resource.toString(), range, DEFAULT_WORD_REGEXP.source, DEFAULT_WORD_REGEXP.flags); + return this._worker.$computeWordRanges(resource.toString(), range, DEFAULT_WORD_REGEXP.source, DEFAULT_WORD_REGEXP.flags); } }; diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index d1a70eba3d2..fbfe4e18b53 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -12,7 +12,7 @@ import { FontMeasurements } from 'vs/editor/browser/config/fontMeasurements'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { IWebWorkerOptions, MonacoWebWorker, createWebWorker as actualCreateWebWorker } from 'vs/editor/browser/services/webWorker'; +import { IWebWorkerOptions, MonacoWebWorker, createWebWorker as actualCreateWebWorker } from 'vs/editor/standalone/browser/standaloneWebWorker'; import { ApplyUpdateResult, ConfigurationChangedEvent, EditorOptions } from 'vs/editor/common/config/editorOptions'; import { EditorZoom } from 'vs/editor/common/config/editorZoom'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; @@ -21,7 +21,6 @@ import { IRange } from 'vs/editor/common/core/range'; import { EditorType, IDiffEditor } from 'vs/editor/common/editorCommon'; import * as languages from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { NullState, nullTokenize } from 'vs/editor/common/languages/nullTokenize'; import { FindMatch, ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model'; @@ -333,7 +332,7 @@ export function onDidChangeModelLanguage(listener: (e: { readonly model: ITextMo * Specify an AMD module to load that will `create` an object that will be proxied. */ export function createWebWorker(opts: IWebWorkerOptions): MonacoWebWorker { - return actualCreateWebWorker(StandaloneServices.get(IModelService), StandaloneServices.get(ILanguageConfigurationService), opts); + return actualCreateWebWorker(StandaloneServices.get(IModelService), opts); } /** diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 0656f65a308..e4f06ac453f 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -98,6 +98,7 @@ import { mainWindow } from 'vs/base/browser/window'; import { ResourceMap } from 'vs/base/common/map'; import { ITreeSitterParserService } from 'vs/editor/common/services/treeSitterParserService'; import { StandaloneTreeSitterParserService } from 'vs/editor/standalone/browser/standaloneTreeSitterService'; +import { IWorkerDescriptor } from 'vs/base/common/worker/simpleWorker'; class SimpleModel implements IResolvedTextEditorModel { @@ -1075,6 +1076,12 @@ class StandaloneContextMenuService extends ContextMenuService { } } +export const standaloneEditorWorkerDescriptor: IWorkerDescriptor = { + amdModuleId: 'vs/editor/common/services/editorSimpleWorker', + esmModuleLocation: undefined, + label: 'editorWorkerService' +}; + class StandaloneEditorWorkerService extends EditorWorkerService { constructor( @IModelService modelService: IModelService, @@ -1083,7 +1090,7 @@ class StandaloneEditorWorkerService extends EditorWorkerService { @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, ) { - super(undefined, modelService, configurationService, logService, languageConfigurationService, languageFeaturesService); + super(standaloneEditorWorkerDescriptor, modelService, configurationService, logService, languageConfigurationService, languageFeaturesService); } } diff --git a/src/vs/editor/browser/services/webWorker.ts b/src/vs/editor/standalone/browser/standaloneWebWorker.ts similarity index 75% rename from src/vs/editor/browser/services/webWorker.ts rename to src/vs/editor/standalone/browser/standaloneWebWorker.ts index 8ca8af228ed..74b1de918ff 100644 --- a/src/vs/editor/browser/services/webWorker.ts +++ b/src/vs/editor/standalone/browser/standaloneWebWorker.ts @@ -3,26 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { FileAccess } from 'vs/base/common/network'; import { getAllMethodNames } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; +import { IWorkerDescriptor } from 'vs/base/common/worker/simpleWorker'; import { EditorWorkerClient } from 'vs/editor/browser/services/editorWorkerService'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { IModelService } from 'vs/editor/common/services/model'; +import { standaloneEditorWorkerDescriptor } from 'vs/editor/standalone/browser/standaloneServices'; /** * 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(modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions): MonacoWebWorker { - return new MonacoWebWorkerImpl(undefined, modelService, languageConfigurationService, opts); -} - -/** - * @internal - */ -export function createWorkbenchWebWorker(modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions): MonacoWebWorker { - return new MonacoWebWorkerImpl(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, languageConfigurationService, opts); +export function createWebWorker(modelService: IModelService, opts: IWebWorkerOptions): MonacoWebWorker { + return new MonacoWebWorkerImpl(modelService, opts); } /** @@ -76,8 +69,13 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implement private _foreignModuleCreateData: any | null; private _foreignProxy: Promise | null; - constructor(workerMainLocation: URI | undefined, modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions) { - super(workerMainLocation, modelService, opts.keepIdleModels || false, opts.label, languageConfigurationService); + constructor(modelService: IModelService, opts: IWebWorkerOptions) { + const workerDescriptor: IWorkerDescriptor = { + amdModuleId: standaloneEditorWorkerDescriptor.amdModuleId, + esmModuleLocation: standaloneEditorWorkerDescriptor.esmModuleLocation, + label: opts.label, + }; + super(workerDescriptor, opts.keepIdleModels || false, modelService); this._foreignModuleId = opts.moduleId; this._foreignModuleCreateData = opts.createData || null; this._foreignModuleHost = opts.host || null; @@ -101,11 +99,11 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implement if (!this._foreignProxy) { this._foreignProxy = this._getProxy().then((proxy) => { const foreignHostMethods = this._foreignModuleHost ? getAllMethodNames(this._foreignModuleHost) : []; - return proxy.loadForeignModule(this._foreignModuleId, this._foreignModuleCreateData, foreignHostMethods).then((foreignMethods) => { + return proxy.$loadForeignModule(this._foreignModuleId, this._foreignModuleCreateData, foreignHostMethods).then((foreignMethods) => { this._foreignModuleCreateData = null; const proxyMethodRequest = (method: string, args: any[]): Promise => { - return proxy.fmr(method, args); + return proxy.$fmr(method, args); }; const createProxyMethod = (method: string, proxyMethodRequest: (method: string, args: any[]) => Promise): () => Promise => { @@ -132,6 +130,6 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implement } public withSyncedResources(resources: URI[]): Promise { - return this._withSyncedResources(resources).then(_ => this.getProxy()); + return this.workerWithSyncedResources(resources).then(_ => this.getProxy()); } } diff --git a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts index 781ff450211..f35928e297f 100644 --- a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts +++ b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts @@ -8,14 +8,14 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { TextEdit } from 'vs/editor/common/languages'; -import { EditorSimpleWorker, ICommonModel } from 'vs/editor/common/services/editorSimpleWorker'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; +import { BaseEditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { ICommonModel } from 'vs/editor/common/services/textModelSync/textModelSync.impl'; suite('EditorSimpleWorker', () => { ensureNoDisposablesAreLeakedInTestSuite(); - class WorkerWithModels extends EditorSimpleWorker { + class WorkerWithModels extends BaseEditorSimpleWorker { getModel(uri: string) { return this._getModel(uri); @@ -23,13 +23,13 @@ suite('EditorSimpleWorker', () => { addModel(lines: string[], eol: string = '\n') { const uri = 'test:file#' + Date.now(); - this.acceptNewModel({ + this.$acceptNewModel({ url: uri, versionId: 1, lines: lines, EOL: eol }); - return this._getModel(uri); + return this._getModel(uri)!; } } @@ -37,7 +37,7 @@ suite('EditorSimpleWorker', () => { let model: ICommonModel; setup(() => { - worker = new WorkerWithModels(null!, null); + worker = new WorkerWithModels(); model = worker.addModel([ 'This is line one', //16 'and this is line number two', //27 @@ -93,7 +93,7 @@ suite('EditorSimpleWorker', () => { test('MoreMinimal', () => { - return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: 'This is line One', range: new Range(1, 1, 1, 17) }], false).then(edits => { + return worker.$computeMoreMinimalEdits(model.uri.toString(), [{ text: 'This is line One', range: new Range(1, 1, 1, 17) }], false).then(edits => { assert.strictEqual(edits.length, 1); const [first] = edits; assert.strictEqual(first.text, 'O'); @@ -112,7 +112,7 @@ suite('EditorSimpleWorker', () => { ], '\n'); - const newEdits = await worker.computeMoreMinimalEdits(model.uri.toString(), [ + const newEdits = await worker.$computeMoreMinimalEdits(model.uri.toString(), [ { range: new Range(1, 1, 2, 1), text: 'one\ntwo\nthree\n', @@ -144,7 +144,7 @@ suite('EditorSimpleWorker', () => { '}' ], '\n'); - return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"a":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => { + return worker.$computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"a":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => { assert.strictEqual(edits.length, 0); }); }); @@ -157,7 +157,7 @@ suite('EditorSimpleWorker', () => { '}' ], '\n'); - return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"b":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => { + return worker.$computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"b":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => { assert.strictEqual(edits.length, 1); const [first] = edits; assert.strictEqual(first.text, 'b'); @@ -173,7 +173,7 @@ suite('EditorSimpleWorker', () => { '}' // 3 ]); - return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '\n', range: new Range(3, 2, 4, 1000) }], false).then(edits => { + return worker.$computeMoreMinimalEdits(model.uri.toString(), [{ text: '\n', range: new Range(3, 2, 4, 1000) }], false).then(edits => { assert.strictEqual(edits.length, 1); const [first] = edits; assert.strictEqual(first.text, '\n'); @@ -184,7 +184,7 @@ suite('EditorSimpleWorker', () => { async function testEdits(lines: string[], edits: TextEdit[]): Promise { const model = worker.addModel(lines); - const smallerEdits = await worker.computeHumanReadableDiff( + const smallerEdits = await worker.$computeHumanReadableDiff( model.uri.toString(), edits, { ignoreTrimWhitespace: false, maxComputationTimeMs: 0, computeMoves: false } @@ -286,7 +286,7 @@ suite('EditorSimpleWorker', () => { 'f f' // 2 ]); - return worker.textualSuggest([model.uri.toString()], 'f', '[a-z]+', 'img').then((result) => { + return worker.$textualSuggest([model.uri.toString()], 'f', '[a-z]+', 'img').then((result) => { if (!result) { assert.ok(false); } diff --git a/src/vs/editor/test/common/services/testEditorWorkerService.ts b/src/vs/editor/test/common/services/testEditorWorkerService.ts index e7d5154f9f6..078cb885e3b 100644 --- a/src/vs/editor/test/common/services/testEditorWorkerService.ts +++ b/src/vs/editor/test/common/services/testEditorWorkerService.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { DiffAlgorithmName, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; -import { TextEdit, IInplaceReplaceSupportResult } from 'vs/editor/common/languages'; +import { TextEdit, IInplaceReplaceSupportResult, IColorInformation } from 'vs/editor/common/languages'; import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { SectionHeader } from 'vs/editor/common/services/findSectionHeaders'; @@ -27,4 +27,5 @@ export class TestEditorWorkerService implements IEditorWorkerService { canNavigateValueSet(resource: URI): boolean { return false; } async navigateValueSet(resource: URI, range: IRange, up: boolean): Promise { return null; } async findSectionHeaders(uri: URI): Promise { return []; } + async computeDefaultDocumentColors(uri: URI): Promise { return null; } } diff --git a/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.ts b/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.ts index 14d6a473c09..4d04422e391 100644 --- a/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.ts +++ b/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorker.ts @@ -6,12 +6,16 @@ import { basename } from 'vs/base/common/path'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { URI } from 'vs/base/common/uri'; -import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; +import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; import { IV8Profile, Utils } from 'vs/platform/profiling/common/profiling'; import { IProfileModel, BottomUpSample, buildModel, BottomUpNode, processNode, CdpCallFrame } from 'vs/platform/profiling/common/profilingModel'; import { BottomUpAnalysis, IProfileAnalysisWorker, ProfilingOutput } from 'vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService'; -export function create(): IRequestHandler { +/** + * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle + */ +export function create(workerServer: IWorkerServer): IRequestHandler { return new ProfileAnalysisWorker(); } @@ -19,7 +23,7 @@ class ProfileAnalysisWorker implements IRequestHandler, IProfileAnalysisWorker { _requestHandlerBrand: any; - analyseBottomUp(profile: IV8Profile): BottomUpAnalysis { + $analyseBottomUp(profile: IV8Profile): BottomUpAnalysis { if (!Utils.isValidProfile(profile)) { return { kind: ProfilingOutput.Irrelevant, samples: [] }; } @@ -37,7 +41,7 @@ class ProfileAnalysisWorker implements IRequestHandler, IProfileAnalysisWorker { return { kind: ProfilingOutput.Interesting, samples }; } - analyseByUrlCategory(profile: IV8Profile, categories: [url: URI, category: string][]): [category: string, aggregated: number][] { + $analyseByUrlCategory(profile: IV8Profile, categories: [url: URI, category: string][]): [category: string, aggregated: number][] { // build search tree const searchTree = TernarySearchTree.forUris(); diff --git a/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts b/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts index f862cc5944a..1f308faa5f5 100644 --- a/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts +++ b/src/vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService.ts @@ -4,10 +4,9 @@ *--------------------------------------------------------------------------------------------*/ -import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory'; -import { FileAccess } from 'vs/base/common/network'; +import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory'; import { URI } from 'vs/base/common/uri'; -import { SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; +import { Proxied } from 'vs/base/common/worker/simpleWorker'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; @@ -16,7 +15,6 @@ import { BottomUpSample } from 'vs/platform/profiling/common/profilingModel'; import { reportSample } from 'vs/platform/profiling/common/profilingTelemetrySpec'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; - export const enum ProfilingOutput { Failure, Irrelevant, @@ -42,8 +40,6 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService { declare _serviceBrand: undefined; - private readonly _workerFactory = new DefaultWorkerFactory(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), 'CpuProfileAnalysis'); - constructor( @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILogService private readonly _logService: ILogService, @@ -51,14 +47,13 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService { private async _withWorker(callback: (worker: Proxied) => Promise): Promise { - const worker = new SimpleWorkerClient, {}>( - this._workerFactory, + const worker = createWebWorker( 'vs/platform/profiling/electron-sandbox/profileAnalysisWorker', - { /* host */ } + 'CpuProfileAnalysis' ); try { - const r = await callback(await worker.getProxyObject()); + const r = await callback(worker.proxy); return r; } finally { worker.dispose(); @@ -67,7 +62,7 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService { async analyseBottomUp(profile: IV8Profile, callFrameClassifier: IScriptUrlClassifier, perfBaseline: number, sendAsErrorTelemtry: boolean): Promise { return this._withWorker(async worker => { - const result = await worker.analyseBottomUp(profile); + const result = await worker.$analyseBottomUp(profile); if (result.kind === ProfilingOutput.Interesting) { for (const sample of result.samples) { reportSample({ @@ -83,7 +78,7 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService { async analyseByLocation(profile: IV8Profile, locations: [location: URI, id: string][]): Promise<[category: string, aggregated: number][]> { return this._withWorker(async worker => { - const result = await worker.analyseByUrlCategory(profile, locations); + const result = await worker.$analyseByUrlCategory(profile, locations); return result; }); } @@ -104,15 +99,8 @@ export interface CategoryAnalysis { } export interface IProfileAnalysisWorker { - analyseBottomUp(profile: IV8Profile): BottomUpAnalysis; - analyseByUrlCategory(profile: IV8Profile, categories: [url: URI, category: string][]): [category: string, aggregated: number][]; + $analyseBottomUp(profile: IV8Profile): BottomUpAnalysis; + $analyseByUrlCategory(profile: IV8Profile, categories: [url: URI, category: string][]): [category: string, aggregated: number][]; } -// TODO@jrieken move into worker logic -type Proxied = { [K in keyof T]: T[K] extends (...args: infer A) => infer R - ? (...args: A) => Promise> - : never -}; - - registerSingleton(IProfileAnalysisWorkerService, ProfileAnalysisWorkerService, InstantiationType.Delayed); diff --git a/src/vs/workbench/api/worker/extensionHostWorker.ts b/src/vs/workbench/api/worker/extensionHostWorker.ts index 86136a080e6..b98117318b7 100644 --- a/src/vs/workbench/api/worker/extensionHostWorker.ts +++ b/src/vs/workbench/api/worker/extensionHostWorker.ts @@ -234,6 +234,10 @@ function isInitMessage(a: any): a is IInitMessage { return !!a && typeof a === 'object' && a.type === 'vscode.init' && a.data instanceof Map; } +/** + * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle + */ export function create(): { onmessage: (message: any) => void } { performance.mark(`code/extHost/willConnectToRenderer`); const res = new ExtensionWorker(); diff --git a/src/vs/workbench/contrib/codeEditor/browser/workbenchEditorWorkerService.ts b/src/vs/workbench/contrib/codeEditor/browser/workbenchEditorWorkerService.ts index 5eb837a9509..ba3005b0e9b 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/workbenchEditorWorkerService.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/workbenchEditorWorkerService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { FileAccess } from 'vs/base/common/network'; +import { WorkerDescriptor } from 'vs/base/browser/defaultWorkerFactory'; import { EditorWorkerService } from 'vs/editor/browser/services/editorWorkerService'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; @@ -19,6 +19,7 @@ export class WorkbenchEditorWorkerService extends EditorWorkerService { @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, ) { - super(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, configurationService, logService, languageConfigurationService, languageFeaturesService); + const workerDescriptor = new WorkerDescriptor('vs/editor/common/services/editorSimpleWorker', 'editorWorkerService'); + super(workerDescriptor, modelService, configurationService, logService, languageConfigurationService, languageFeaturesService); } } diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts b/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts index d9b64666384..409c170c448 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/testWorkerService.ts @@ -9,7 +9,7 @@ import { IModelService } from 'vs/editor/common/services/model'; import { assertType } from 'vs/base/common/types'; import { DiffAlgorithmName, IEditorWorkerService, ILineChange } from 'vs/editor/common/services/editorWorker'; import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; -import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { BaseEditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { MovedText } from 'vs/editor/common/diff/linesDiffComputer'; import { LineRangeMapping, DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; @@ -18,7 +18,7 @@ import { TextEdit } from 'vs/editor/common/languages'; export class TestWorkerService extends mock() { - private readonly _worker = new EditorSimpleWorker(null!, null); + private readonly _worker = new BaseEditorSimpleWorker(); constructor(@IModelService private readonly _modelService: IModelService) { super(); @@ -36,21 +36,21 @@ export class TestWorkerService extends mock() { assertType(originalModel); assertType(modifiedModel); - this._worker.acceptNewModel({ + this._worker.$acceptNewModel({ url: originalModel.uri.toString(), versionId: originalModel.getVersionId(), lines: originalModel.getLinesContent(), EOL: originalModel.getEOL(), }); - this._worker.acceptNewModel({ + this._worker.$acceptNewModel({ url: modifiedModel.uri.toString(), versionId: modifiedModel.getVersionId(), lines: modifiedModel.getLinesContent(), EOL: modifiedModel.getEOL(), }); - const result = await this._worker.computeDiff(originalModel.uri.toString(), modifiedModel.uri.toString(), options, algorithm); + const result = await this._worker.$computeDiff(originalModel.uri.toString(), modifiedModel.uri.toString(), options, algorithm); if (!result) { return result; } diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts index e643b5f0534..da01847d2c9 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookWorkerServiceImpl.ts @@ -5,15 +5,13 @@ import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; -import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory'; +import { IWorkerClient, Proxied } from 'vs/base/common/worker/simpleWorker'; +import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { IMainCellDto, INotebookDiffResult, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookEditorSimpleWorker } from 'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker'; -import { INotebookWorkerHost } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerHost'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; -import { FileAccess } from 'vs/base/common/network'; export class NotebookEditorWorkerServiceImpl extends Disposable implements INotebookEditorWorkerService { declare readonly _serviceBrand: undefined; @@ -59,23 +57,18 @@ class WorkerManager extends Disposable { withWorker(): Promise { // this._lastWorkerUsedTime = (new Date()).getTime(); if (!this._editorWorkerClient) { - this._editorWorkerClient = new NotebookWorkerClient(this._notebookService, 'notebookEditorWorkerService'); + this._editorWorkerClient = new NotebookWorkerClient(this._notebookService); } return Promise.resolve(this._editorWorkerClient); } } -interface IWorkerClient { - getProxyObject(): Promise; - dispose(): void; -} - class NotebookEditorModelManager extends Disposable { private _syncedModels: { [modelUrl: string]: IDisposable } = Object.create(null); private _syncedModelsLastUsedTime: { [modelUrl: string]: number } = Object.create(null); constructor( - private readonly _proxy: NotebookEditorSimpleWorker, + private readonly _proxy: Proxied, private readonly _notebookService: INotebookService ) { super(); @@ -102,7 +95,7 @@ class NotebookEditorModelManager extends Disposable { const modelUrl = resource.toString(); - this._proxy.acceptNewModel( + this._proxy.$acceptNewModel( model.uri.toString(), { cells: model.cells.map(cell => ({ @@ -162,7 +155,7 @@ class NotebookEditorModelManager extends Disposable { return data; }); - this._proxy.acceptModelChanged(modelUrl.toString(), { + this._proxy.$acceptModelChanged(modelUrl.toString(), { rawEvents: dto, versionId: event.versionId }); @@ -172,7 +165,7 @@ class NotebookEditorModelManager extends Disposable { this._stopModelSync(modelUrl); })); toDispose.add(toDisposable(() => { - this._proxy.acceptRemovedModel(modelUrl); + this._proxy.$acceptRemovedModel(modelUrl); })); this._syncedModels[modelUrl] = toDispose; @@ -186,72 +179,47 @@ class NotebookEditorModelManager extends Disposable { } } -class NotebookWorkerHost implements INotebookWorkerHost { - - private readonly _workerClient: NotebookWorkerClient; - - constructor(workerClient: NotebookWorkerClient) { - this._workerClient = workerClient; - } - - // foreign host request - public fhr(method: string, args: any[]): Promise { - return this._workerClient.fhr(method, args); - } -} - class NotebookWorkerClient extends Disposable { private _worker: IWorkerClient | null; - private readonly _workerFactory: DefaultWorkerFactory; private _modelManager: NotebookEditorModelManager | null; - constructor(private readonly _notebookService: INotebookService, label: string) { + constructor(private readonly _notebookService: INotebookService) { super(); - this._workerFactory = new DefaultWorkerFactory(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), label); this._worker = null; this._modelManager = null; } - // foreign host request - public fhr(method: string, args: any[]): Promise { - throw new Error(`Not implemented!`); - } - computeDiff(original: URI, modified: URI) { - return this._withSyncedResources([original, modified]).then(proxy => { - return proxy.computeDiff(original.toString(), modified.toString()); - }); + const proxy = this._ensureSyncedResources([original, modified]); + return proxy.$computeDiff(original.toString(), modified.toString()); } canPromptRecommendation(modelUri: URI) { - return this._withSyncedResources([modelUri]).then(proxy => { - return proxy.canPromptRecommendation(modelUri.toString()); - }); + const proxy = this._ensureSyncedResources([modelUri]); + return proxy.$canPromptRecommendation(modelUri.toString()); } - private _getOrCreateModelManager(proxy: NotebookEditorSimpleWorker): NotebookEditorModelManager { + private _getOrCreateModelManager(proxy: Proxied): NotebookEditorModelManager { if (!this._modelManager) { this._modelManager = this._register(new NotebookEditorModelManager(proxy, this._notebookService)); } return this._modelManager; } - protected _withSyncedResources(resources: URI[]): Promise { - return this._getProxy().then((proxy) => { - this._getOrCreateModelManager(proxy).ensureSyncedResources(resources); - return proxy; - }); + protected _ensureSyncedResources(resources: URI[]): Proxied { + const proxy = this._getOrCreateWorker().proxy; + this._getOrCreateModelManager(proxy).ensureSyncedResources(resources); + return proxy; } private _getOrCreateWorker(): IWorkerClient { if (!this._worker) { try { - this._worker = this._register(new SimpleWorkerClient( - this._workerFactory, + this._worker = this._register(createWebWorker( 'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker', - new NotebookWorkerHost(this) + 'notebookEditorWorkerService' )); } catch (err) { // logOnceWebWorkerWarning(err); @@ -261,15 +229,4 @@ class NotebookWorkerClient extends Disposable { } return this._worker; } - - protected _getProxy(): Promise { - return this._getOrCreateWorker().getProxyObject().then(undefined, (err) => { - // logOnceWebWorkerWarning(err); - // this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null)); - // return this._getOrCreateWorker().getProxyObject(); - throw (err); - }); - } - - } diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts index b0b8c5153ab..aea29952839 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts @@ -6,12 +6,11 @@ import { ISequence, LcsDiff } from 'vs/base/common/diff/diff'; import { doHash, hash, numberHash } from 'vs/base/common/hash'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; +import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; import * as model from 'vs/editor/common/model'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { CellKind, ICellDto2, IMainCellDto, INotebookDiffResult, IOutputDto, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellTextModelSplice, NotebookData, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Range } from 'vs/editor/common/core/range'; -import { INotebookWorkerHost } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerHost'; import { VSBuffer } from 'vs/base/common/buffer'; import { SearchParams } from 'vs/editor/common/model/textModelSearch'; @@ -191,7 +190,7 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable dispose(): void { } - public acceptNewModel(uri: string, data: NotebookData): void { + public $acceptNewModel(uri: string, data: NotebookData): void { this._models[uri] = new MirrorNotebookDocument(URI.parse(uri), data.cells.map(dto => new MirrorCell( (dto as unknown as IMainCellDto).handle, dto.source, @@ -202,19 +201,19 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable )), data.metadata); } - public acceptModelChanged(strURL: string, event: NotebookCellsChangedEventDto) { + public $acceptModelChanged(strURL: string, event: NotebookCellsChangedEventDto) { const model = this._models[strURL]; model?.acceptModelChanged(event); } - public acceptRemovedModel(strURL: string): void { + public $acceptRemovedModel(strURL: string): void { if (!this._models[strURL]) { return; } delete this._models[strURL]; } - computeDiff(originalUrl: string, modifiedUrl: string): INotebookDiffResult { + $computeDiff(originalUrl: string, modifiedUrl: string): INotebookDiffResult { const original = this._getModel(originalUrl); const modified = this._getModel(modifiedUrl); @@ -276,7 +275,7 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable }; } - canPromptRecommendation(modelUrl: string): boolean { + $canPromptRecommendation(modelUrl: string): boolean { const model = this._getModel(modelUrl); const cells = model.cells; @@ -315,9 +314,9 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable } /** - * Called on the worker side - * @internal + * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle */ -export function create(host: INotebookWorkerHost): IRequestHandler { +export function create(workerServer: IWorkerServer): IRequestHandler { return new NotebookEditorSimpleWorker(); } diff --git a/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts b/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts index b9a82cf5584..afadcfbcb15 100644 --- a/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts +++ b/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts @@ -9,24 +9,24 @@ import { IModelService } from 'vs/editor/common/services/model'; import { ILink } from 'vs/editor/common/languages'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/services/output/common/output'; -import { MonacoWebWorker, createWorkbenchWebWorker } from 'vs/editor/browser/services/webWorker'; -import { ICreateData, OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer'; +import { OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory'; +import { IWorkerClient } from 'vs/base/common/worker/simpleWorker'; +import { WorkerTextModelSyncClient } from 'vs/editor/common/services/textModelSync/textModelSync.impl'; export class OutputLinkProvider extends Disposable { private static readonly DISPOSE_WORKER_TIME = 3 * 60 * 1000; // dispose worker after 3 minutes of inactivity - private worker?: MonacoWebWorker; + private worker?: OutputLinkWorkerClient; private disposeWorkerScheduler: RunOnceScheduler; private linkProviderRegistration: IDisposable | undefined; constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IModelService private readonly modelService: IModelService, - @ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService, @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, ) { super(); @@ -65,28 +65,18 @@ export class OutputLinkProvider extends Disposable { this.disposeWorkerScheduler.cancel(); } - private getOrCreateWorker(): MonacoWebWorker { + private getOrCreateWorker(): OutputLinkWorkerClient { this.disposeWorkerScheduler.schedule(); if (!this.worker) { - const createData: ICreateData = { - workspaceFolders: this.contextService.getWorkspace().folders.map(folder => folder.uri.toString()) - }; - - this.worker = createWorkbenchWebWorker(this.modelService, this.languageConfigurationService, { - moduleId: 'vs/workbench/contrib/output/common/outputLinkComputer', - createData, - label: 'outputLinkComputer' - }); + this.worker = new OutputLinkWorkerClient(this.contextService, this.modelService); } return this.worker; } private async provideLinks(modelUri: URI): Promise { - const linkComputer = await this.getOrCreateWorker().withSyncedResources([modelUri]); - - return linkComputer.computeLinks(modelUri.toString()); + return this.getOrCreateWorker().provideLinks(modelUri); } private disposeWorker(): void { @@ -96,3 +86,32 @@ export class OutputLinkProvider extends Disposable { } } } + +class OutputLinkWorkerClient extends Disposable { + private readonly _workerClient: IWorkerClient; + private readonly _workerTextModelSyncClient: WorkerTextModelSyncClient; + private readonly _initializeBarrier: Promise; + + constructor( + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IModelService modelService: IModelService, + ) { + super(); + this._workerClient = this._register(createWebWorker( + 'vs/workbench/contrib/output/common/outputLinkComputer', + 'outputLinkComputer' + )); + this._workerTextModelSyncClient = WorkerTextModelSyncClient.create(this._workerClient, modelService); + this._initializeBarrier = this._ensureWorkspaceFolders(); + } + + private async _ensureWorkspaceFolders(): Promise { + await this._workerClient.proxy.$setWorkspaceFolders(this.contextService.getWorkspace().folders.map(folder => folder.uri.toString())); + } + + public async provideLinks(modelUri: URI): Promise { + await this._initializeBarrier; + await this._workerTextModelSyncClient.ensureSyncedResources([modelUri]); + return this._workerClient.proxy.$computeLinks(modelUri.toString()); + } +} diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookWorkerHost.ts b/src/vs/workbench/contrib/output/common/outputLinkComputer.esm.ts similarity index 68% rename from src/vs/workbench/contrib/notebook/common/services/notebookWorkerHost.ts rename to src/vs/workbench/contrib/output/common/outputLinkComputer.esm.ts index 44a4e22da79..2d3fd96b4e8 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookWorkerHost.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkComputer.esm.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export interface INotebookWorkerHost { - // foreign host request - fhr(method: string, args: any[]): Promise; -} +import { create } from './outputLinkComputer'; +import { bootstrapSimpleWorker } from 'vs/base/common/worker/simpleWorkerBootstrap'; + +bootstrapSimpleWorker(create); diff --git a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts index 0de2f9f2e15..aad9864e4f1 100644 --- a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IMirrorModel, IWorkerContext } from 'vs/editor/common/services/editorSimpleWorker'; import { ILink } from 'vs/editor/common/languages'; import { URI } from 'vs/base/common/uri'; import * as extpath from 'vs/base/common/extpath'; @@ -12,28 +11,33 @@ import * as strings from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { isWindows } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; - -export interface ICreateData { - workspaceFolders: string[]; -} +import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; +import { WorkerTextModelSyncServer, ICommonModel } from 'vs/editor/common/services/textModelSync/textModelSync.impl'; export interface IResourceCreator { toResource: (folderRelativePath: string) => URI | null; } -export class OutputLinkComputer { +export class OutputLinkComputer implements IRequestHandler { + _requestHandlerBrand: any; + + private readonly workerTextModelSyncServer = new WorkerTextModelSyncServer(); private patterns = new Map(); - constructor(private ctx: IWorkerContext, createData: ICreateData) { - this.computePatterns(createData); + constructor(workerServer: IWorkerServer) { + this.workerTextModelSyncServer.bindToServer(workerServer); } - private computePatterns(createData: ICreateData): void { + $setWorkspaceFolders(workspaceFolders: string[]) { + this.computePatterns(workspaceFolders); + } + + private computePatterns(_workspaceFolders: string[]): void { // Produce patterns for each workspace root we are configured with // This means that we will be able to detect links for paths that // contain any of the workspace roots as segments. - const workspaceFolders = createData.workspaceFolders + const workspaceFolders = _workspaceFolders .sort((resourceStrA, resourceStrB) => resourceStrB.length - resourceStrA.length) // longest paths first (for https://github.com/microsoft/vscode/issues/88121) .map(resourceStr => URI.parse(resourceStr)); @@ -43,13 +47,11 @@ export class OutputLinkComputer { } } - private getModel(uri: string): IMirrorModel | undefined { - const models = this.ctx.getMirrorModels(); - - return models.find(model => model.uri.toString() === uri); + private getModel(uri: string): ICommonModel | undefined { + return this.workerTextModelSyncServer.getModel(uri); } - computeLinks(uri: string): ILink[] { + $computeLinks(uri: string): ILink[] { const model = this.getModel(uri); if (!model) { return []; @@ -179,7 +181,10 @@ export class OutputLinkComputer { } } -// Export this function because this will be called by the web worker for computing links -export function create(ctx: IWorkerContext, createData: ICreateData): OutputLinkComputer { - return new OutputLinkComputer(ctx, createData); +/** + * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle + */ +export function create(workerServer: IWorkerServer): OutputLinkComputer { + return new OutputLinkComputer(workerServer); } diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.esm.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.esm.ts index 46e5fb85eba..9e60f634136 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.esm.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.esm.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LanguageDetectionSimpleWorker } from './languageDetectionSimpleWorker'; +import { create } from './languageDetectionSimpleWorker'; import { bootstrapSimpleWorker } from 'vs/base/common/worker/simpleWorkerBootstrap'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; -bootstrapSimpleWorker(host => new LanguageDetectionSimpleWorker(host, () => { return {}; })); +bootstrapSimpleWorker(create); diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts index ad75f10f039..8067c690fa3 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts @@ -6,38 +6,48 @@ import type { ModelOperations, ModelResult } from '@vscode/vscode-languagedetection'; import { importAMDNodeModule } from 'vs/amdX'; import { StopWatch } from 'vs/base/common/stopwatch'; -import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; -import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; -import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; +import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; +import { LanguageDetectionWorkerHost, ILanguageDetectionWorker } from 'vs/workbench/services/languageDetection/browser/languageDetectionWorker.protocol'; +import { WorkerTextModelSyncServer } from 'vs/editor/common/services/textModelSync/textModelSync.impl'; type RegexpModel = { detect: (inp: string, langBiases: Record, supportedLangs?: string[]) => string | undefined }; /** - * Called on the worker side - * @internal + * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle */ -export function create(host: IEditorWorkerHost): IRequestHandler { - return new LanguageDetectionSimpleWorker(host, null); +export function create(workerServer: IWorkerServer): IRequestHandler { + return new LanguageDetectionSimpleWorker(workerServer); } /** * @internal */ -export class LanguageDetectionSimpleWorker extends EditorSimpleWorker { +export class LanguageDetectionSimpleWorker implements ILanguageDetectionWorker { + _requestHandlerBrand: any; + private static readonly expectedRelativeConfidence = 0.2; private static readonly positiveConfidenceCorrectionBucket1 = 0.05; private static readonly positiveConfidenceCorrectionBucket2 = 0.025; private static readonly negativeConfidenceCorrection = 0.5; + private readonly _workerTextModelSyncServer = new WorkerTextModelSyncServer(); + + private readonly _host: LanguageDetectionWorkerHost; private _regexpModel: RegexpModel | undefined; private _regexpLoadFailed: boolean = false; private _modelOperations: ModelOperations | undefined; private _loadFailed: boolean = false; - private modelIdToCoreId = new Map(); + private modelIdToCoreId = new Map(); - public async detectLanguage(uri: string, langBiases: Record | undefined, preferHistory: boolean, supportedLangs?: string[]): Promise { + constructor(workerServer: IWorkerServer) { + this._host = LanguageDetectionWorkerHost.getChannel(workerServer); + this._workerTextModelSyncServer.bindToServer(workerServer); + } + + public async $detectLanguage(uri: string, langBiases: Record | undefined, preferHistory: boolean, supportedLangs?: string[]): Promise { const languages: string[] = []; const confidences: number[] = []; const stopWatch = new StopWatch(); @@ -47,7 +57,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker { const neuralResolver = async () => { for await (const language of this.detectLanguagesImpl(documentTextSample)) { if (!this.modelIdToCoreId.has(language.languageId)) { - this.modelIdToCoreId.set(language.languageId, await this._host.fhr('getLanguageId', [language.languageId])); + this.modelIdToCoreId.set(language.languageId, await this._host.$getLanguageId(language.languageId)); } const coreId = this.modelIdToCoreId.get(language.languageId); if (coreId && (!supportedLangs?.length || supportedLangs.includes(coreId))) { @@ -58,7 +68,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker { stopWatch.stop(); if (languages.length) { - this._host.fhr('sendTelemetryEvent', [languages, confidences, stopWatch.elapsed()]); + this._host.$sendTelemetryEvent(languages, confidences, stopWatch.elapsed()); return languages[0]; } return undefined; @@ -82,7 +92,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker { } private getTextForDetection(uri: string): string | undefined { - const editorModel = this._getModel(uri); + const editorModel = this._workerTextModelSyncServer.getModel(uri); if (!editorModel) { return; } const end = editorModel.positionAt(10000); @@ -102,7 +112,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker { if (this._regexpModel) { return this._regexpModel; } - const uri: string = await this._host.fhr('getRegexpModelUri', []); + const uri: string = await this._host.$getRegexpModelUri(); try { this._regexpModel = await importAMDNodeModule(uri, '') as RegexpModel; return this._regexpModel; @@ -137,11 +147,11 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker { return this._modelOperations; } - const uri: string = await this._host.fhr('getIndexJsUri', []); + const uri: string = await this._host.$getIndexJsUri(); const { ModelOperations } = await importAMDNodeModule(uri, '') as typeof import('@vscode/vscode-languagedetection'); this._modelOperations = new ModelOperations({ modelJsonLoaderFunc: async () => { - const response = await fetch(await this._host.fhr('getModelJsonUri', [])); + const response = await fetch(await this._host.$getModelJsonUri()); try { const modelJSON = await response.json(); return modelJSON; @@ -151,7 +161,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker { } }, weightsLoaderFunc: async () => { - const response = await fetch(await this._host.fhr('getWeightsUri', [])); + const response = await fetch(await this._host.$getWeightsUri()); const buffer = await response.arrayBuffer(); return buffer; } diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorker.protocol.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorker.protocol.ts new file mode 100644 index 00000000000..f0f494b5d80 --- /dev/null +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorker.protocol.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkerClient, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; + +export abstract class LanguageDetectionWorkerHost { + public static CHANNEL_NAME = 'languageDetectionWorkerHost'; + public static getChannel(workerServer: IWorkerServer): LanguageDetectionWorkerHost { + return workerServer.getChannel(LanguageDetectionWorkerHost.CHANNEL_NAME); + } + public static setChannel(workerClient: IWorkerClient, obj: LanguageDetectionWorkerHost): void { + workerClient.setChannel(LanguageDetectionWorkerHost.CHANNEL_NAME, obj); + } + + abstract $getIndexJsUri(): Promise; + abstract $getLanguageId(languageIdOrExt: string | undefined): Promise; + abstract $sendTelemetryEvent(languages: string[], confidences: number[], timeSpent: number): Promise; + abstract $getRegexpModelUri(): Promise; + abstract $getModelJsonUri(): Promise; + abstract $getWeightsUri(): Promise; +} + +export interface ILanguageDetectionWorker { + $detectLanguage(uri: string, langBiases: Record | undefined, preferHistory: boolean, supportedLangs?: string[]): Promise; +} diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts index 29256414c6c..5df93c29ebf 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts @@ -12,12 +12,9 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { URI } from 'vs/base/common/uri'; import { isWeb } from 'vs/base/common/platform'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { LanguageDetectionSimpleWorker } from 'vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker'; import { IModelService } from 'vs/editor/common/services/model'; -import { SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; +import { IWorkerClient } from 'vs/base/common/worker/simpleWorker'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { EditorWorkerClient, EditorWorkerHost } from 'vs/editor/browser/services/editorWorkerService'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -25,6 +22,9 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { LRUCache } from 'vs/base/common/map'; import { ILogService } from 'vs/platform/log/common/log'; import { canASAR } from 'vs/base/common/amd'; +import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory'; +import { WorkerTextModelSyncClient } from 'vs/editor/common/services/textModelSync/textModelSync.impl'; +import { ILanguageDetectionWorker, LanguageDetectionWorkerHost } from 'vs/workbench/services/languageDetection/browser/languageDetectionWorker.protocol'; const TOP_LANG_COUNTS = 12; @@ -62,8 +62,7 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet @IEditorService private readonly _editorService: IEditorService, @ITelemetryService telemetryService: ITelemetryService, @IStorageService storageService: IStorageService, - @ILogService private readonly _logService: ILogService, - @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService + @ILogService private readonly _logService: ILogService ) { super(); @@ -85,7 +84,6 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet useAsar ? FileAccess.asBrowserUri(`${regexpModuleLocationAsar}/dist/index.js`).toString(true) : FileAccess.asBrowserUri(`${regexpModuleLocation}/dist/index.js`).toString(true), - languageConfigurationService )); this.initEditorOpenedListeners(storageService); @@ -179,80 +177,45 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet } } -export interface IWorkerClient { - getProxyObject(): Promise; - dispose(): void; -} - -export class LanguageDetectionWorkerHost { - constructor( - private _indexJsUri: string, - private _modelJsonUri: string, - private _weightsUri: string, - private _telemetryService: ITelemetryService, - ) { - } - - async getIndexJsUri() { - return this._indexJsUri; - } - - async getModelJsonUri() { - return this._modelJsonUri; - } - - async getWeightsUri() { - return this._weightsUri; - } - - async sendTelemetryEvent(languages: string[], confidences: number[], timeSpent: number): Promise { - type LanguageDetectionStats = { languages: string; confidences: string; timeSpent: number }; - type LanguageDetectionStatsClassification = { - owner: 'TylerLeonhardt'; - comment: 'Helps understand how effective language detection is via confidences and how long it takes to run'; - languages: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The languages that are guessed' }; - confidences: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The confidences of each language guessed' }; - timeSpent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The time it took to run language detection' }; - }; - - this._telemetryService.publicLog2('automaticlanguagedetection.stats', { - languages: languages.join(','), - confidences: confidences.join(','), - timeSpent - }); - } -} - -export class LanguageDetectionWorkerClient extends EditorWorkerClient { - private workerPromise: Promise> | undefined; +export class LanguageDetectionWorkerClient extends Disposable { + private worker: { + workerClient: IWorkerClient; + workerTextModelSyncClient: WorkerTextModelSyncClient; + } | undefined; constructor( - modelService: IModelService, + private readonly _modelService: IModelService, private readonly _languageService: ILanguageService, private readonly _telemetryService: ITelemetryService, private readonly _indexJsUri: string, private readonly _modelJsonUri: string, private readonly _weightsUri: string, private readonly _regexpModelUri: string, - languageConfigurationService: ILanguageConfigurationService, ) { - super(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, true, 'languageDetectionWorkerService', languageConfigurationService); + super(); } - private _getOrCreateLanguageDetectionWorker(): Promise> { - if (this.workerPromise) { - return this.workerPromise; - } - - this.workerPromise = new Promise((resolve, reject) => { - resolve(this._register(new SimpleWorkerClient( - this._workerFactory, + private _getOrCreateLanguageDetectionWorker(): { + workerClient: IWorkerClient; + workerTextModelSyncClient: WorkerTextModelSyncClient; + } { + if (!this.worker) { + const workerClient = this._register(createWebWorker( 'vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker', - new EditorWorkerHost(this) - ))); - }); - - return this.workerPromise; + 'languageDetectionWorkerService' + )); + LanguageDetectionWorkerHost.setChannel(workerClient, { + $getIndexJsUri: async () => this.getIndexJsUri(), + $getLanguageId: async (languageIdOrExt) => this.getLanguageId(languageIdOrExt), + $sendTelemetryEvent: async (languages, confidences, timeSpent) => this.sendTelemetryEvent(languages, confidences, timeSpent), + $getRegexpModelUri: async () => this.getRegexpModelUri(), + $getModelJsonUri: async () => this.getModelJsonUri(), + $getWeightsUri: async () => this.getWeightsUri(), + }); + const workerTextModelSyncClient = WorkerTextModelSyncClient.create(workerClient, this._modelService); + this.worker = { workerClient, workerTextModelSyncClient }; + } + return this.worker; } private _guessLanguageIdByUri(uri: URI): string | undefined { @@ -263,30 +226,6 @@ export class LanguageDetectionWorkerClient extends EditorWorkerClient { return undefined; } - protected override async _getProxy(): Promise { - return (await this._getOrCreateLanguageDetectionWorker()).getProxyObject(); - } - - // foreign host request - public override async fhr(method: string, args: any[]): Promise { - switch (method) { - case 'getIndexJsUri': - return this.getIndexJsUri(); - case 'getModelJsonUri': - return this.getModelJsonUri(); - case 'getWeightsUri': - return this.getWeightsUri(); - case 'getRegexpModelUri': - return this.getRegexpModelUri(); - case 'getLanguageId': - return this.getLanguageId(args[0]); - case 'sendTelemetryEvent': - return this.sendTelemetryEvent(args[0], args[1], args[2]); - default: - return super.fhr(method, args); - } - } - async getIndexJsUri() { return this._indexJsUri; } @@ -332,8 +271,9 @@ export class LanguageDetectionWorkerClient extends EditorWorkerClient { return quickGuess; } - await this._withSyncedResources([resource]); - const modelId = await (await this._getProxy()).detectLanguage(resource.toString(), langBiases, preferHistory, supportedLangs); + const { workerClient, workerTextModelSyncClient } = this._getOrCreateLanguageDetectionWorker(); + await workerTextModelSyncClient.ensureSyncedResources([resource]); + const modelId = await workerClient.proxy.$detectLanguage(resource.toString(), langBiases, preferHistory, supportedLangs); const languageId = this.getLanguageId(modelId); const LanguageDetectionStatsId = 'automaticlanguagedetection.perf'; diff --git a/src/vs/workbench/services/search/browser/searchService.ts b/src/vs/workbench/services/search/browser/searchService.ts index 7c8570a4622..d6fb8cc49b1 100644 --- a/src/vs/workbench/services/search/browser/searchService.ts +++ b/src/vs/workbench/services/search/browser/searchService.ts @@ -14,14 +14,14 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IFileMatch, IFileQuery, ISearchComplete, ISearchProgressItem, ISearchResultProvider, ISearchService, ITextQuery, SearchProviderType, TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/search'; import { SearchService } from 'vs/workbench/services/search/common/searchService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IWorkerClient, logOnceWebWorkerWarning, SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; +import { IWorkerClient, logOnceWebWorkerWarning } from 'vs/base/common/worker/simpleWorker'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory'; +import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ILocalFileSearchSimpleWorker, ILocalFileSearchSimpleWorkerHost } from 'vs/workbench/services/search/common/localFileSearchWorkerTypes'; +import { ILocalFileSearchSimpleWorker, LocalFileSearchSimpleWorkerHost } from 'vs/workbench/services/search/common/localFileSearchWorkerTypes'; import { memoize } from 'vs/base/common/decorators'; import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider'; -import { FileAccess, Schemas } from 'vs/base/common/network'; +import { Schemas } from 'vs/base/common/network'; import { URI, UriComponents } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; @@ -46,10 +46,9 @@ export class RemoteSearchService extends SearchService { } } -export class LocalFileSearchWorkerClient extends Disposable implements ISearchResultProvider, ILocalFileSearchSimpleWorkerHost { +export class LocalFileSearchWorkerClient extends Disposable implements ISearchResultProvider { protected _worker: IWorkerClient | null; - protected readonly _workerFactory: DefaultWorkerFactory; private readonly _onDidReceiveTextSearchMatch = new Emitter<{ match: IFileMatch; queryId: number }>(); readonly onDidReceiveTextSearchMatch: Event<{ match: IFileMatch; queryId: number }> = this._onDidReceiveTextSearchMatch.event; @@ -64,7 +63,6 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe ) { super(); this._worker = null; - this._workerFactory = new DefaultWorkerFactory(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), 'localFileSearchWorker'); } sendTextSearchMatch(match: IFileMatch, queryId: number): void { @@ -77,15 +75,15 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe } private async cancelQuery(queryId: number) { - const proxy = await this._getOrCreateWorker().getProxyObject(); - proxy.cancelQuery(queryId); + const proxy = this._getOrCreateWorker().proxy; + proxy.$cancelQuery(queryId); } async textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): Promise { try { const queryDisposables = new DisposableStore(); - const proxy = await this._getOrCreateWorker().getProxyObject(); + const proxy = this._getOrCreateWorker().proxy; const results: IFileMatch[] = []; let limitHit = false; @@ -114,7 +112,7 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe })); const ignorePathCasing = this.uriIdentityService.extUri.ignorePathCasing(fq.folder); - const folderResults = await proxy.searchDirectory(handle, query, fq, ignorePathCasing, queryId); + const folderResults = await proxy.$searchDirectory(handle, query, fq, ignorePathCasing, queryId); for (const folderResult of folderResults.results) { results.push(revive(folderResult)); } @@ -144,7 +142,7 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe const queryDisposables = new DisposableStore(); let limitHit = false; - const proxy = await this._getOrCreateWorker().getProxyObject(); + const proxy = this._getOrCreateWorker().proxy; const results: IFileMatch[] = []; await Promise.all(query.folderQueries.map(async fq => { const queryId = this.queryId++; @@ -156,7 +154,7 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe return; } const caseSensitive = this.uriIdentityService.extUri.ignorePathCasing(fq.folder); - const folderResults = await proxy.listDirectory(handle, query, fq, caseSensitive, queryId); + const folderResults = await proxy.$listDirectory(handle, query, fq, caseSensitive, queryId); for (const folderResult of folderResults.results) { results.push({ resource: URI.joinPath(fq.folder, folderResult) }); } @@ -185,11 +183,15 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe private _getOrCreateWorker(): IWorkerClient { if (!this._worker) { try { - this._worker = this._register(new SimpleWorkerClient( - this._workerFactory, + this._worker = this._register(createWebWorker( 'vs/workbench/services/search/worker/localFileSearch', - this, + 'localFileSearchWorker' )); + LocalFileSearchSimpleWorkerHost.setChannel(this._worker, { + $sendTextSearchMatch: (match, queryId) => { + return this.sendTextSearchMatch(match, queryId); + } + }); } catch (err) { logOnceWebWorkerWarning(err); throw err; diff --git a/src/vs/workbench/services/search/common/localFileSearchWorkerTypes.ts b/src/vs/workbench/services/search/common/localFileSearchWorkerTypes.ts index 8b51e88bedd..8515cd5eed0 100644 --- a/src/vs/workbench/services/search/common/localFileSearchWorkerTypes.ts +++ b/src/vs/workbench/services/search/common/localFileSearchWorkerTypes.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { UriComponents } from 'vs/base/common/uri'; +import { IWorkerClient, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; import { IFileMatch, IFileQueryProps, IFolderQuery, ITextQueryProps } from 'vs/workbench/services/search/common/search'; export interface IWorkerTextSearchComplete { @@ -41,12 +42,20 @@ export interface IWorkerFileSystemFileHandle extends IWorkerFileSystemHandle { export interface ILocalFileSearchSimpleWorker { _requestHandlerBrand: any; - cancelQuery(queryId: number): void; + $cancelQuery(queryId: number): void; - listDirectory(handle: IWorkerFileSystemDirectoryHandle, queryProps: IFileQueryProps, folderQuery: IFolderQuery, ignorePathCasing: boolean, queryId: number): Promise; - searchDirectory(handle: IWorkerFileSystemDirectoryHandle, queryProps: ITextQueryProps, folderQuery: IFolderQuery, ignorePathCasing: boolean, queryId: number): Promise; + $listDirectory(handle: IWorkerFileSystemDirectoryHandle, queryProps: IFileQueryProps, folderQuery: IFolderQuery, ignorePathCasing: boolean, queryId: number): Promise; + $searchDirectory(handle: IWorkerFileSystemDirectoryHandle, queryProps: ITextQueryProps, folderQuery: IFolderQuery, ignorePathCasing: boolean, queryId: number): Promise; } -export interface ILocalFileSearchSimpleWorkerHost { - sendTextSearchMatch(match: IFileMatch, queryId: number): void; +export abstract class LocalFileSearchSimpleWorkerHost { + public static CHANNEL_NAME = 'localFileSearchWorkerHost'; + public static getChannel(workerServer: IWorkerServer): LocalFileSearchSimpleWorkerHost { + return workerServer.getChannel(LocalFileSearchSimpleWorkerHost.CHANNEL_NAME); + } + public static setChannel(workerClient: IWorkerClient, obj: LocalFileSearchSimpleWorkerHost): void { + workerClient.setChannel(LocalFileSearchSimpleWorkerHost.CHANNEL_NAME, obj); + } + + abstract $sendTextSearchMatch(match: IFileMatch, queryId: number): void; } diff --git a/src/vs/workbench/services/search/worker/localFileSearch.ts b/src/vs/workbench/services/search/worker/localFileSearch.ts index c241281b51a..ab9fbe6f030 100644 --- a/src/vs/workbench/services/search/worker/localFileSearch.ts +++ b/src/vs/workbench/services/search/worker/localFileSearch.ts @@ -5,8 +5,8 @@ import * as glob from 'vs/base/common/glob'; import { UriComponents, URI } from 'vs/base/common/uri'; -import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; -import { ILocalFileSearchSimpleWorker, ILocalFileSearchSimpleWorkerHost, IWorkerFileSearchComplete, IWorkerFileSystemDirectoryHandle, IWorkerFileSystemHandle, IWorkerTextSearchComplete } from 'vs/workbench/services/search/common/localFileSearchWorkerTypes'; +import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; +import { ILocalFileSearchSimpleWorker, LocalFileSearchSimpleWorkerHost, IWorkerFileSearchComplete, IWorkerFileSystemDirectoryHandle, IWorkerFileSystemHandle, IWorkerTextSearchComplete } from 'vs/workbench/services/search/common/localFileSearchWorkerTypes'; import { ICommonQueryProps, IFileMatch, IFileQueryProps, IFolderQuery, IPatternInfo, ITextQueryProps, } from 'vs/workbench/services/search/common/search'; import * as paths from 'vs/base/common/path'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; @@ -49,21 +49,24 @@ const time = async (name: string, task: () => Promise | T) => { }; /** - * Called on the worker side - * @internal + * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle */ -export function create(host: ILocalFileSearchSimpleWorkerHost): IRequestHandler { - return new LocalFileSearchSimpleWorker(host); +export function create(workerServer: IWorkerServer): IRequestHandler { + return new LocalFileSearchSimpleWorker(workerServer); } export class LocalFileSearchSimpleWorker implements ILocalFileSearchSimpleWorker, IRequestHandler { _requestHandlerBrand: any; + private readonly host: LocalFileSearchSimpleWorkerHost; cancellationTokens: Map = new Map(); - constructor(private host: ILocalFileSearchSimpleWorkerHost) { } + constructor(workerServer: IWorkerServer) { + this.host = LocalFileSearchSimpleWorkerHost.getChannel(workerServer); + } - cancelQuery(queryId: number): void { + $cancelQuery(queryId: number): void { this.cancellationTokens.get(queryId)?.cancel(); } @@ -73,7 +76,7 @@ export class LocalFileSearchSimpleWorker implements ILocalFileSearchSimpleWorker return source; } - async listDirectory(handle: IWorkerFileSystemDirectoryHandle, query: IFileQueryProps, folderQuery: IFolderQuery, ignorePathCasing: boolean, queryId: number): Promise { + async $listDirectory(handle: IWorkerFileSystemDirectoryHandle, query: IFileQueryProps, folderQuery: IFolderQuery, ignorePathCasing: boolean, queryId: number): Promise { const revivedFolderQuery = reviveFolderQuery(folderQuery); const extUri = new ExtUri(() => ignorePathCasing); @@ -108,7 +111,7 @@ export class LocalFileSearchSimpleWorker implements ILocalFileSearchSimpleWorker }; } - async searchDirectory(handle: IWorkerFileSystemDirectoryHandle, query: ITextQueryProps, folderQuery: IFolderQuery, ignorePathCasing: boolean, queryId: number): Promise { + async $searchDirectory(handle: IWorkerFileSystemDirectoryHandle, query: ITextQueryProps, folderQuery: IFolderQuery, ignorePathCasing: boolean, queryId: number): Promise { const revivedQuery = reviveFolderQuery(folderQuery); const extUri = new ExtUri(() => ignorePathCasing); @@ -153,7 +156,7 @@ export class LocalFileSearchSimpleWorker implements ILocalFileSearchSimpleWorker resource: URI.joinPath(revivedQuery.folder, file.path), results: fileResults, }; - this.host.sendTextSearchMatch(match, queryId); + this.host.$sendTextSearchMatch(match, queryId); results.push(match); } }; diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController.ts index 3695379f0e9..419a1320def 100644 --- a/src/vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController.ts @@ -6,6 +6,7 @@ import { importAMDNodeModule } from 'vs/amdX'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, autorun, keepObserved } from 'vs/base/common/observable'; +import { Proxied } from 'vs/base/common/worker/simpleWorker'; import { countEOL } from 'vs/editor/common/core/eolCounter'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { Range } from 'vs/editor/common/core/range'; @@ -39,7 +40,7 @@ export class TextMateWorkerTokenizerController extends Disposable { constructor( private readonly _model: ITextModel, - private readonly _worker: TextMateTokenizationWorker, + private readonly _worker: Proxied, private readonly _languageIdCodec: ILanguageIdCodec, private readonly _backgroundTokenizationStore: IBackgroundTokenizationStore, private readonly _configurationService: IConfigurationService, @@ -56,7 +57,7 @@ export class TextMateWorkerTokenizerController extends Disposable { changes: changesToString(e.changes), }); } - this._worker.acceptModelChanged(this.controllerId, e); + this._worker.$acceptModelChanged(this.controllerId, e); this._pendingChanges.push(e); })); @@ -64,7 +65,7 @@ export class TextMateWorkerTokenizerController extends Disposable { const languageId = this._model.getLanguageId(); const encodedLanguageId = this._languageIdCodec.encodeLanguageId(languageId); - this._worker.acceptModelLanguageChanged( + this._worker.$acceptModelLanguageChanged( this.controllerId, languageId, encodedLanguageId @@ -73,7 +74,7 @@ export class TextMateWorkerTokenizerController extends Disposable { const languageId = this._model.getLanguageId(); const encodedLanguageId = this._languageIdCodec.encodeLanguageId(languageId); - this._worker.acceptNewModel({ + this._worker.$acceptNewModel({ uri: this._model.uri, versionId: this._model.getVersionId(), lines: this._model.getLinesContent(), @@ -87,17 +88,17 @@ export class TextMateWorkerTokenizerController extends Disposable { this._register(autorun(reader => { /** @description update maxTokenizationLineLength */ const maxTokenizationLineLength = this._maxTokenizationLineLength.read(reader); - this._worker.acceptMaxTokenizationLineLength(this.controllerId, maxTokenizationLineLength); + this._worker.$acceptMaxTokenizationLineLength(this.controllerId, maxTokenizationLineLength); })); } public override dispose(): void { super.dispose(); - this._worker.acceptRemovedModel(this.controllerId); + this._worker.$acceptRemovedModel(this.controllerId); } public requestTokens(startLineNumber: number, endLineNumberExclusive: number): void { - this._worker.retokenize(this.controllerId, startLineNumber, endLineNumberExclusive); + this._worker.$retokenize(this.controllerId, startLineNumber, endLineNumberExclusive); } /** diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts index 1a54e0d1bb6..487f5b19f63 100644 --- a/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts @@ -9,28 +9,28 @@ import { AppResourcePath, FileAccess, nodeModulesAsarPath, nodeModulesPath } fro import { IObservable } from 'vs/base/common/observable'; import { isWeb } from 'vs/base/common/platform'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { MonacoWebWorker, createWebWorker } from 'vs/editor/browser/services/webWorker'; import { IBackgroundTokenizationStore, IBackgroundTokenizer } from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/model'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ICreateData, ITextMateWorkerHost, StateDeltas, TextMateTokenizationWorker } from 'vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'; +import { ICreateData, StateDeltas, TextMateTokenizationWorker } from 'vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'; +import { TextMateWorkerHost } from './worker/textMateWorkerHost'; import { TextMateWorkerTokenizerController } from 'vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController'; import { IValidGrammarDefinition } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; import type { IRawTheme } from 'vscode-textmate'; +import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory'; +import { IWorkerClient, Proxied } from 'vs/base/common/worker/simpleWorker'; export class ThreadedBackgroundTokenizerFactory implements IDisposable { private static _reportedMismatchingTokens = false; - private _workerProxyPromise: Promise | null = null; - private _worker: MonacoWebWorker | null = null; - private _workerProxy: TextMateTokenizationWorker | null = null; + private _workerProxyPromise: Promise | null> | null = null; + private _worker: IWorkerClient | null = null; + private _workerProxy: Proxied | null = null; private readonly _workerTokenizerControllers = new Map(); private _currentTheme: IRawTheme | null = null; @@ -41,8 +41,6 @@ export class ThreadedBackgroundTokenizerFactory implements IDisposable { private readonly _reportTokenizationTime: (timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean) => void, private readonly _shouldTokenizeAsync: () => boolean, @IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, - @IModelService private readonly _modelService: IModelService, - @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ILanguageService private readonly _languageService: ILanguageService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @@ -116,18 +114,18 @@ export class ThreadedBackgroundTokenizerFactory implements IDisposable { this._currentTheme = theme; this._currentTokenColorMap = colorMap; if (this._currentTheme && this._currentTokenColorMap && this._workerProxy) { - this._workerProxy.acceptTheme(this._currentTheme, this._currentTokenColorMap); + this._workerProxy.$acceptTheme(this._currentTheme, this._currentTokenColorMap); } } - private _getWorkerProxy(): Promise { + private _getWorkerProxy(): Promise | null> { if (!this._workerProxyPromise) { this._workerProxyPromise = this._createWorkerProxy(); } return this._workerProxyPromise; } - private async _createWorkerProxy(): Promise { + private async _createWorkerProxy(): Promise | null> { const onigurumaModuleLocation: AppResourcePath = `${nodeModulesPath}/vscode-oniguruma`; const onigurumaModuleLocationAsar: AppResourcePath = `${nodeModulesAsarPath}/vscode-oniguruma`; @@ -139,12 +137,16 @@ export class ThreadedBackgroundTokenizerFactory implements IDisposable { grammarDefinitions: this._grammarDefinitions, onigurumaWASMUri: FileAccess.asBrowserUri(onigurumaWASM).toString(true), }; - const host: ITextMateWorkerHost = { - readFile: async (_resource: UriComponents): Promise => { + const worker = this._worker = createWebWorker( + 'vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker', + 'textMateWorker' + ); + TextMateWorkerHost.setChannel(worker, { + $readFile: async (_resource: UriComponents): Promise => { const resource = URI.revive(_resource); return this._extensionResourceLoaderService.readExtensionResource(resource); }, - setTokensAndStates: async (controllerId: number, versionId: number, tokens: Uint8Array, lineEndStateDeltas: StateDeltas[]): Promise => { + $setTokensAndStates: async (controllerId: number, versionId: number, tokens: Uint8Array, lineEndStateDeltas: StateDeltas[]): Promise => { const controller = this._workerTokenizerControllers.get(controllerId); // When a model detaches, it is removed synchronously from the map. // However, the worker might still be sending tokens for that model, @@ -153,27 +155,21 @@ export class ThreadedBackgroundTokenizerFactory implements IDisposable { controller.setTokensAndStates(controllerId, versionId, tokens, lineEndStateDeltas); } }, - reportTokenizationTime: (timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void => { + $reportTokenizationTime: (timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void => { this._reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, isRandomSample); } - }; - const worker = this._worker = createWebWorker(this._modelService, this._languageConfigurationService, { - createData, - label: 'textMateWorker', - moduleId: 'vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker', - host, }); - const proxy = await worker.getProxy(); + await worker.proxy.$init(createData); if (this._worker !== worker) { // disposed in the meantime return null; } - this._workerProxy = proxy; + this._workerProxy = worker.proxy; if (this._currentTheme && this._currentTokenColorMap) { - this._workerProxy.acceptTheme(this._currentTheme, this._currentTokenColorMap); + this._workerProxy.$acceptTheme(this._currentTheme, this._currentTokenColorMap); } - return proxy; + return worker.proxy; } private _disposeWorker(): void { diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.esm.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.esm.ts new file mode 100644 index 00000000000..a96bd44b037 --- /dev/null +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.esm.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { create } from './textMateTokenizationWorker.worker'; +import { bootstrapSimpleWorker } from 'vs/base/common/worker/simpleWorkerBootstrap'; + +bootstrapSimpleWorker(create); diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts index 28a58f6837d..237f681f3bc 100644 --- a/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts @@ -6,24 +6,20 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { LanguageId } from 'vs/editor/common/encodedTokenAttributes'; import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; -import { IWorkerContext } from 'vs/editor/common/services/editorSimpleWorker'; import { ICreateGrammarResult, TMGrammarFactory } from 'vs/workbench/services/textMate/common/TMGrammarFactory'; import { IValidEmbeddedLanguagesMap, IValidGrammarDefinition, IValidTokenTypeMap } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; import type { IOnigLib, IRawTheme, StackDiff } from 'vscode-textmate'; import { TextMateWorkerTokenizer } from './textMateWorkerTokenizer'; import { importAMDNodeModule } from 'vs/amdX'; +import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker'; +import { TextMateWorkerHost } from './textMateWorkerHost'; /** * Defines the worker entry point. Must be exported and named `create`. + * @skipMangle */ -export function create(ctx: IWorkerContext, createData: ICreateData): TextMateTokenizationWorker { - return new TextMateTokenizationWorker(ctx, createData); -} - -export interface ITextMateWorkerHost { - readFile(_resource: UriComponents): Promise; - setTokensAndStates(controllerId: number, versionId: number, tokens: Uint8Array, lineEndStateDeltas: StateDeltas[]): Promise; - reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void; +export function create(workerServer: IWorkerServer): TextMateTokenizationWorker { + return new TextMateTokenizationWorker(workerServer); } export interface ICreateData { @@ -49,17 +45,19 @@ export interface StateDeltas { stateDeltas: (StackDiff | null)[]; } -export class TextMateTokenizationWorker { - private readonly _host: ITextMateWorkerHost; +export class TextMateTokenizationWorker implements IRequestHandler { + _requestHandlerBrand: any; + + private readonly _host: TextMateWorkerHost; private readonly _models = new Map(); private readonly _grammarCache: Promise[] = []; - private readonly _grammarFactory: Promise; + private _grammarFactory: Promise = Promise.resolve(null); - constructor( - ctx: IWorkerContext, - private readonly _createData: ICreateData - ) { - this._host = ctx.host; + constructor(workerServer: IWorkerServer) { + this._host = TextMateWorkerHost.getChannel(workerServer); + } + + public async $init(_createData: ICreateData): Promise { const grammarDefinitions = _createData.grammarDefinitions.map((def) => { return { location: URI.revive(def.location), @@ -73,13 +71,13 @@ export class TextMateTokenizationWorker { sourceExtensionId: def.sourceExtensionId, }; }); - this._grammarFactory = this._loadTMGrammarFactory(grammarDefinitions); + this._grammarFactory = this._loadTMGrammarFactory(grammarDefinitions, _createData.onigurumaWASMUri); } - private async _loadTMGrammarFactory(grammarDefinitions: IValidGrammarDefinition[]): Promise { + private async _loadTMGrammarFactory(grammarDefinitions: IValidGrammarDefinition[], onigurumaWASMUri: string): Promise { const vscodeTextmate = await importAMDNodeModule('vscode-textmate', 'release/main.js'); const vscodeOniguruma = await importAMDNodeModule('vscode-oniguruma', 'release/main.js'); - const response = await fetch(this._createData.onigurumaWASMUri); + const response = await fetch(onigurumaWASMUri); // Using the response directly only works if the server sets the MIME type 'application/wasm'. // Otherwise, a TypeError is thrown when using the streaming compiler. @@ -95,13 +93,13 @@ export class TextMateTokenizationWorker { return new TMGrammarFactory({ logTrace: (msg: string) => {/* console.log(msg) */ }, logError: (msg: string, err: any) => console.error(msg, err), - readFile: (resource: URI) => this._host.readFile(resource) + readFile: (resource: URI) => this._host.$readFile(resource) }, grammarDefinitions, vscodeTextmate, onigLib); } // These methods are called by the renderer - public acceptNewModel(data: IRawModelData): void { + public $acceptNewModel(data: IRawModelData): void { const uri = URI.revive(data.uri); const that = this; this._models.set(data.controllerId, new TextMateWorkerTokenizer(uri, data.lines, data.EOL, data.versionId, { @@ -116,27 +114,27 @@ export class TextMateTokenizationWorker { return that._grammarCache[encodedLanguageId]; }, setTokensAndStates(versionId: number, tokens: Uint8Array, stateDeltas: StateDeltas[]): void { - that._host.setTokensAndStates(data.controllerId, versionId, tokens, stateDeltas); + that._host.$setTokensAndStates(data.controllerId, versionId, tokens, stateDeltas); }, reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void { - that._host.reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, isRandomSample); + that._host.$reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, isRandomSample); }, }, data.languageId, data.encodedLanguageId, data.maxTokenizationLineLength)); } - public acceptModelChanged(controllerId: number, e: IModelChangedEvent): void { + public $acceptModelChanged(controllerId: number, e: IModelChangedEvent): void { this._models.get(controllerId)!.onEvents(e); } - public retokenize(controllerId: number, startLineNumber: number, endLineNumberExclusive: number): void { + public $retokenize(controllerId: number, startLineNumber: number, endLineNumberExclusive: number): void { this._models.get(controllerId)!.retokenize(startLineNumber, endLineNumberExclusive); } - public acceptModelLanguageChanged(controllerId: number, newLanguageId: string, newEncodedLanguageId: LanguageId): void { + public $acceptModelLanguageChanged(controllerId: number, newLanguageId: string, newEncodedLanguageId: LanguageId): void { this._models.get(controllerId)!.onLanguageId(newLanguageId, newEncodedLanguageId); } - public acceptRemovedModel(controllerId: number): void { + public $acceptRemovedModel(controllerId: number): void { const model = this._models.get(controllerId); if (model) { model.dispose(); @@ -144,12 +142,12 @@ export class TextMateTokenizationWorker { } } - public async acceptTheme(theme: IRawTheme, colorMap: string[]): Promise { + public async $acceptTheme(theme: IRawTheme, colorMap: string[]): Promise { const grammarFactory = await this._grammarFactory; grammarFactory?.setTheme(theme, colorMap); } - public acceptMaxTokenizationLineLength(controllerId: number, value: number): void { + public $acceptMaxTokenizationLineLength(controllerId: number, value: number): void { this._models.get(controllerId)!.acceptMaxTokenizationLineLength(value); } } diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerHost.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerHost.ts new file mode 100644 index 00000000000..b2eb8549e99 --- /dev/null +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerHost.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { UriComponents } from 'vs/base/common/uri'; +import { IWorkerServer, IWorkerClient } from 'vs/base/common/worker/simpleWorker'; +import { StateDeltas } from 'vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'; + +export abstract class TextMateWorkerHost { + public static CHANNEL_NAME = 'textMateWorkerHost'; + public static getChannel(workerServer: IWorkerServer): TextMateWorkerHost { + return workerServer.getChannel(TextMateWorkerHost.CHANNEL_NAME); + } + public static setChannel(workerClient: IWorkerClient, obj: TextMateWorkerHost): void { + workerClient.setChannel(TextMateWorkerHost.CHANNEL_NAME, obj); + } + + abstract $readFile(_resource: UriComponents): Promise; + abstract $setTokensAndStates(controllerId: number, versionId: number, tokens: Uint8Array, lineEndStateDeltas: StateDeltas[]): Promise; + abstract $reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void; +} diff --git a/test/monaco/dist/core.html b/test/monaco/dist/core.html index 13a208944b1..35f656becf1 100644 --- a/test/monaco/dist/core.html +++ b/test/monaco/dist/core.html @@ -5,9 +5,6 @@
- diff --git a/test/monaco/monaco.test.ts b/test/monaco/monaco.test.ts index 3613a1a7212..078fb2cb69e 100644 --- a/test/monaco/monaco.test.ts +++ b/test/monaco/monaco.test.ts @@ -7,6 +7,7 @@ import * as playwright from '@playwright/test'; import { assert } from 'chai'; const PORT = 8563; +const TIMEOUT = 20 * 1000; const APP = `http://127.0.0.1:${PORT}/dist/core.html`; @@ -18,7 +19,7 @@ type BrowserType = 'chromium' | 'firefox' | 'webkit'; const browserType: BrowserType = process.env.BROWSER as BrowserType || 'chromium'; before(async function () { - this.timeout(20 * 1000); + this.timeout(TIMEOUT); console.log(`Starting browser: ${browserType}`); browser = await playwright[browserType].launch({ headless: process.argv.includes('--headless'), @@ -26,13 +27,13 @@ before(async function () { }); after(async function () { - this.timeout(20 * 1000); + this.timeout(TIMEOUT); await browser.close(); }); const pageErrors: any[] = []; beforeEach(async function () { - this.timeout(20 * 1000); + this.timeout(TIMEOUT); page = await browser.newPage({ viewport: { width: 800, @@ -59,7 +60,7 @@ afterEach(async () => { }); describe('API Integration Tests', function (): void { - this.timeout(20000); + this.timeout(TIMEOUT); beforeEach(async () => { await page.goto(APP);