Move workbench workers off EditorWorker (#225796)

* Add support for having channels in SimpleWorker

* Extract text model syncing code to a separate file

* Use a channel to do text model syncing for the language detection worker

* Simplify code

* Remove unused method

* Move OutputLinkComputer worker off editor worker

* Move TextMateTokenizationWorker off editor worker

* Simplify code

* Adopt channels for the host object

* More adopting channels for the host object

* More adopting channels for the host object

* More adopting channels for the host object

* Remove host object support from SimpleWorker

* Use the IEditorWorkerService, avoid starting a separate worker

* Bring the amd module id, the esm location and the worker label in a single type

* Improve typings

* SImplify worker creation pattern

* Enforce that all proxied methods start with `$` or `on`

* Adopt native proxy support

* Simplify code

* Simplify code

* Reintroduce a different standalone / workbench editor worker service
This commit is contained in:
Alexandru Dima 2024-08-20 14:00:18 +02:00 коммит произвёл GitHub
Родитель 5a8d73cebb
Коммит 4e0de3a8f4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
44 изменённых файлов: 1282 добавлений и 1206 удалений

Просмотреть файл

@ -92,7 +92,7 @@ declare namespace monaco.editor {
#includeAll(vs/editor/standalone/browser/standaloneEditor;languages.Token=>Token): #includeAll(vs/editor/standalone/browser/standaloneEditor;languages.Token=>Token):
#include(vs/editor/standalone/common/standaloneTheme): BuiltinTheme, IStandaloneThemeData, IColors #include(vs/editor/standalone/common/standaloneTheme): BuiltinTheme, IStandaloneThemeData, IColors
#include(vs/editor/common/languages/supports/tokenization): ITokenThemeRule #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 #include(vs/editor/standalone/browser/standaloneCodeEditor): IActionDescriptor, IGlobalEditorOptions, IStandaloneEditorConstructionOptions, IStandaloneDiffEditorConstructionOptions, IStandaloneCodeEditor, IStandaloneDiffEditor
export interface ICommandHandler { export interface ICommandHandler {
(...args: any[]): void; (...args: any[]): void;

Просмотреть файл

@ -26,17 +26,14 @@ function createModuleDescription(name, exclude) {
/** /**
* @param {string} name * @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']); const amdVariant = createModuleDescription(name, ['vs/base/common/worker/simpleWorker', 'vs/editor/common/services/editorSimpleWorker']);
amdVariant.target = 'amd'; amdVariant.target = 'amd';
const esmVariant = { ...amdVariant, dest: undefined }; const esmVariant = { ...amdVariant, dest: undefined };
esmVariant.target = 'esm'; esmVariant.target = 'esm';
if (!noEsmSuffix) { esmVariant.name = `${esmVariant.name}.esm`;
esmVariant.name = `${esmVariant.name}.esm`;
}
return [amdVariant, esmVariant]; return [amdVariant, esmVariant];
} }
@ -68,8 +65,8 @@ exports.workerNotebook = createEditorWorkerModuleDescription('vs/workbench/contr
exports.workerLanguageDetection = createEditorWorkerModuleDescription('vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker'); exports.workerLanguageDetection = createEditorWorkerModuleDescription('vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker');
exports.workerLocalFileSearch = createEditorWorkerModuleDescription('vs/workbench/services/search/worker/localFileSearch'); exports.workerLocalFileSearch = createEditorWorkerModuleDescription('vs/workbench/services/search/worker/localFileSearch');
exports.workerProfileAnalysis = createEditorWorkerModuleDescription('vs/platform/profiling/electron-sandbox/profileAnalysisWorker'); exports.workerProfileAnalysis = createEditorWorkerModuleDescription('vs/platform/profiling/electron-sandbox/profileAnalysisWorker');
exports.workerOutputLinks = createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer', true); exports.workerOutputLinks = createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer');
exports.workerBackgroundTokenization = createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker', true); exports.workerBackgroundTokenization = createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker');
exports.workbenchDesktop = function () { exports.workbenchDesktop = function () {
return isESM() ? [ return isESM() ? [
@ -80,8 +77,8 @@ exports.workbenchDesktop = function () {
createModuleDescription('vs/workbench/contrib/issue/electron-sandbox/issueReporterMain'), createModuleDescription('vs/workbench/contrib/issue/electron-sandbox/issueReporterMain'),
createModuleDescription('vs/workbench/workbench.desktop.main') createModuleDescription('vs/workbench/workbench.desktop.main')
] : [ ] : [
...createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer', true), ...createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'),
...createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker', true), ...createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'),
createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp'), createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp'),
createModuleDescription('vs/platform/files/node/watcher/watcherMain'), createModuleDescription('vs/platform/files/node/watcher/watcherMain'),
createModuleDescription('vs/platform/terminal/node/ptyHostMain'), createModuleDescription('vs/platform/terminal/node/ptyHostMain'),
@ -94,8 +91,8 @@ exports.workbenchWeb = function () {
return isESM() ? [ return isESM() ? [
createModuleDescription('vs/workbench/workbench.web.main') createModuleDescription('vs/workbench/workbench.web.main')
] : [ ] : [
...createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer', true), ...createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'),
...createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker', true), ...createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'),
createModuleDescription('vs/code/browser/workbench/workbench', ['vs/workbench/workbench.web.main']) createModuleDescription('vs/code/browser/workbench/workbench', ['vs/workbench/workbench.web.main'])
]; ];
}; };

Просмотреть файл

@ -7,7 +7,7 @@ import { createTrustedTypesPolicy } from 'vs/base/browser/trustedTypes';
import { onUnexpectedError } from 'vs/base/common/errors'; import { onUnexpectedError } from 'vs/base/common/errors';
import { AppResourcePath, COI, FileAccess } from 'vs/base/common/network'; import { AppResourcePath, COI, FileAccess } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri'; 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 { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { coalesce } from 'vs/base/common/arrays'; 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 }); 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<Worker> { function getWorker(esmWorkerLocation: URI | undefined, label: string): Worker | Promise<Worker> {
// Option for hosts to overwrite the worker script (used in the standalone editor) // Option for hosts to overwrite the worker script (used in the standalone editor)
interface IMonacoEnvironment { interface IMonacoEnvironment {
getWorker?(moduleId: string, label: string): Worker | Promise<Worker>; getWorker?(moduleId: string, label: string): Worker | Promise<Worker>;
@ -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 }); return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: isESM ? 'module' : undefined });
} }
// ESM-comment-end // ESM-comment-end
if (workerMainLocation) { if (esmWorkerLocation) {
const workerUrl = getWorkerBootstrapUrl(label, workerMainLocation.toString(true)); 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 }); const worker = new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: isESM ? 'module' : undefined });
if (isESM) { if (isESM) {
return whenESMWorkerReady(worker); return whenESMWorkerReady(worker);
@ -136,17 +136,17 @@ class WebWorker extends Disposable implements IWorker {
private readonly label: string; private readonly label: string;
private worker: Promise<Worker> | null; private worker: Promise<Worker> | 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(); super();
this.id = id; this.id = id;
this.label = label; this.label = label;
const workerOrPromise = getWorker(workerMainLocation, label); const workerOrPromise = getWorker(esmWorkerLocation, label);
if (isPromiseLike(workerOrPromise)) { if (isPromiseLike(workerOrPromise)) {
this.worker = workerOrPromise; this.worker = workerOrPromise;
} else { } else {
this.worker = Promise.resolve(workerOrPromise); this.worker = Promise.resolve(workerOrPromise);
} }
this.postMessage(moduleId, []); this.postMessage(amdModuleId, []);
this.worker.then((w) => { this.worker.then((w) => {
w.onmessage = function (ev) { w.onmessage = function (ev) {
onMessageCallback(ev.data); 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 static LAST_WORKER_ID = 0;
private _label: string | undefined;
private _webWorkerFailedBeforeError: any; private _webWorkerFailedBeforeError: any;
constructor(private readonly workerMainLocation: URI | undefined, label: string | undefined) { constructor() {
this._label = label;
this._webWorkerFailedBeforeError = false; 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); const workerId = (++DefaultWorkerFactory.LAST_WORKER_ID);
if (this._webWorkerFailedBeforeError) { if (this._webWorkerFailedBeforeError) {
throw this._webWorkerFailedBeforeError; throw this._webWorkerFailedBeforeError;
} }
let workerMainLocation = this.workerMainLocation; return new WebWorker(desc.esmModuleLocation, desc.amdModuleId, workerId, desc.label || 'anonymous' + workerId, onMessageCallback, (err) => {
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) => {
logOnceWebWorkerWarning(err); logOnceWebWorkerWarning(err);
this._webWorkerFailedBeforeError = err; this._webWorkerFailedBeforeError = err;
onErrorCallback(err); onErrorCallback(err);
}); });
} }
} }
export function createWebWorker<T extends object>(amdModuleId: string, label: string | undefined): IWorkerClient<T>;
export function createWebWorker<T extends object>(workerDescriptor: IWorkerDescriptor): IWorkerClient<T>;
export function createWebWorker<T extends object>(arg0: string | IWorkerDescriptor, arg1?: string | undefined): IWorkerClient<T> {
const workerDescriptor = (typeof arg0 === 'string' ? new WorkerDescriptor(arg0, arg1) : arg0);
return new SimpleWorkerClient<T>(new DefaultWorkerFactory(), workerDescriptor);
}

Просмотреть файл

@ -3,13 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { AppResourcePath, FileAccess } from 'vs/base/common/network'; import { AppResourcePath, FileAccess } from 'vs/base/common/network';
import { getAllMethodNames } from 'vs/base/common/objects';
import { isWeb } from 'vs/base/common/platform'; import { isWeb } from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings'; import * as strings from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
// ESM-comment-begin // ESM-comment-begin
const isESM = false; const isESM = false;
@ -18,6 +19,7 @@ const isESM = false;
// const isESM = true; // const isESM = true;
// ESM-uncomment-end // ESM-uncomment-end
const DEFAULT_CHANNEL = 'default';
const INITIALIZE = '$initialize'; const INITIALIZE = '$initialize';
export interface IWorker extends IDisposable { export interface IWorker extends IDisposable {
@ -30,7 +32,13 @@ export interface IWorkerCallback {
} }
export interface IWorkerFactory { 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; let webWorkerWarningLogged = false;
@ -58,6 +66,7 @@ class RequestMessage {
constructor( constructor(
public readonly vsWorker: number, public readonly vsWorker: number,
public readonly req: string, public readonly req: string,
public readonly channel: string,
public readonly method: string, public readonly method: string,
public readonly args: any[] public readonly args: any[]
) { } ) { }
@ -76,6 +85,7 @@ class SubscribeEventMessage {
constructor( constructor(
public readonly vsWorker: number, public readonly vsWorker: number,
public readonly req: string, public readonly req: string,
public readonly channel: string,
public readonly eventName: string, public readonly eventName: string,
public readonly arg: any public readonly arg: any
) { } ) { }
@ -104,8 +114,8 @@ interface IMessageReply {
interface IMessageHandler { interface IMessageHandler {
sendMessage(msg: any, transfer?: ArrayBuffer[]): void; sendMessage(msg: any, transfer?: ArrayBuffer[]): void;
handleMessage(method: string, args: any[]): Promise<any>; handleMessage(channel: string, method: string, args: any[]): Promise<any>;
handleEvent(eventName: string, arg: any): Event<any>; handleEvent(channel: string, eventName: string, arg: any): Event<any>;
} }
class SimpleWorkerProtocol { class SimpleWorkerProtocol {
@ -130,24 +140,24 @@ class SimpleWorkerProtocol {
this._workerId = workerId; this._workerId = workerId;
} }
public sendMessage(method: string, args: any[]): Promise<any> { public sendMessage(channel: string, method: string, args: any[]): Promise<any> {
const req = String(++this._lastSentReq); const req = String(++this._lastSentReq);
return new Promise<any>((resolve, reject) => { return new Promise<any>((resolve, reject) => {
this._pendingReplies[req] = { this._pendingReplies[req] = {
resolve: resolve, resolve: resolve,
reject: reject 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<any> { public listen(channel: string, eventName: string, arg: any): Event<any> {
let req: string | null = null; let req: string | null = null;
const emitter = new Emitter<any>({ const emitter = new Emitter<any>({
onWillAddFirstListener: () => { onWillAddFirstListener: () => {
req = String(++this._lastSentReq); req = String(++this._lastSentReq);
this._pendingEmitters.set(req, emitter); 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: () => { onDidRemoveLastListener: () => {
this._pendingEmitters.delete(req!); this._pendingEmitters.delete(req!);
@ -168,6 +178,29 @@ class SimpleWorkerProtocol {
this._handleMessage(message); this._handleMessage(message);
} }
public createProxyToRemoteChannel<T extends object>(channel: string, sendMessageBarrier?: () => Promise<void>): T {
const handler = {
get: (target: any, name: PropertyKey) => {
if (typeof name === 'string' && !target[name]) {
if (propertyIsDynamicEvent(name)) { // onDynamic...
target[name] = (arg: any): Event<any> => {
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 { private _handleMessage(msg: Message): void {
switch (msg.type) { switch (msg.type) {
case MessageType.Reply: case MessageType.Reply:
@ -209,7 +242,7 @@ class SimpleWorkerProtocol {
private _handleRequestMessage(requestMessage: RequestMessage): void { private _handleRequestMessage(requestMessage: RequestMessage): void {
const req = requestMessage.req; 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) => { result.then((r) => {
this._send(new ReplyMessage(this._workerId, req, r, undefined)); this._send(new ReplyMessage(this._workerId, req, r, undefined));
}, (e) => { }, (e) => {
@ -223,7 +256,7 @@ class SimpleWorkerProtocol {
private _handleSubscribeEventMessage(msg: SubscribeEventMessage): void { private _handleSubscribeEventMessage(msg: SubscribeEventMessage): void {
const req = msg.req; 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._send(new EventMessage(this._workerId, req, event));
}); });
this._pendingEvents.set(req, disposable); this._pendingEvents.set(req, disposable);
@ -263,35 +296,60 @@ class SimpleWorkerProtocol {
} }
} }
type ProxiedMethodName = (`$${string}` | `on${string}`);
export type Proxied<T> = { [K in keyof T]: T[K] extends (...args: infer A) => infer R
? (
K extends ProxiedMethodName
? (...args: A) => Promise<Awaited<R>>
: never
)
: never
};
export interface IWorkerClient<W> { export interface IWorkerClient<W> {
getProxyObject(): Promise<W>; proxy: Proxied<W>;
dispose(): void; dispose(): void;
setChannel<T extends object>(channel: string, handler: T): void;
getChannel<T extends object>(channel: string): Proxied<T>;
}
export interface IWorkerServer {
setChannel<T extends object>(channel: string, handler: T): void;
getChannel<T extends object>(channel: string): Proxied<T>;
} }
/** /**
* Main thread side * Main thread side
*/ */
export class SimpleWorkerClient<W extends object, H extends object> extends Disposable implements IWorkerClient<W> { export class SimpleWorkerClient<W extends object> extends Disposable implements IWorkerClient<W> {
private readonly _worker: IWorker; private readonly _worker: IWorker;
private readonly _onModuleLoaded: Promise<string[]>; private readonly _onModuleLoaded: Promise<void>;
private readonly _protocol: SimpleWorkerProtocol; private readonly _protocol: SimpleWorkerProtocol;
private readonly _lazyProxy: Promise<W>; public readonly proxy: Proxied<W>;
private readonly _localChannels: Map<string, object> = new Map();
private readonly _remoteChannels: Map<string, object> = new Map();
constructor(workerFactory: IWorkerFactory, moduleId: string, host: H) { constructor(
workerFactory: IWorkerFactory,
workerDescriptor: IWorkerDescriptor,
) {
super(); super();
let lazyProxyReject: ((err: any) => void) | null = null;
this._worker = this._register(workerFactory.create( 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) => { (msg: Message) => {
this._protocol.handleMessage(msg); this._protocol.handleMessage(msg);
}, },
(err: any) => { (err: any) => {
// in Firefox, web workers fail lazily :( // in Firefox, web workers fail lazily :(
// we will reject the proxy // we will reject the proxy
lazyProxyReject?.(err); onUnexpectedError(err);
} }
)); ));
@ -299,33 +357,11 @@ export class SimpleWorkerClient<W extends object, H extends object> extends Disp
sendMessage: (msg: any, transfer: ArrayBuffer[]): void => { sendMessage: (msg: any, transfer: ArrayBuffer[]): void => {
this._worker.postMessage(msg, transfer); this._worker.postMessage(msg, transfer);
}, },
handleMessage: (method: string, args: any[]): Promise<any> => { handleMessage: (channel: string, method: string, args: any[]): Promise<any> => {
if (typeof (host as any)[method] !== 'function') { return this._handleMessage(channel, method, args);
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);
}
}, },
handleEvent: (eventName: string, arg: any): Event<any> => { handleEvent: (channel: string, eventName: string, arg: any): Event<any> => {
if (propertyIsDynamicEvent(eventName)) { return this._handleEvent(channel, eventName, arg);
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}`);
} }
}); });
this._protocol.setWorkerId(this._worker.getId()); this._protocol.setWorkerId(this._worker.getId());
@ -342,45 +378,67 @@ export class SimpleWorkerClient<W extends object, H extends object> extends Disp
loaderConfiguration = (globalThis as any).requirejs.s.contexts._.config; loaderConfiguration = (globalThis as any).requirejs.s.contexts._.config;
} }
const hostMethods = getAllMethodNames(host);
// Send initialize message // Send initialize message
this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [ this._onModuleLoaded = this._protocol.sendMessage(DEFAULT_CHANNEL, INITIALIZE, [
this._worker.getId(), this._worker.getId(),
JSON.parse(JSON.stringify(loaderConfiguration)), JSON.parse(JSON.stringify(loaderConfiguration)),
moduleId, workerDescriptor.amdModuleId,
hostMethods,
]); ]);
// Create proxy to loaded code this.proxy = this._protocol.createProxyToRemoteChannel(DEFAULT_CHANNEL, async () => { await this._onModuleLoaded; });
const proxyMethodRequest = (method: string, args: any[]): Promise<any> => { this._onModuleLoaded.catch((e) => {
return this._request(method, args); this._onError('Worker failed to load ' + workerDescriptor.amdModuleId, e);
};
const proxyListen = (eventName: string, arg: any): Event<any> => {
return this._protocol.listen(eventName, arg);
};
this._lazyProxy = new Promise<W>((resolve, reject) => {
lazyProxyReject = reject;
this._onModuleLoaded.then((availableMethods: string[]) => {
resolve(createProxyObject<W>(availableMethods, proxyMethodRequest, proxyListen));
}, (e) => {
reject(e);
this._onError('Worker failed to load ' + moduleId, e);
});
}); });
} }
public getProxyObject(): Promise<W> { private _handleMessage(channelName: string, method: string, args: any[]): Promise<any> {
return this._lazyProxy; 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<any> { private _handleEvent(channelName: string, eventName: string, arg: any): Event<any> {
return new Promise<any>((resolve, reject) => { const channel: object | undefined = this._localChannels.get(channelName);
this._onModuleLoaded.then(() => { if (!channel) {
this._protocol.sendMessage(method, args).then(resolve, reject); throw new Error(`Missing channel ${channelName} on main thread`);
}, reject); }
}); 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<T extends object>(channel: string, handler: T): void {
this._localChannels.set(channel, handler);
}
public getChannel<T extends object>(channel: string): Proxied<T> {
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<T>;
} }
private _onError(message: string, error?: any): void { 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)); return /^onDynamic/.test(name) && strings.isUpperAsciiLetter(name.charCodeAt(9));
} }
function createProxyObject<T extends object>(
methodNames: string[],
invoke: (method: string, args: unknown[]) => unknown,
proxyListen: (eventName: string, arg: any) => Event<any>
): 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<any> => {
return function (arg) {
return proxyListen(eventName, arg);
};
};
const result = {} as T;
for (const methodName of methodNames) {
if (propertyIsDynamicEvent(methodName)) {
(<any>result)[methodName] = createProxyDynamicEvent(methodName);
continue;
}
if (propertyIsEvent(methodName)) {
(<any>result)[methodName] = proxyListen(methodName, undefined);
continue;
}
(<any>result)[methodName] = createProxyMethod(methodName);
}
return result;
}
export interface IRequestHandler { export interface IRequestHandler {
_requestHandlerBrand: any; _requestHandlerBrand: any;
[prop: string]: any; [prop: string]: any;
} }
export interface IRequestHandlerFactory<H> { export interface IRequestHandlerFactory {
(host: H): IRequestHandler; (workerServer: IWorkerServer): IRequestHandler;
} }
/** /**
* Worker side * Worker side
*/ */
export class SimpleWorkerServer<H extends object> { export class SimpleWorkerServer implements IWorkerServer {
private _requestHandlerFactory: IRequestHandlerFactory<H> | null; private _requestHandlerFactory: IRequestHandlerFactory | null;
private _requestHandler: IRequestHandler | null; private _requestHandler: IRequestHandler | null;
private _protocol: SimpleWorkerProtocol; private _protocol: SimpleWorkerProtocol;
private readonly _localChannels: Map<string, object> = new Map();
private readonly _remoteChannels: Map<string, object> = new Map();
constructor(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void, requestHandlerFactory: IRequestHandlerFactory<H> | null) { constructor(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void, requestHandlerFactory: IRequestHandlerFactory | null) {
this._requestHandlerFactory = requestHandlerFactory; this._requestHandlerFactory = requestHandlerFactory;
this._requestHandler = null; this._requestHandler = null;
this._protocol = new SimpleWorkerProtocol({ this._protocol = new SimpleWorkerProtocol({
sendMessage: (msg: any, transfer: ArrayBuffer[]): void => { sendMessage: (msg: any, transfer: ArrayBuffer[]): void => {
postMessage(msg, transfer); postMessage(msg, transfer);
}, },
handleMessage: (method: string, args: any[]): Promise<any> => this._handleMessage(method, args), handleMessage: (channel: string, method: string, args: any[]): Promise<any> => this._handleMessage(channel, method, args),
handleEvent: (eventName: string, arg: any): Event<any> => this._handleEvent(eventName, arg) handleEvent: (channel: string, eventName: string, arg: any): Event<any> => this._handleEvent(channel, eventName, arg)
}); });
} }
@ -465,35 +493,40 @@ export class SimpleWorkerServer<H extends object> {
this._protocol.handleMessage(msg); this._protocol.handleMessage(msg);
} }
private _handleMessage(method: string, args: any[]): Promise<any> { private _handleMessage(channel: string, method: string, args: any[]): Promise<any> {
if (method === INITIALIZE) { if (channel === DEFAULT_CHANNEL && method === INITIALIZE) {
return this.initialize(<number>args[0], <any>args[1], <string>args[2], <string[]>args[3]); return this.initialize(<number>args[0], <any>args[1], <string>args[2]);
} }
if (!this._requestHandler || typeof this._requestHandler[method] !== 'function') { const requestHandler: object | null | undefined = (channel === DEFAULT_CHANNEL ? this._requestHandler : this._localChannels.get(channel));
return Promise.reject(new Error('Missing requestHandler or method: ' + method)); 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 { try {
return Promise.resolve(this._requestHandler[method].apply(this._requestHandler, args)); return Promise.resolve((requestHandler as any)[method].apply(requestHandler, args));
} catch (e) { } catch (e) {
return Promise.reject(e); return Promise.reject(e);
} }
} }
private _handleEvent(eventName: string, arg: any): Event<any> { private _handleEvent(channel: string, eventName: string, arg: any): Event<any> {
if (!this._requestHandler) { const requestHandler: object | null | undefined = (channel === DEFAULT_CHANNEL ? this._requestHandler : this._localChannels.get(channel));
throw new Error(`Missing requestHandler`); if (!requestHandler) {
throw new Error(`Missing channel ${channel} on worker thread`);
} }
if (propertyIsDynamicEvent(eventName)) { 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') { if (typeof event !== 'function') {
throw new Error(`Missing dynamic event ${eventName} on request handler.`); throw new Error(`Missing dynamic event ${eventName} on request handler.`);
} }
return event; return event;
} }
if (propertyIsEvent(eventName)) { if (propertyIsEvent(eventName)) {
const event = (this._requestHandler as any)[eventName]; const event = (requestHandler as any)[eventName];
if (typeof event !== 'function') { if (typeof event !== 'function') {
throw new Error(`Missing event ${eventName} on request handler.`); throw new Error(`Missing event ${eventName} on request handler.`);
} }
@ -502,22 +535,25 @@ export class SimpleWorkerServer<H extends object> {
throw new Error(`Malformed event name ${eventName}`); throw new Error(`Malformed event name ${eventName}`);
} }
private initialize(workerId: number, loaderConfig: any, moduleId: string, hostMethods: string[]): Promise<string[]> { public setChannel<T extends object>(channel: string, handler: T): void {
this._localChannels.set(channel, handler);
}
public getChannel<T extends object>(channel: string): Proxied<T> {
if (!this._remoteChannels.has(channel)) {
const inst = this._protocol.createProxyToRemoteChannel(channel);
this._remoteChannels.set(channel, inst);
}
return this._remoteChannels.get(channel) as Proxied<T>;
}
private async initialize(workerId: number, loaderConfig: any, moduleId: string): Promise<void> {
this._protocol.setWorkerId(workerId); this._protocol.setWorkerId(workerId);
const proxyMethodRequest = (method: string, args: any[]): Promise<any> => {
return this._protocol.sendMessage(method, args);
};
const proxyListen = (eventName: string, arg: any): Event<any> => {
return this._protocol.listen(eventName, arg);
};
const hostProxy = createProxyObject<H>(hostMethods, proxyMethodRequest, proxyListen);
if (this._requestHandlerFactory) { if (this._requestHandlerFactory) {
// static request handler // static request handler
this._requestHandler = this._requestHandlerFactory(hostProxy); this._requestHandler = this._requestHandlerFactory(this);
return Promise.resolve(getAllMethodNames(this._requestHandler)); return;
} }
if (loaderConfig) { if (loaderConfig) {
@ -542,18 +578,16 @@ export class SimpleWorkerServer<H extends object> {
if (isESM) { if (isESM) {
const url = FileAccess.asBrowserUri(`${moduleId}.js` as AppResourcePath).toString(true); const url = FileAccess.asBrowserUri(`${moduleId}.js` as AppResourcePath).toString(true);
return import(`${url}`).then((module: { create: IRequestHandlerFactory<H> }) => { return import(`${url}`).then((module: { create: IRequestHandlerFactory }) => {
this._requestHandler = module.create(hostProxy); this._requestHandler = module.create(this);
if (!this._requestHandler) { if (!this._requestHandler) {
throw new Error(`No RequestHandler!`); throw new Error(`No RequestHandler!`);
} }
return getAllMethodNames(this._requestHandler);
}); });
} }
return new Promise<string[]>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
// Use the global require to be sure to get the global config // Use the global require to be sure to get the global config
// ESM-comment-begin // ESM-comment-begin
@ -563,24 +597,24 @@ export class SimpleWorkerServer<H extends object> {
// const req = globalThis.require; // const req = globalThis.require;
// ESM-uncomment-end // ESM-uncomment-end
req([moduleId], (module: { create: IRequestHandlerFactory<H> }) => { req([moduleId], (module: { create: IRequestHandlerFactory }) => {
this._requestHandler = module.create(hostProxy); this._requestHandler = module.create(this);
if (!this._requestHandler) { if (!this._requestHandler) {
reject(new Error(`No RequestHandler!`)); reject(new Error(`No RequestHandler!`));
return; return;
} }
resolve(getAllMethodNames(this._requestHandler)); resolve();
}, reject); }, reject);
}); });
} }
} }
/** /**
* Called on the worker side * Defines the worker entry point. Must be exported and named `create`.
* @skipMangle * @skipMangle
*/ */
export function create(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void): SimpleWorkerServer<any> { export function create(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void): SimpleWorkerServer {
return new SimpleWorkerServer(postMessage, null); return new SimpleWorkerServer(postMessage, null);
} }

Просмотреть файл

@ -16,15 +16,15 @@ declare const globalThis: {
let initialized = false; let initialized = false;
function initialize<H extends object>(factory: IRequestHandlerFactory<H>) { function initialize(factory: IRequestHandlerFactory) {
if (initialized) { if (initialized) {
return; return;
} }
initialized = true; initialized = true;
const simpleWorker = new SimpleWorkerServer<H>( const simpleWorker = new SimpleWorkerServer(
msg => globalThis.postMessage(msg), msg => globalThis.postMessage(msg),
host => factory(host) (workerServer) => factory(workerServer)
); );
globalThis.onmessage = (e: MessageEvent) => { globalThis.onmessage = (e: MessageEvent) => {
@ -32,7 +32,7 @@ function initialize<H extends object>(factory: IRequestHandlerFactory<H>) {
}; };
} }
export function bootstrapSimpleWorker<H extends object>(factory: IRequestHandlerFactory<H>) { export function bootstrapSimpleWorker(factory: IRequestHandlerFactory) {
globalThis.onmessage = (_e: MessageEvent) => { globalThis.onmessage = (_e: MessageEvent) => {
// Ignore first message in this case and initialize if not yet initialized // Ignore first message in this case and initialize if not yet initialized
if (!initialized) { if (!initialized) {

Просмотреть файл

@ -3,18 +3,18 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IntervalTimer, timeout } from 'vs/base/common/async'; import { timeout } from 'vs/base/common/async';
import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { SimpleWorkerClient, logOnceWebWorkerWarning, IWorkerClient } from 'vs/base/common/worker/simpleWorker'; import { logOnceWebWorkerWarning, IWorkerClient, Proxied, IWorkerDescriptor } from 'vs/base/common/worker/simpleWorker';
import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory'; import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range'; import { IRange, Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model'; import { ITextModel } from 'vs/editor/common/model';
import * as languages from 'vs/editor/common/languages'; import * as languages from 'vs/editor/common/languages';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; 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 { IModelService } from 'vs/editor/common/services/model';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
import { isNonEmptyArray } from 'vs/base/common/arrays'; 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 { StopWatch } from 'vs/base/common/stopwatch';
import { canceled, onUnexpectedError } from 'vs/base/common/errors'; import { canceled, onUnexpectedError } from 'vs/base/common/errors';
import { UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter'; 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 { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer';
import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; 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 { SectionHeader, FindSectionHeaderOptions } from 'vs/editor/common/services/findSectionHeaders';
import { mainWindow } from 'vs/base/browser/window'; import { mainWindow } from 'vs/base/browser/window';
import { WindowIntervalTimer } from 'vs/base/browser/dom'; import { WindowIntervalTimer } from 'vs/base/browser/dom';
import { WorkerTextModelSyncClient } from 'vs/editor/common/services/textModelSync/textModelSync.impl';
/** import { EditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost';
* Stop syncing a model to the worker if it was not needed for 1 min.
*/
const STOP_SYNC_MODEL_DELTA_TIME_MS = 60 * 1000;
/** /**
* Stop the worker if it was not needed for 5 min. * Stop the worker if it was not needed for 5 min.
@ -54,7 +50,7 @@ function canSyncModel(modelService: IModelService, resource: URI): boolean {
return true; return true;
} }
export class EditorWorkerService extends Disposable implements IEditorWorkerService { export abstract class EditorWorkerService extends Disposable implements IEditorWorkerService {
declare readonly _serviceBrand: undefined; declare readonly _serviceBrand: undefined;
@ -63,30 +59,30 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ
private readonly _logService: ILogService; private readonly _logService: ILogService;
constructor( constructor(
workerMainLocation: URI | undefined, workerDescriptor: IWorkerDescriptor,
@IModelService modelService: IModelService, @IModelService modelService: IModelService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@ILogService logService: ILogService, @ILogService logService: ILogService,
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService,
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
) { ) {
super(); super();
this._modelService = modelService; 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; this._logService = logService;
// register default link-provider and default completions-provider // register default link-provider and default completions-provider
this._register(languageFeaturesService.linkProvider.register({ language: '*', hasAccessToAllModels: true }, { this._register(languageFeaturesService.linkProvider.register({ language: '*', hasAccessToAllModels: true }, {
provideLinks: (model, token) => { provideLinks: async (model, token) => {
if (!canSyncModel(this._modelService, model.uri)) { if (!canSyncModel(this._modelService, model.uri)) {
return Promise.resolve({ links: [] }); // File too large return Promise.resolve({ links: [] }); // File too large
} }
return this._workerManager.withWorker().then(client => client.computeLinks(model.uri)).then(links => { const worker = await this._workerWithResources([model.uri]);
return links && { links }; 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 { public override dispose(): void {
@ -97,12 +93,14 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ
return canSyncModel(this._modelService, uri); return canSyncModel(this._modelService, uri);
} }
public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult> { public async computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult> {
return this._workerManager.withWorker().then(client => client.computedUnicodeHighlights(uri, options, range)); 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<IDocumentDiff | null> { public async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise<IDocumentDiff | null> {
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) { if (!result) {
return null; return null;
} }
@ -138,17 +136,18 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ
return (canSyncModel(this._modelService, original) && canSyncModel(this._modelService, modified)); return (canSyncModel(this._modelService, original) && canSyncModel(this._modelService, modified));
} }
public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<IChange[] | null> { public async computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<IChange[] | null> {
return this._workerManager.withWorker().then(client => client.computeDirtyDiff(original, modified, ignoreTrimWhitespace)); 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<languages.TextEdit[] | undefined> { public async computeMoreMinimalEdits(resource: URI, edits: languages.TextEdit[] | null | undefined, pretty: boolean = false): Promise<languages.TextEdit[] | undefined> {
if (isNonEmptyArray(edits)) { if (isNonEmptyArray(edits)) {
if (!canSyncModel(this._modelService, resource)) { if (!canSyncModel(this._modelService, resource)) {
return Promise.resolve(edits); // File too large return Promise.resolve(edits); // File too large
} }
const sw = StopWatch.create(); 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())); result.finally(() => this._logService.trace('FORMAT#computeMoreMinimalEdits', resource.toString(true), sw.elapsed()));
return Promise.race([result, timeout(1000).then(() => edits)]); 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 return Promise.resolve(edits); // File too large
} }
const sw = StopWatch.create(); const sw = StopWatch.create();
const result = this._workerManager.withWorker().then(client => client.computeHumanReadableDiff(resource, edits, const opts: ILinesDiffComputerOptions = { ignoreTrimWhitespace: false, maxComputationTimeMs: 1000, computeMoves: false };
{ ignoreTrimWhitespace: false, maxComputationTimeMs: 1000, computeMoves: false, })).catch((err) => { const result = (
onUnexpectedError(err); this._workerWithResources([resource])
// In case of an exception, fall back to computeMoreMinimalEdits .then(worker => worker.$computeHumanReadableDiff(resource.toString(), edits, opts))
return this.computeMoreMinimalEdits(resource, edits, true); .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())); result.finally(() => this._logService.trace('FORMAT#computeHumanReadableDiff', resource.toString(true), sw.elapsed()));
return result; return result;
@ -181,20 +184,47 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ
return (canSyncModel(this._modelService, resource)); return (canSyncModel(this._modelService, resource));
} }
public navigateValueSet(resource: URI, range: IRange, up: boolean): Promise<languages.IInplaceReplaceSupportResult | null> { public async navigateValueSet(resource: URI, range: IRange, up: boolean): Promise<languages.IInplaceReplaceSupportResult | null> {
return this._workerManager.withWorker().then(client => client.navigateValueSet(resource, range, up)); 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); return canSyncModel(this._modelService, resource);
} }
computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { public async computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> {
return this._workerManager.withWorker().then(client => client.computeWordRanges(resource, range)); 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<SectionHeader[]> { public async findSectionHeaders(uri: URI, options: FindSectionHeaderOptions): Promise<SectionHeader[]> {
return this._workerManager.withWorker().then(client => client.findSectionHeaders(uri, options)); const worker = await this._workerWithResources([uri]);
return worker.$findSectionHeaders(uri.toString(), options);
}
public async computeDefaultDocumentColors(uri: URI): Promise<languages.IColorInformation[] | null> {
const worker = await this._workerWithResources([uri]);
return worker.$computeDefaultDocumentColors(uri.toString());
}
private async _workerWithResources(resources: URI[], forceLargeModels: boolean = false): Promise<Proxied<EditorSimpleWorker>> {
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 _editorWorkerClient: EditorWorkerClient | null;
private _lastWorkerUsedTime: number; private _lastWorkerUsedTime: number;
constructor(private readonly workerMainLocation: URI | undefined, modelService: IModelService, private readonly languageConfigurationService: ILanguageConfigurationService) { constructor(
private readonly _workerDescriptor: IWorkerDescriptor,
@IModelService modelService: IModelService
) {
super(); super();
this._modelService = modelService; this._modelService = modelService;
this._editorWorkerClient = null; this._editorWorkerClient = null;
@ -336,124 +369,31 @@ class WorkerManager extends Disposable {
public withWorker(): Promise<EditorWorkerClient> { public withWorker(): Promise<EditorWorkerClient> {
this._lastWorkerUsedTime = (new Date()).getTime(); this._lastWorkerUsedTime = (new Date()).getTime();
if (!this._editorWorkerClient) { 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); 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<T extends IDisposable> implements IWorkerClient<T> { class SynchronousWorkerClient<T extends IDisposable> implements IWorkerClient<T> {
private readonly _instance: T; private readonly _instance: T;
private readonly _proxyObj: Promise<T>; public readonly proxy: Proxied<T>;
constructor(instance: T) { constructor(instance: T) {
this._instance = instance; this._instance = instance;
this._proxyObj = Promise.resolve(this._instance); this.proxy = this._instance as Proxied<T>;
} }
public dispose(): void { public dispose(): void {
this._instance.dispose(); this._instance.dispose();
} }
public getProxyObject(): Promise<T> { public setChannel<T extends object>(channel: string, handler: T): void {
return this._proxyObj; throw new Error(`Not supported`);
}
public getChannel<T extends object>(channel: string): Proxied<T> {
throw new Error(`Not supported`);
} }
} }
@ -461,40 +401,22 @@ export interface IEditorWorkerClient {
fhr(method: string, args: any[]): Promise<any>; fhr(method: string, args: any[]): Promise<any>;
} }
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<any> {
return this._workerClient.fhr(method, args);
}
}
export class EditorWorkerClient extends Disposable implements IEditorWorkerClient { export class EditorWorkerClient extends Disposable implements IEditorWorkerClient {
private readonly _modelService: IModelService; private readonly _modelService: IModelService;
private readonly _keepIdleModels: boolean; private readonly _keepIdleModels: boolean;
protected _worker: IWorkerClient<EditorSimpleWorker> | null; private _worker: IWorkerClient<EditorSimpleWorker> | null;
protected readonly _workerFactory: DefaultWorkerFactory; private _modelManager: WorkerTextModelSyncClient | null;
private _modelManager: EditorModelManager | null;
private _disposed = false; private _disposed = false;
constructor( constructor(
workerMainLocation: URI | undefined, private readonly _workerDescriptor: IWorkerDescriptor,
modelService: IModelService,
keepIdleModels: boolean, keepIdleModels: boolean,
label: string | undefined, @IModelService modelService: IModelService,
private readonly languageConfigurationService: ILanguageConfigurationService
) { ) {
super(); super();
this._modelService = modelService; this._modelService = modelService;
this._keepIdleModels = keepIdleModels; this._keepIdleModels = keepIdleModels;
this._workerFactory = new DefaultWorkerFactory(workerMainLocation, label);
this._worker = null; this._worker = null;
this._modelManager = null; this._modelManager = null;
} }
@ -507,123 +429,59 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien
private _getOrCreateWorker(): IWorkerClient<EditorSimpleWorker> { private _getOrCreateWorker(): IWorkerClient<EditorSimpleWorker> {
if (!this._worker) { if (!this._worker) {
try { try {
this._worker = this._register(new SimpleWorkerClient<EditorSimpleWorker, EditorWorkerHost>( this._worker = this._register(createWebWorker<EditorSimpleWorker>(this._workerDescriptor));
this._workerFactory, EditorWorkerHost.setChannel(this._worker, this._createEditorWorkerHost());
'vs/editor/common/services/editorSimpleWorker',
new EditorWorkerHost(this)
));
} catch (err) { } catch (err) {
logOnceWebWorkerWarning(err); logOnceWebWorkerWarning(err);
this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null)); this._worker = this._createFallbackLocalWorker();
} }
} }
return this._worker; return this._worker;
} }
protected _getProxy(): Promise<EditorSimpleWorker> { protected async _getProxy(): Promise<Proxied<EditorSimpleWorker>> {
return this._getOrCreateWorker().getProxyObject().then(undefined, (err) => { try {
const proxy = this._getOrCreateWorker().proxy;
await proxy.$ping();
return proxy;
} catch (err) {
logOnceWebWorkerWarning(err); logOnceWebWorkerWarning(err);
this._worker = new SynchronousWorkerClient(new EditorSimpleWorker(new EditorWorkerHost(this), null)); this._worker = this._createFallbackLocalWorker();
return this._getOrCreateWorker().getProxyObject(); return this._worker.proxy;
}); }
} }
private _getOrCreateModelManager(proxy: EditorSimpleWorker): EditorModelManager { private _createFallbackLocalWorker(): SynchronousWorkerClient<EditorSimpleWorker> {
return new SynchronousWorkerClient(new EditorSimpleWorker(this._createEditorWorkerHost(), null));
}
private _createEditorWorkerHost(): EditorWorkerHost {
return {
$fhr: (method, args) => this.fhr(method, args)
};
}
private _getOrCreateModelManager(proxy: Proxied<EditorSimpleWorker>): WorkerTextModelSyncClient {
if (!this._modelManager) { 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; return this._modelManager;
} }
protected async _withSyncedResources(resources: URI[], forceLargeModels: boolean = false): Promise<EditorSimpleWorker> { public async workerWithSyncedResources(resources: URI[], forceLargeModels: boolean = false): Promise<Proxied<EditorSimpleWorker>> {
if (this._disposed) { if (this._disposed) {
return Promise.reject(canceled()); return Promise.reject(canceled());
} }
return this._getProxy().then((proxy) => { const proxy = await this._getProxy();
this._getOrCreateModelManager(proxy).ensureSyncedResources(resources, forceLargeModels); this._getOrCreateModelManager(proxy).ensureSyncedResources(resources, forceLargeModels);
return proxy; return proxy;
});
}
public computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult> {
return this._withSyncedResources([uri]).then(proxy => {
return proxy.computeUnicodeHighlights(uri.toString(), options, range);
});
}
public computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise<IDiffComputationResult | null> {
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<IChange[] | null> {
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<languages.TextEdit[]> {
return this._withSyncedResources([resource]).then(proxy => {
return proxy.computeMoreMinimalEdits(resource.toString(), edits, pretty);
});
}
public computeHumanReadableDiff(resource: URI, edits: languages.TextEdit[], options: ILinesDiffComputerOptions): Promise<languages.TextEdit[]> {
return this._withSyncedResources([resource]).then(proxy => {
return proxy.computeHumanReadableDiff(resource.toString(), edits, options);
});
}
public computeLinks(resource: URI): Promise<languages.ILink[] | null> {
return this._withSyncedResources([resource]).then(proxy => {
return proxy.computeLinks(resource.toString());
});
}
public computeDefaultDocumentColors(resource: URI): Promise<languages.IColorInformation[] | null> {
return this._withSyncedResources([resource]).then(proxy => {
return proxy.computeDefaultDocumentColors(resource.toString());
});
} }
public async textualSuggest(resources: URI[], leadingWord: string | undefined, wordDefRegExp: RegExp): Promise<{ words: string[]; duration: number } | null> { 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 wordDef = wordDefRegExp.source;
const wordDefFlags = wordDefRegExp.flags; const wordDefFlags = wordDefRegExp.flags;
return proxy.textualSuggest(resources.map(r => r.toString()), leadingWord, wordDef, wordDefFlags); 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<languages.IInplaceReplaceSupportResult | null> {
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<SectionHeader[]> {
return this._withSyncedResources([uri]).then(proxy => {
return proxy.findSectionHeaders(uri.toString(), options);
});
} }
override dispose(): void { override dispose(): void {

Просмотреть файл

@ -6,18 +6,17 @@
import { stringDiff } from 'vs/base/common/diff/diff'; import { stringDiff } from 'vs/base/common/diff/diff';
import { IDisposable } from 'vs/base/common/lifecycle'; import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; 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 { IPosition, Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range'; import { IRange, Range } from 'vs/editor/common/core/range';
import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model';
import { IMirrorTextModel, IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel'; import { IMirrorTextModel, IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
import { ensureValidWordDefinition, getWordAtText, IWordAtPosition } from 'vs/editor/common/core/wordHelper';
import { IColorInformation, IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/languages'; 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 { BasicInplaceReplace } from 'vs/editor/common/languages/supports/inplaceReplaceSupport';
import { DiffAlgorithmName, IDiffComputationResult, ILineChange, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; import { DiffAlgorithmName, IDiffComputationResult, ILineChange, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker';
import { createMonacoBaseAPI } from 'vs/editor/common/services/editorBaseApi'; 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 { StopWatch } from 'vs/base/common/stopwatch';
import { UnicodeTextModelHighlighter, UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter'; import { UnicodeTextModelHighlighter, UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter';
import { DiffComputer, IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; 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 { IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider';
import { AppResourcePath, FileAccess } from 'vs/base/common/network'; import { AppResourcePath, FileAccess } from 'vs/base/common/network';
import { BugIndicatingError } from 'vs/base/common/errors'; 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 { 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 // ESM-comment-begin
const isESM = false; const isESM = false;
@ -55,43 +56,11 @@ export interface IWorkerContext<H = undefined> {
getMirrorModels(): IMirrorModel[]; 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<string>;
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. * Range of a word inside a model.
* @internal * @internal
*/ */
interface IWordRange { export interface IWordRange {
/** /**
* The index where the word starts. * The index where the word starts.
*/ */
@ -102,246 +71,6 @@ interface IWordRange {
readonly end: number; 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<string> {
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 * @internal
*/ */
@ -354,55 +83,38 @@ declare const require: any;
/** /**
* @internal * @internal
*/ */
export class EditorSimpleWorker implements IRequestHandler, IDisposable { export class BaseEditorSimpleWorker implements IDisposable, IWorkerTextModelSyncChannelServer, IRequestHandler {
_requestHandlerBrand: any; _requestHandlerBrand: any;
protected readonly _host: IEditorWorkerHost; private readonly _workerTextModelSyncServer = new WorkerTextModelSyncServer();
private _models: { [uri: string]: MirrorModel };
private readonly _foreignModuleFactory: IForeignModuleFactory | null;
private _foreignModule: any;
constructor(host: IEditorWorkerHost, foreignModuleFactory: IForeignModuleFactory | null) { constructor() {
this._host = host;
this._models = Object.create(null);
this._foreignModuleFactory = foreignModuleFactory;
this._foreignModule = null;
} }
public dispose(): void { dispose(): void {
this._models = Object.create(null);
} }
protected _getModel(uri: string): ICommonModel { protected _getModel(uri: string): ICommonModel | undefined {
return this._models[uri]; return this._workerTextModelSyncServer.getModel(uri);
} }
private _getModels(): ICommonModel[] { protected _getModels(): ICommonModel[] {
const all: MirrorModel[] = []; return this._workerTextModelSyncServer.getModels();
Object.keys(this._models).forEach((key) => all.push(this._models[key]));
return all;
} }
public acceptNewModel(data: IRawModelData): void { public $acceptNewModel(data: IRawModelData): void {
this._models[data.url] = new MirrorModel(URI.parse(data.url), data.lines, data.EOL, data.versionId); this._workerTextModelSyncServer.$acceptNewModel(data);
} }
public acceptModelChanged(strURL: string, e: IModelChangedEvent): void { public $acceptModelChanged(uri: string, e: IModelChangedEvent): void {
if (!this._models[strURL]) { this._workerTextModelSyncServer.$acceptModelChanged(uri, e);
return;
}
const model = this._models[strURL];
model.onEvents(e);
} }
public acceptRemovedModel(strURL: string): void { public $acceptRemovedModel(uri: string): void {
if (!this._models[strURL]) { this._workerTextModelSyncServer.$acceptRemovedModel(uri);
return;
}
delete this._models[strURL];
} }
public async computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult> { public async $computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult> {
const model = this._getModel(url); const model = this._getModel(url);
if (!model) { if (!model) {
return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 }; 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); return UnicodeTextModelHighlighter.computeUnicodeHighlights(model, options, range);
} }
public async findSectionHeaders(url: string, options: FindSectionHeaderOptions): Promise<SectionHeader[]> { public async $findSectionHeaders(url: string, options: FindSectionHeaderOptions): Promise<SectionHeader[]> {
const model = this._getModel(url); const model = this._getModel(url);
if (!model) { if (!model) {
return []; return [];
@ -420,7 +132,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
// ---- BEGIN diff -------------------------------------------------------------------------- // ---- BEGIN diff --------------------------------------------------------------------------
public async computeDiff(originalUrl: string, modifiedUrl: string, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise<IDiffComputationResult | null> { public async $computeDiff(originalUrl: string, modifiedUrl: string, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise<IDiffComputationResult | null> {
const original = this._getModel(originalUrl); const original = this._getModel(originalUrl);
const modified = this._getModel(modifiedUrl); const modified = this._getModel(modifiedUrl);
if (!original || !modified) { if (!original || !modified) {
@ -484,7 +196,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
return true; return true;
} }
public async computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise<IChange[] | null> { public async $computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise<IChange[] | null> {
const original = this._getModel(originalUrl); const original = this._getModel(originalUrl);
const modified = this._getModel(modifiedUrl); const modified = this._getModel(modifiedUrl);
if (!original || !modified) { if (!original || !modified) {
@ -510,7 +222,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
private static readonly _diffLimit = 100000; private static readonly _diffLimit = 100000;
public async computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): Promise<TextEdit[]> { public async $computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): Promise<TextEdit[]> {
const model = this._getModel(modelUrl); const model = this._getModel(modelUrl);
if (!model) { if (!model) {
return edits; return edits;
@ -592,7 +304,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
return result; 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); const model = this._getModel(modelUrl);
if (!model) { if (!model) {
return edits; return edits;
@ -692,7 +404,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
// ---- END minimal edits --------------------------------------------------------------- // ---- END minimal edits ---------------------------------------------------------------
public async computeLinks(modelUrl: string): Promise<ILink[] | null> { public async $computeLinks(modelUrl: string): Promise<ILink[] | null> {
const model = this._getModel(modelUrl); const model = this._getModel(modelUrl);
if (!model) { if (!model) {
return null; return null;
@ -703,7 +415,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
// --- BEGIN default document colors ----------------------------------------------------------- // --- BEGIN default document colors -----------------------------------------------------------
public async computeDefaultDocumentColors(modelUrl: string): Promise<IColorInformation[] | null> { public async $computeDefaultDocumentColors(modelUrl: string): Promise<IColorInformation[] | null> {
const model = this._getModel(modelUrl); const model = this._getModel(modelUrl);
if (!model) { if (!model) {
return null; return null;
@ -715,7 +427,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
private static readonly _suggestionsLimit = 10000; 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 sw = new StopWatch();
const wordDefRegExp = new RegExp(wordDef, wordDefFlags); const wordDefRegExp = new RegExp(wordDef, wordDefFlags);
@ -746,7 +458,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
//#region -- word ranges -- //#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); const model = this._getModel(modelUrl);
if (!model) { if (!model) {
return Object.create(null); return Object.create(null);
@ -777,7 +489,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
//#endregion //#endregion
public async navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): Promise<IInplaceReplaceSupportResult | null> { public async $navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): Promise<IInplaceReplaceSupportResult | null> {
const model = this._getModel(modelUrl); const model = this._getModel(modelUrl);
if (!model) { if (!model) {
return null; return null;
@ -804,12 +516,31 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
const result = BasicInplaceReplace.INSTANCE.navigateValueSet(range, selectionText, wordRange, word, up); const result = BasicInplaceReplace.INSTANCE.navigateValueSet(range, selectionText, wordRange, word, up);
return result; 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 -------------------------------------------------------------------------- // ---- BEGIN foreign module support --------------------------------------------------------------------------
public loadForeignModule(moduleId: string, createData: any, foreignHostMethods: string[]): Promise<string[]> { public $loadForeignModule(moduleId: string, createData: any, foreignHostMethods: string[]): Promise<string[]> {
const proxyMethodRequest = (method: string, args: any[]): Promise<any> => { const proxyMethodRequest = (method: string, args: any[]): Promise<any> => {
return this._host.fhr(method, args); return this._host.$fhr(method, args);
}; };
const foreignHost = createProxyObject(foreignHostMethods, proxyMethodRequest); const foreignHost = createProxyObject(foreignHostMethods, proxyMethodRequest);
@ -844,7 +575,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
} }
// foreign method request // foreign method request
public fmr(method: string, args: any[]): Promise<any> { public $fmr(method: string, args: any[]): Promise<any> {
if (!this._foreignModule || typeof this._foreignModule[method] !== 'function') { if (!this._foreignModule || typeof this._foreignModule[method] !== 'function') {
return Promise.reject(new Error('Missing requestHandler or method: ' + method)); 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 * @internal
*/ */
export function create(host: IEditorWorkerHost): IRequestHandler { export function create(workerServer: IWorkerServer): IRequestHandler {
return new EditorSimpleWorker(host, null); return new EditorSimpleWorker(EditorWorkerHost.getChannel(workerServer), null);
} }
// This is only available in a Web Worker // This is only available in a Web Worker

Просмотреть файл

@ -7,10 +7,10 @@ import { URI } from 'vs/base/common/uri';
import { IRange } from 'vs/editor/common/core/range'; import { IRange } from 'vs/editor/common/core/range';
import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider';
import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; 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 { UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; 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'; import { SectionHeader, FindSectionHeaderOptions } from 'vs/editor/common/services/findSectionHeaders';
export const IEditorWorkerService = createDecorator<IEditorWorkerService>('editorWorkerService'); export const IEditorWorkerService = createDecorator<IEditorWorkerService>('editorWorkerService');
@ -23,7 +23,7 @@ export interface IEditorWorkerService {
canComputeUnicodeHighlights(uri: URI): boolean; canComputeUnicodeHighlights(uri: URI): boolean;
computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult>; computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult>;
/** Implementation in {@link EditorSimpleWorker.computeDiff} */ /** Implementation in {@link BaseEditorSimpleWorker.computeDiff} */
computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise<IDocumentDiff | null>; computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise<IDocumentDiff | null>;
canComputeDirtyDiff(original: URI, modified: URI): boolean; canComputeDirtyDiff(original: URI, modified: URI): boolean;
@ -39,6 +39,9 @@ export interface IEditorWorkerService {
navigateValueSet(resource: URI, range: IRange, up: boolean): Promise<IInplaceReplaceSupportResult | null>; navigateValueSet(resource: URI, range: IRange, up: boolean): Promise<IInplaceReplaceSupportResult | null>;
findSectionHeaders(uri: URI, options: FindSectionHeaderOptions): Promise<SectionHeader[]>; findSectionHeaders(uri: URI, options: FindSectionHeaderOptions): Promise<SectionHeader[]>;
computeDefaultDocumentColors(uri: URI): Promise<IColorInformation[] | null>;
} }
export interface IDiffComputationResult { export interface IDiffComputationResult {

Просмотреть файл

@ -3,9 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { 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 = { type MessageEvent = {
data: any; data: any;
@ -26,7 +26,7 @@ export function initialize(factory: any) {
const simpleWorker = new SimpleWorkerServer((msg) => { const simpleWorker = new SimpleWorkerServer((msg) => {
globalThis.postMessage(msg); globalThis.postMessage(msg);
}, (host: IEditorWorkerHost) => new EditorSimpleWorker(host, null)); }, (workerServer: IWorkerServer) => new EditorSimpleWorker(EditorWorkerHost.getChannel(workerServer), null));
globalThis.onmessage = (e: MessageEvent) => { globalThis.onmessage = (e: MessageEvent) => {
simpleWorker.onmessage(e.data); simpleWorker.onmessage(e.data);

Просмотреть файл

@ -3,7 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * 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>(EditorWorkerHost.CHANNEL_NAME);
}
public static setChannel(workerClient: IWorkerClient<any>, obj: EditorWorkerHost): void {
workerClient.setChannel<EditorWorkerHost>(EditorWorkerHost.CHANNEL_NAME, obj);
}
// foreign host request // foreign host request
fhr(method: string, args: any[]): Promise<any>; abstract $fhr(method: string, args: any[]): Promise<any>;
} }

Просмотреть файл

@ -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<any>, modelService: IModelService): WorkerTextModelSyncClient {
return new WorkerTextModelSyncClient(
workerClient.getChannel<IWorkerTextModelSyncChannelServer>(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<string> {
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<string>;
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[];
}

Просмотреть файл

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

Просмотреть файл

@ -7,27 +7,19 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Color, RGBA } from 'vs/base/common/color'; import { Color, RGBA } from 'vs/base/common/color';
import { ITextModel } from 'vs/editor/common/model'; import { ITextModel } from 'vs/editor/common/model';
import { DocumentColorProvider, IColor, IColorInformation, IColorPresentation } from 'vs/editor/common/languages'; 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 { Disposable } from 'vs/base/common/lifecycle';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { registerEditorFeature } from 'vs/editor/common/editorFeatures'; 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 { export class DefaultDocumentColorProvider implements DocumentColorProvider {
private _editorWorkerClient: EditorWorkerClient;
constructor( constructor(
modelService: IModelService, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
languageConfigurationService: ILanguageConfigurationService, ) { }
) {
this._editorWorkerClient = new EditorWorkerClient(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, false, 'editorWorkerService', languageConfigurationService);
}
async provideDocumentColors(model: ITextModel, _token: CancellationToken): Promise<IColorInformation[] | null> { async provideDocumentColors(model: ITextModel, _token: CancellationToken): Promise<IColorInformation[] | null> {
return this._editorWorkerClient.computeDefaultDocumentColors(model.uri); return this._editorWorkerService.computeDefaultDocumentColors(model.uri);
} }
provideColorPresentations(_model: ITextModel, colorInfo: IColorInformation, _token: CancellationToken): IColorPresentation[] { provideColorPresentations(_model: ITextModel, colorInfo: IColorInformation, _token: CancellationToken): IColorPresentation[] {
@ -50,12 +42,11 @@ export class DefaultDocumentColorProvider implements DocumentColorProvider {
class DefaultDocumentColorProviderFeature extends Disposable { class DefaultDocumentColorProviderFeature extends Disposable {
constructor( constructor(
@IModelService _modelService: IModelService,
@ILanguageConfigurationService _languageConfigurationService: ILanguageConfigurationService,
@ILanguageFeaturesService _languageFeaturesService: ILanguageFeaturesService, @ILanguageFeaturesService _languageFeaturesService: ILanguageFeaturesService,
@IEditorWorkerService editorWorkerService: IEditorWorkerService,
) { ) {
super(); super();
this._register(_languageFeaturesService.colorProvider.register('*', new DefaultDocumentColorProvider(_modelService, _languageConfigurationService))); this._register(_languageFeaturesService.colorProvider.register('*', new DefaultDocumentColorProvider(editorWorkerService)));
} }
} }

Просмотреть файл

@ -22,11 +22,10 @@ import { EditorContributionInstantiation, registerEditorContribution } from 'vs/
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IRange } from 'vs/editor/common/core/range'; 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 { DefaultDocumentColorProvider } from 'vs/editor/contrib/colorPicker/browser/defaultDocumentColorProvider';
import * as dom from 'vs/base/browser/dom'; import * as dom from 'vs/base/browser/dom';
import 'vs/css!./colorPicker'; import 'vs/css!./colorPicker';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
export class StandaloneColorPickerController extends Disposable implements IEditorContribution { export class StandaloneColorPickerController extends Disposable implements IEditorContribution {
@ -38,11 +37,7 @@ export class StandaloneColorPickerController extends Disposable implements IEdit
constructor( constructor(
private readonly _editor: ICodeEditor, private readonly _editor: ICodeEditor,
@IContextKeyService _contextKeyService: IContextKeyService, @IContextKeyService _contextKeyService: IContextKeyService,
@IModelService private readonly _modelService: IModelService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@IInstantiationService private readonly _instantiationService: IInstantiationService, @IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILanguageFeaturesService private readonly _languageFeatureService: ILanguageFeaturesService,
@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService
) { ) {
super(); super();
this._standaloneColorPickerVisible = EditorContextKeys.standaloneColorPickerVisible.bindTo(_contextKeyService); this._standaloneColorPickerVisible = EditorContextKeys.standaloneColorPickerVisible.bindTo(_contextKeyService);
@ -54,7 +49,12 @@ export class StandaloneColorPickerController extends Disposable implements IEdit
return; return;
} }
if (!this._standaloneColorPickerVisible.get()) { 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()) { } else if (!this._standaloneColorPickerFocused.get()) {
this._standaloneColorPickerWidget?.focus(); this._standaloneColorPickerWidget?.focus();
} }
@ -102,10 +102,9 @@ export class StandaloneColorPickerWidget extends Disposable implements IContentW
private readonly _standaloneColorPickerVisible: IContextKey<boolean>, private readonly _standaloneColorPickerVisible: IContextKey<boolean>,
private readonly _standaloneColorPickerFocused: IContextKey<boolean>, private readonly _standaloneColorPickerFocused: IContextKey<boolean>,
@IInstantiationService _instantiationService: IInstantiationService, @IInstantiationService _instantiationService: IInstantiationService,
@IModelService private readonly _modelService: IModelService,
@IKeybindingService private readonly _keybindingService: IKeybindingService, @IKeybindingService private readonly _keybindingService: IKeybindingService,
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
) { ) {
super(); super();
this._standaloneColorPickerVisible.set(true); this._standaloneColorPickerVisible.set(true);
@ -205,7 +204,7 @@ export class StandaloneColorPickerWidget extends Disposable implements IContentW
range: range, range: range,
color: { red: 0, green: 0, blue: 0, alpha: 1 } 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) { if (!colorHoverResult) {
return null; return null;
} }

Просмотреть файл

@ -13,9 +13,8 @@ import { IRange } from 'vs/editor/common/core/range';
import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/core/wordHelper'; import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/core/wordHelper';
import * as languages from 'vs/editor/common/languages'; import * as languages from 'vs/editor/common/languages';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; 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 { EditorWorkerService } from 'vs/editor/browser/services/editorWorkerService';
import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost';
import { IModelService } from 'vs/editor/common/services/model'; import { IModelService } from 'vs/editor/common/services/model';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
import { CompletionItem } from 'vs/editor/contrib/suggest/browser/suggest'; import { CompletionItem } from 'vs/editor/contrib/suggest/browser/suggest';
@ -63,20 +62,20 @@ suite('suggest, word distance', function () {
const service = new class extends EditorWorkerService { const service = new class extends EditorWorkerService {
private _worker = new EditorSimpleWorker(new class extends mock<IEditorWorkerHost>() { }, null); private _worker = new BaseEditorSimpleWorker();
constructor() { constructor() {
super(undefined, modelService, new class extends mock<ITextResourceConfigurationService>() { }, new NullLogService(), new TestLanguageConfigurationService(), new LanguageFeaturesService()); super(null!, modelService, new class extends mock<ITextResourceConfigurationService>() { }, new NullLogService(), new TestLanguageConfigurationService(), new LanguageFeaturesService());
this._worker.acceptNewModel({ this._worker.$acceptNewModel({
url: model.uri.toString(), url: model.uri.toString(),
lines: model.getLinesContent(), lines: model.getLinesContent(),
EOL: model.getEOL(), EOL: model.getEOL(),
versionId: model.getVersionId() 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> { 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);
} }
}; };

Просмотреть файл

@ -12,7 +12,7 @@ import { FontMeasurements } from 'vs/editor/browser/config/fontMeasurements';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; 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 { ApplyUpdateResult, ConfigurationChangedEvent, EditorOptions } from 'vs/editor/common/config/editorOptions';
import { EditorZoom } from 'vs/editor/common/config/editorZoom'; import { EditorZoom } from 'vs/editor/common/config/editorZoom';
import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; 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 { EditorType, IDiffEditor } from 'vs/editor/common/editorCommon';
import * as languages from 'vs/editor/common/languages'; import * as languages from 'vs/editor/common/languages';
import { ILanguageService } from 'vs/editor/common/languages/language'; 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 { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
import { NullState, nullTokenize } from 'vs/editor/common/languages/nullTokenize'; import { NullState, nullTokenize } from 'vs/editor/common/languages/nullTokenize';
import { FindMatch, ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model'; 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. * Specify an AMD module to load that will `create` an object that will be proxied.
*/ */
export function createWebWorker<T extends object>(opts: IWebWorkerOptions): MonacoWebWorker<T> { export function createWebWorker<T extends object>(opts: IWebWorkerOptions): MonacoWebWorker<T> {
return actualCreateWebWorker<T>(StandaloneServices.get(IModelService), StandaloneServices.get(ILanguageConfigurationService), opts); return actualCreateWebWorker<T>(StandaloneServices.get(IModelService), opts);
} }
/** /**

Просмотреть файл

@ -98,6 +98,7 @@ import { mainWindow } from 'vs/base/browser/window';
import { ResourceMap } from 'vs/base/common/map'; import { ResourceMap } from 'vs/base/common/map';
import { ITreeSitterParserService } from 'vs/editor/common/services/treeSitterParserService'; import { ITreeSitterParserService } from 'vs/editor/common/services/treeSitterParserService';
import { StandaloneTreeSitterParserService } from 'vs/editor/standalone/browser/standaloneTreeSitterService'; import { StandaloneTreeSitterParserService } from 'vs/editor/standalone/browser/standaloneTreeSitterService';
import { IWorkerDescriptor } from 'vs/base/common/worker/simpleWorker';
class SimpleModel implements IResolvedTextEditorModel { 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 { class StandaloneEditorWorkerService extends EditorWorkerService {
constructor( constructor(
@IModelService modelService: IModelService, @IModelService modelService: IModelService,
@ -1083,7 +1090,7 @@ class StandaloneEditorWorkerService extends EditorWorkerService {
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService,
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
) { ) {
super(undefined, modelService, configurationService, logService, languageConfigurationService, languageFeaturesService); super(standaloneEditorWorkerDescriptor, modelService, configurationService, logService, languageConfigurationService, languageFeaturesService);
} }
} }

Просмотреть файл

@ -3,26 +3,19 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { getAllMethodNames } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { IWorkerDescriptor } from 'vs/base/common/worker/simpleWorker';
import { EditorWorkerClient } from 'vs/editor/browser/services/editorWorkerService'; 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 { 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. * 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. * Specify an AMD module to load that will `create` an object that will be proxied.
*/ */
export function createWebWorker<T extends object>(modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions): MonacoWebWorker<T> { export function createWebWorker<T extends object>(modelService: IModelService, opts: IWebWorkerOptions): MonacoWebWorker<T> {
return new MonacoWebWorkerImpl<T>(undefined, modelService, languageConfigurationService, opts); return new MonacoWebWorkerImpl<T>(modelService, opts);
}
/**
* @internal
*/
export function createWorkbenchWebWorker<T extends object>(modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions): MonacoWebWorker<T> {
return new MonacoWebWorkerImpl<T>(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, languageConfigurationService, opts);
} }
/** /**
@ -76,8 +69,13 @@ class MonacoWebWorkerImpl<T extends object> extends EditorWorkerClient implement
private _foreignModuleCreateData: any | null; private _foreignModuleCreateData: any | null;
private _foreignProxy: Promise<T> | null; private _foreignProxy: Promise<T> | null;
constructor(workerMainLocation: URI | undefined, modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions) { constructor(modelService: IModelService, opts: IWebWorkerOptions) {
super(workerMainLocation, modelService, opts.keepIdleModels || false, opts.label, languageConfigurationService); const workerDescriptor: IWorkerDescriptor = {
amdModuleId: standaloneEditorWorkerDescriptor.amdModuleId,
esmModuleLocation: standaloneEditorWorkerDescriptor.esmModuleLocation,
label: opts.label,
};
super(workerDescriptor, opts.keepIdleModels || false, modelService);
this._foreignModuleId = opts.moduleId; this._foreignModuleId = opts.moduleId;
this._foreignModuleCreateData = opts.createData || null; this._foreignModuleCreateData = opts.createData || null;
this._foreignModuleHost = opts.host || null; this._foreignModuleHost = opts.host || null;
@ -101,11 +99,11 @@ class MonacoWebWorkerImpl<T extends object> extends EditorWorkerClient implement
if (!this._foreignProxy) { if (!this._foreignProxy) {
this._foreignProxy = this._getProxy().then((proxy) => { this._foreignProxy = this._getProxy().then((proxy) => {
const foreignHostMethods = this._foreignModuleHost ? getAllMethodNames(this._foreignModuleHost) : []; 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; this._foreignModuleCreateData = null;
const proxyMethodRequest = (method: string, args: any[]): Promise<any> => { const proxyMethodRequest = (method: string, args: any[]): Promise<any> => {
return proxy.fmr(method, args); return proxy.$fmr(method, args);
}; };
const createProxyMethod = (method: string, proxyMethodRequest: (method: string, args: any[]) => Promise<any>): () => Promise<any> => { const createProxyMethod = (method: string, proxyMethodRequest: (method: string, args: any[]) => Promise<any>): () => Promise<any> => {
@ -132,6 +130,6 @@ class MonacoWebWorkerImpl<T extends object> extends EditorWorkerClient implement
} }
public withSyncedResources(resources: URI[]): Promise<T> { public withSyncedResources(resources: URI[]): Promise<T> {
return this._withSyncedResources(resources).then(_ => this.getProxy()); return this.workerWithSyncedResources(resources).then(_ => this.getProxy());
} }
} }

Просмотреть файл

@ -8,14 +8,14 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range'; import { IRange, Range } from 'vs/editor/common/core/range';
import { TextEdit } from 'vs/editor/common/languages'; import { TextEdit } from 'vs/editor/common/languages';
import { EditorSimpleWorker, ICommonModel } from 'vs/editor/common/services/editorSimpleWorker'; import { BaseEditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker';
import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; import { ICommonModel } from 'vs/editor/common/services/textModelSync/textModelSync.impl';
suite('EditorSimpleWorker', () => { suite('EditorSimpleWorker', () => {
ensureNoDisposablesAreLeakedInTestSuite(); ensureNoDisposablesAreLeakedInTestSuite();
class WorkerWithModels extends EditorSimpleWorker { class WorkerWithModels extends BaseEditorSimpleWorker {
getModel(uri: string) { getModel(uri: string) {
return this._getModel(uri); return this._getModel(uri);
@ -23,13 +23,13 @@ suite('EditorSimpleWorker', () => {
addModel(lines: string[], eol: string = '\n') { addModel(lines: string[], eol: string = '\n') {
const uri = 'test:file#' + Date.now(); const uri = 'test:file#' + Date.now();
this.acceptNewModel({ this.$acceptNewModel({
url: uri, url: uri,
versionId: 1, versionId: 1,
lines: lines, lines: lines,
EOL: eol EOL: eol
}); });
return this._getModel(uri); return this._getModel(uri)!;
} }
} }
@ -37,7 +37,7 @@ suite('EditorSimpleWorker', () => {
let model: ICommonModel; let model: ICommonModel;
setup(() => { setup(() => {
worker = new WorkerWithModels(<IEditorWorkerHost>null!, null); worker = new WorkerWithModels();
model = worker.addModel([ model = worker.addModel([
'This is line one', //16 'This is line one', //16
'and this is line number two', //27 'and this is line number two', //27
@ -93,7 +93,7 @@ suite('EditorSimpleWorker', () => {
test('MoreMinimal', () => { 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); assert.strictEqual(edits.length, 1);
const [first] = edits; const [first] = edits;
assert.strictEqual(first.text, 'O'); assert.strictEqual(first.text, 'O');
@ -112,7 +112,7 @@ suite('EditorSimpleWorker', () => {
], '\n'); ], '\n');
const newEdits = await worker.computeMoreMinimalEdits(model.uri.toString(), [ const newEdits = await worker.$computeMoreMinimalEdits(model.uri.toString(), [
{ {
range: new Range(1, 1, 2, 1), range: new Range(1, 1, 2, 1),
text: 'one\ntwo\nthree\n', text: 'one\ntwo\nthree\n',
@ -144,7 +144,7 @@ suite('EditorSimpleWorker', () => {
'}' '}'
], '\n'); ], '\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); assert.strictEqual(edits.length, 0);
}); });
}); });
@ -157,7 +157,7 @@ suite('EditorSimpleWorker', () => {
'}' '}'
], '\n'); ], '\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); assert.strictEqual(edits.length, 1);
const [first] = edits; const [first] = edits;
assert.strictEqual(first.text, 'b'); assert.strictEqual(first.text, 'b');
@ -173,7 +173,7 @@ suite('EditorSimpleWorker', () => {
'}' // 3 '}' // 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); assert.strictEqual(edits.length, 1);
const [first] = edits; const [first] = edits;
assert.strictEqual(first.text, '\n'); assert.strictEqual(first.text, '\n');
@ -184,7 +184,7 @@ suite('EditorSimpleWorker', () => {
async function testEdits(lines: string[], edits: TextEdit[]): Promise<unknown> { async function testEdits(lines: string[], edits: TextEdit[]): Promise<unknown> {
const model = worker.addModel(lines); const model = worker.addModel(lines);
const smallerEdits = await worker.computeHumanReadableDiff( const smallerEdits = await worker.$computeHumanReadableDiff(
model.uri.toString(), model.uri.toString(),
edits, edits,
{ ignoreTrimWhitespace: false, maxComputationTimeMs: 0, computeMoves: false } { ignoreTrimWhitespace: false, maxComputationTimeMs: 0, computeMoves: false }
@ -286,7 +286,7 @@ suite('EditorSimpleWorker', () => {
'f f' // 2 '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) { if (!result) {
assert.ok(false); assert.ok(false);
} }

Просмотреть файл

@ -6,7 +6,7 @@
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { IRange } from 'vs/editor/common/core/range'; import { IRange } from 'vs/editor/common/core/range';
import { DiffAlgorithmName, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; 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 { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider';
import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer';
import { SectionHeader } from 'vs/editor/common/services/findSectionHeaders'; import { SectionHeader } from 'vs/editor/common/services/findSectionHeaders';
@ -27,4 +27,5 @@ export class TestEditorWorkerService implements IEditorWorkerService {
canNavigateValueSet(resource: URI): boolean { return false; } canNavigateValueSet(resource: URI): boolean { return false; }
async navigateValueSet(resource: URI, range: IRange, up: boolean): Promise<IInplaceReplaceSupportResult | null> { return null; } async navigateValueSet(resource: URI, range: IRange, up: boolean): Promise<IInplaceReplaceSupportResult | null> { return null; }
async findSectionHeaders(uri: URI): Promise<SectionHeader[]> { return []; } async findSectionHeaders(uri: URI): Promise<SectionHeader[]> { return []; }
async computeDefaultDocumentColors(uri: URI): Promise<IColorInformation[] | null> { return null; }
} }

Просмотреть файл

@ -6,12 +6,16 @@
import { basename } from 'vs/base/common/path'; import { basename } from 'vs/base/common/path';
import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree';
import { URI } from 'vs/base/common/uri'; 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 { IV8Profile, Utils } from 'vs/platform/profiling/common/profiling';
import { IProfileModel, BottomUpSample, buildModel, BottomUpNode, processNode, CdpCallFrame } from 'vs/platform/profiling/common/profilingModel'; import { IProfileModel, BottomUpSample, buildModel, BottomUpNode, processNode, CdpCallFrame } from 'vs/platform/profiling/common/profilingModel';
import { BottomUpAnalysis, IProfileAnalysisWorker, ProfilingOutput } from 'vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService'; 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(); return new ProfileAnalysisWorker();
} }
@ -19,7 +23,7 @@ class ProfileAnalysisWorker implements IRequestHandler, IProfileAnalysisWorker {
_requestHandlerBrand: any; _requestHandlerBrand: any;
analyseBottomUp(profile: IV8Profile): BottomUpAnalysis { $analyseBottomUp(profile: IV8Profile): BottomUpAnalysis {
if (!Utils.isValidProfile(profile)) { if (!Utils.isValidProfile(profile)) {
return { kind: ProfilingOutput.Irrelevant, samples: [] }; return { kind: ProfilingOutput.Irrelevant, samples: [] };
} }
@ -37,7 +41,7 @@ class ProfileAnalysisWorker implements IRequestHandler, IProfileAnalysisWorker {
return { kind: ProfilingOutput.Interesting, samples }; 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 // build search tree
const searchTree = TernarySearchTree.forUris<string>(); const searchTree = TernarySearchTree.forUris<string>();

Просмотреть файл

@ -4,10 +4,9 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory'; import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory';
import { FileAccess } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri'; 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 { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log'; 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 { reportSample } from 'vs/platform/profiling/common/profilingTelemetrySpec';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
export const enum ProfilingOutput { export const enum ProfilingOutput {
Failure, Failure,
Irrelevant, Irrelevant,
@ -42,8 +40,6 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService {
declare _serviceBrand: undefined; declare _serviceBrand: undefined;
private readonly _workerFactory = new DefaultWorkerFactory(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), 'CpuProfileAnalysis');
constructor( constructor(
@ITelemetryService private readonly _telemetryService: ITelemetryService, @ITelemetryService private readonly _telemetryService: ITelemetryService,
@ILogService private readonly _logService: ILogService, @ILogService private readonly _logService: ILogService,
@ -51,14 +47,13 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService {
private async _withWorker<R>(callback: (worker: Proxied<IProfileAnalysisWorker>) => Promise<R>): Promise<R> { private async _withWorker<R>(callback: (worker: Proxied<IProfileAnalysisWorker>) => Promise<R>): Promise<R> {
const worker = new SimpleWorkerClient<Proxied<IProfileAnalysisWorker>, {}>( const worker = createWebWorker<IProfileAnalysisWorker>(
this._workerFactory,
'vs/platform/profiling/electron-sandbox/profileAnalysisWorker', 'vs/platform/profiling/electron-sandbox/profileAnalysisWorker',
{ /* host */ } 'CpuProfileAnalysis'
); );
try { try {
const r = await callback(await worker.getProxyObject()); const r = await callback(worker.proxy);
return r; return r;
} finally { } finally {
worker.dispose(); worker.dispose();
@ -67,7 +62,7 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService {
async analyseBottomUp(profile: IV8Profile, callFrameClassifier: IScriptUrlClassifier, perfBaseline: number, sendAsErrorTelemtry: boolean): Promise<ProfilingOutput> { async analyseBottomUp(profile: IV8Profile, callFrameClassifier: IScriptUrlClassifier, perfBaseline: number, sendAsErrorTelemtry: boolean): Promise<ProfilingOutput> {
return this._withWorker(async worker => { return this._withWorker(async worker => {
const result = await worker.analyseBottomUp(profile); const result = await worker.$analyseBottomUp(profile);
if (result.kind === ProfilingOutput.Interesting) { if (result.kind === ProfilingOutput.Interesting) {
for (const sample of result.samples) { for (const sample of result.samples) {
reportSample({ reportSample({
@ -83,7 +78,7 @@ class ProfileAnalysisWorkerService implements IProfileAnalysisWorkerService {
async analyseByLocation(profile: IV8Profile, locations: [location: URI, id: string][]): Promise<[category: string, aggregated: number][]> { async analyseByLocation(profile: IV8Profile, locations: [location: URI, id: string][]): Promise<[category: string, aggregated: number][]> {
return this._withWorker(async worker => { return this._withWorker(async worker => {
const result = await worker.analyseByUrlCategory(profile, locations); const result = await worker.$analyseByUrlCategory(profile, locations);
return result; return result;
}); });
} }
@ -104,15 +99,8 @@ export interface CategoryAnalysis {
} }
export interface IProfileAnalysisWorker { export interface IProfileAnalysisWorker {
analyseBottomUp(profile: IV8Profile): BottomUpAnalysis; $analyseBottomUp(profile: IV8Profile): BottomUpAnalysis;
analyseByUrlCategory(profile: IV8Profile, categories: [url: URI, category: string][]): [category: string, aggregated: number][]; $analyseByUrlCategory(profile: IV8Profile, categories: [url: URI, category: string][]): [category: string, aggregated: number][];
} }
// TODO@jrieken move into worker logic
type Proxied<T> = { [K in keyof T]: T[K] extends (...args: infer A) => infer R
? (...args: A) => Promise<Awaited<R>>
: never
};
registerSingleton(IProfileAnalysisWorkerService, ProfileAnalysisWorkerService, InstantiationType.Delayed); registerSingleton(IProfileAnalysisWorkerService, ProfileAnalysisWorkerService, InstantiationType.Delayed);

Просмотреть файл

@ -234,6 +234,10 @@ function isInitMessage(a: any): a is IInitMessage {
return !!a && typeof a === 'object' && a.type === 'vscode.init' && a.data instanceof Map; 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 } { export function create(): { onmessage: (message: any) => void } {
performance.mark(`code/extHost/willConnectToRenderer`); performance.mark(`code/extHost/willConnectToRenderer`);
const res = new ExtensionWorker(); const res = new ExtensionWorker();

Просмотреть файл

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { EditorWorkerService } from 'vs/editor/browser/services/editorWorkerService';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
@ -19,6 +19,7 @@ export class WorkbenchEditorWorkerService extends EditorWorkerService {
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService, @ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService,
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, @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);
} }
} }

Просмотреть файл

@ -9,7 +9,7 @@ import { IModelService } from 'vs/editor/common/services/model';
import { assertType } from 'vs/base/common/types'; import { assertType } from 'vs/base/common/types';
import { DiffAlgorithmName, IEditorWorkerService, ILineChange } from 'vs/editor/common/services/editorWorker'; import { DiffAlgorithmName, IEditorWorkerService, ILineChange } from 'vs/editor/common/services/editorWorker';
import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; 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 { LineRange } from 'vs/editor/common/core/lineRange';
import { MovedText } from 'vs/editor/common/diff/linesDiffComputer'; import { MovedText } from 'vs/editor/common/diff/linesDiffComputer';
import { LineRangeMapping, DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; 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<IEditorWorkerService>() { export class TestWorkerService extends mock<IEditorWorkerService>() {
private readonly _worker = new EditorSimpleWorker(null!, null); private readonly _worker = new BaseEditorSimpleWorker();
constructor(@IModelService private readonly _modelService: IModelService) { constructor(@IModelService private readonly _modelService: IModelService) {
super(); super();
@ -36,21 +36,21 @@ export class TestWorkerService extends mock<IEditorWorkerService>() {
assertType(originalModel); assertType(originalModel);
assertType(modifiedModel); assertType(modifiedModel);
this._worker.acceptNewModel({ this._worker.$acceptNewModel({
url: originalModel.uri.toString(), url: originalModel.uri.toString(),
versionId: originalModel.getVersionId(), versionId: originalModel.getVersionId(),
lines: originalModel.getLinesContent(), lines: originalModel.getLinesContent(),
EOL: originalModel.getEOL(), EOL: originalModel.getEOL(),
}); });
this._worker.acceptNewModel({ this._worker.$acceptNewModel({
url: modifiedModel.uri.toString(), url: modifiedModel.uri.toString(),
versionId: modifiedModel.getVersionId(), versionId: modifiedModel.getVersionId(),
lines: modifiedModel.getLinesContent(), lines: modifiedModel.getLinesContent(),
EOL: modifiedModel.getEOL(), 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) { if (!result) {
return result; return result;
} }

Просмотреть файл

@ -5,15 +5,13 @@
import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; import { IWorkerClient, Proxied } from 'vs/base/common/worker/simpleWorker';
import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory'; import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { IMainCellDto, INotebookDiffResult, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IMainCellDto, INotebookDiffResult, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { NotebookEditorSimpleWorker } from 'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker'; 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 { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService';
import { FileAccess } from 'vs/base/common/network';
export class NotebookEditorWorkerServiceImpl extends Disposable implements INotebookEditorWorkerService { export class NotebookEditorWorkerServiceImpl extends Disposable implements INotebookEditorWorkerService {
declare readonly _serviceBrand: undefined; declare readonly _serviceBrand: undefined;
@ -59,23 +57,18 @@ class WorkerManager extends Disposable {
withWorker(): Promise<NotebookWorkerClient> { withWorker(): Promise<NotebookWorkerClient> {
// this._lastWorkerUsedTime = (new Date()).getTime(); // this._lastWorkerUsedTime = (new Date()).getTime();
if (!this._editorWorkerClient) { if (!this._editorWorkerClient) {
this._editorWorkerClient = new NotebookWorkerClient(this._notebookService, 'notebookEditorWorkerService'); this._editorWorkerClient = new NotebookWorkerClient(this._notebookService);
} }
return Promise.resolve(this._editorWorkerClient); return Promise.resolve(this._editorWorkerClient);
} }
} }
interface IWorkerClient<W> {
getProxyObject(): Promise<W>;
dispose(): void;
}
class NotebookEditorModelManager extends Disposable { class NotebookEditorModelManager extends Disposable {
private _syncedModels: { [modelUrl: string]: IDisposable } = Object.create(null); private _syncedModels: { [modelUrl: string]: IDisposable } = Object.create(null);
private _syncedModelsLastUsedTime: { [modelUrl: string]: number } = Object.create(null); private _syncedModelsLastUsedTime: { [modelUrl: string]: number } = Object.create(null);
constructor( constructor(
private readonly _proxy: NotebookEditorSimpleWorker, private readonly _proxy: Proxied<NotebookEditorSimpleWorker>,
private readonly _notebookService: INotebookService private readonly _notebookService: INotebookService
) { ) {
super(); super();
@ -102,7 +95,7 @@ class NotebookEditorModelManager extends Disposable {
const modelUrl = resource.toString(); const modelUrl = resource.toString();
this._proxy.acceptNewModel( this._proxy.$acceptNewModel(
model.uri.toString(), model.uri.toString(),
{ {
cells: model.cells.map(cell => ({ cells: model.cells.map(cell => ({
@ -162,7 +155,7 @@ class NotebookEditorModelManager extends Disposable {
return data; return data;
}); });
this._proxy.acceptModelChanged(modelUrl.toString(), { this._proxy.$acceptModelChanged(modelUrl.toString(), {
rawEvents: dto, rawEvents: dto,
versionId: event.versionId versionId: event.versionId
}); });
@ -172,7 +165,7 @@ class NotebookEditorModelManager extends Disposable {
this._stopModelSync(modelUrl); this._stopModelSync(modelUrl);
})); }));
toDispose.add(toDisposable(() => { toDispose.add(toDisposable(() => {
this._proxy.acceptRemovedModel(modelUrl); this._proxy.$acceptRemovedModel(modelUrl);
})); }));
this._syncedModels[modelUrl] = toDispose; 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<any> {
return this._workerClient.fhr(method, args);
}
}
class NotebookWorkerClient extends Disposable { class NotebookWorkerClient extends Disposable {
private _worker: IWorkerClient<NotebookEditorSimpleWorker> | null; private _worker: IWorkerClient<NotebookEditorSimpleWorker> | null;
private readonly _workerFactory: DefaultWorkerFactory;
private _modelManager: NotebookEditorModelManager | null; private _modelManager: NotebookEditorModelManager | null;
constructor(private readonly _notebookService: INotebookService, label: string) { constructor(private readonly _notebookService: INotebookService) {
super(); super();
this._workerFactory = new DefaultWorkerFactory(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), label);
this._worker = null; this._worker = null;
this._modelManager = null; this._modelManager = null;
} }
// foreign host request
public fhr(method: string, args: any[]): Promise<any> {
throw new Error(`Not implemented!`);
}
computeDiff(original: URI, modified: URI) { computeDiff(original: URI, modified: URI) {
return this._withSyncedResources([original, modified]).then(proxy => { const proxy = this._ensureSyncedResources([original, modified]);
return proxy.computeDiff(original.toString(), modified.toString()); return proxy.$computeDiff(original.toString(), modified.toString());
});
} }
canPromptRecommendation(modelUri: URI) { canPromptRecommendation(modelUri: URI) {
return this._withSyncedResources([modelUri]).then(proxy => { const proxy = this._ensureSyncedResources([modelUri]);
return proxy.canPromptRecommendation(modelUri.toString()); return proxy.$canPromptRecommendation(modelUri.toString());
});
} }
private _getOrCreateModelManager(proxy: NotebookEditorSimpleWorker): NotebookEditorModelManager { private _getOrCreateModelManager(proxy: Proxied<NotebookEditorSimpleWorker>): NotebookEditorModelManager {
if (!this._modelManager) { if (!this._modelManager) {
this._modelManager = this._register(new NotebookEditorModelManager(proxy, this._notebookService)); this._modelManager = this._register(new NotebookEditorModelManager(proxy, this._notebookService));
} }
return this._modelManager; return this._modelManager;
} }
protected _withSyncedResources(resources: URI[]): Promise<NotebookEditorSimpleWorker> { protected _ensureSyncedResources(resources: URI[]): Proxied<NotebookEditorSimpleWorker> {
return this._getProxy().then((proxy) => { const proxy = this._getOrCreateWorker().proxy;
this._getOrCreateModelManager(proxy).ensureSyncedResources(resources); this._getOrCreateModelManager(proxy).ensureSyncedResources(resources);
return proxy; return proxy;
});
} }
private _getOrCreateWorker(): IWorkerClient<NotebookEditorSimpleWorker> { private _getOrCreateWorker(): IWorkerClient<NotebookEditorSimpleWorker> {
if (!this._worker) { if (!this._worker) {
try { try {
this._worker = this._register(new SimpleWorkerClient<NotebookEditorSimpleWorker, NotebookWorkerHost>( this._worker = this._register(createWebWorker<NotebookEditorSimpleWorker>(
this._workerFactory,
'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker', 'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker',
new NotebookWorkerHost(this) 'notebookEditorWorkerService'
)); ));
} catch (err) { } catch (err) {
// logOnceWebWorkerWarning(err); // logOnceWebWorkerWarning(err);
@ -261,15 +229,4 @@ class NotebookWorkerClient extends Disposable {
} }
return this._worker; return this._worker;
} }
protected _getProxy(): Promise<NotebookEditorSimpleWorker> {
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);
});
}
} }

Просмотреть файл

@ -6,12 +6,11 @@ import { ISequence, LcsDiff } from 'vs/base/common/diff/diff';
import { doHash, hash, numberHash } from 'vs/base/common/hash'; import { doHash, hash, numberHash } from 'vs/base/common/hash';
import { IDisposable } from 'vs/base/common/lifecycle'; import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; 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 * as model from 'vs/editor/common/model';
import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; 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 { 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 { 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 { VSBuffer } from 'vs/base/common/buffer';
import { SearchParams } from 'vs/editor/common/model/textModelSearch'; import { SearchParams } from 'vs/editor/common/model/textModelSearch';
@ -191,7 +190,7 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable
dispose(): void { 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( this._models[uri] = new MirrorNotebookDocument(URI.parse(uri), data.cells.map(dto => new MirrorCell(
(dto as unknown as IMainCellDto).handle, (dto as unknown as IMainCellDto).handle,
dto.source, dto.source,
@ -202,19 +201,19 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable
)), data.metadata); )), data.metadata);
} }
public acceptModelChanged(strURL: string, event: NotebookCellsChangedEventDto) { public $acceptModelChanged(strURL: string, event: NotebookCellsChangedEventDto) {
const model = this._models[strURL]; const model = this._models[strURL];
model?.acceptModelChanged(event); model?.acceptModelChanged(event);
} }
public acceptRemovedModel(strURL: string): void { public $acceptRemovedModel(strURL: string): void {
if (!this._models[strURL]) { if (!this._models[strURL]) {
return; return;
} }
delete this._models[strURL]; delete this._models[strURL];
} }
computeDiff(originalUrl: string, modifiedUrl: string): INotebookDiffResult { $computeDiff(originalUrl: string, modifiedUrl: string): INotebookDiffResult {
const original = this._getModel(originalUrl); const original = this._getModel(originalUrl);
const modified = this._getModel(modifiedUrl); 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 model = this._getModel(modelUrl);
const cells = model.cells; const cells = model.cells;
@ -315,9 +314,9 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable
} }
/** /**
* Called on the worker side * Defines the worker entry point. Must be exported and named `create`.
* @internal * @skipMangle
*/ */
export function create(host: INotebookWorkerHost): IRequestHandler { export function create(workerServer: IWorkerServer): IRequestHandler {
return new NotebookEditorSimpleWorker(); return new NotebookEditorSimpleWorker();
} }

Просмотреть файл

@ -9,24 +9,24 @@ import { IModelService } from 'vs/editor/common/services/model';
import { ILink } from 'vs/editor/common/languages'; import { ILink } from 'vs/editor/common/languages';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/services/output/common/output'; import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/services/output/common/output';
import { MonacoWebWorker, createWorkbenchWebWorker } from 'vs/editor/browser/services/webWorker'; import { OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer';
import { ICreateData, OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; 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 { 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 { export class OutputLinkProvider extends Disposable {
private static readonly DISPOSE_WORKER_TIME = 3 * 60 * 1000; // dispose worker after 3 minutes of inactivity private static readonly DISPOSE_WORKER_TIME = 3 * 60 * 1000; // dispose worker after 3 minutes of inactivity
private worker?: MonacoWebWorker<OutputLinkComputer>; private worker?: OutputLinkWorkerClient;
private disposeWorkerScheduler: RunOnceScheduler; private disposeWorkerScheduler: RunOnceScheduler;
private linkProviderRegistration: IDisposable | undefined; private linkProviderRegistration: IDisposable | undefined;
constructor( constructor(
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IModelService private readonly modelService: IModelService, @IModelService private readonly modelService: IModelService,
@ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService,
@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,
) { ) {
super(); super();
@ -65,28 +65,18 @@ export class OutputLinkProvider extends Disposable {
this.disposeWorkerScheduler.cancel(); this.disposeWorkerScheduler.cancel();
} }
private getOrCreateWorker(): MonacoWebWorker<OutputLinkComputer> { private getOrCreateWorker(): OutputLinkWorkerClient {
this.disposeWorkerScheduler.schedule(); this.disposeWorkerScheduler.schedule();
if (!this.worker) { if (!this.worker) {
const createData: ICreateData = { this.worker = new OutputLinkWorkerClient(this.contextService, this.modelService);
workspaceFolders: this.contextService.getWorkspace().folders.map(folder => folder.uri.toString())
};
this.worker = createWorkbenchWebWorker<OutputLinkComputer>(this.modelService, this.languageConfigurationService, {
moduleId: 'vs/workbench/contrib/output/common/outputLinkComputer',
createData,
label: 'outputLinkComputer'
});
} }
return this.worker; return this.worker;
} }
private async provideLinks(modelUri: URI): Promise<ILink[]> { private async provideLinks(modelUri: URI): Promise<ILink[]> {
const linkComputer = await this.getOrCreateWorker().withSyncedResources([modelUri]); return this.getOrCreateWorker().provideLinks(modelUri);
return linkComputer.computeLinks(modelUri.toString());
} }
private disposeWorker(): void { private disposeWorker(): void {
@ -96,3 +86,32 @@ export class OutputLinkProvider extends Disposable {
} }
} }
} }
class OutputLinkWorkerClient extends Disposable {
private readonly _workerClient: IWorkerClient<OutputLinkComputer>;
private readonly _workerTextModelSyncClient: WorkerTextModelSyncClient;
private readonly _initializeBarrier: Promise<void>;
constructor(
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IModelService modelService: IModelService,
) {
super();
this._workerClient = this._register(createWebWorker<OutputLinkComputer>(
'vs/workbench/contrib/output/common/outputLinkComputer',
'outputLinkComputer'
));
this._workerTextModelSyncClient = WorkerTextModelSyncClient.create(this._workerClient, modelService);
this._initializeBarrier = this._ensureWorkspaceFolders();
}
private async _ensureWorkspaceFolders(): Promise<void> {
await this._workerClient.proxy.$setWorkspaceFolders(this.contextService.getWorkspace().folders.map(folder => folder.uri.toString()));
}
public async provideLinks(modelUri: URI): Promise<ILink[]> {
await this._initializeBarrier;
await this._workerTextModelSyncClient.ensureSyncedResources([modelUri]);
return this._workerClient.proxy.$computeLinks(modelUri.toString());
}
}

Просмотреть файл

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
export interface INotebookWorkerHost { import { create } from './outputLinkComputer';
// foreign host request import { bootstrapSimpleWorker } from 'vs/base/common/worker/simpleWorkerBootstrap';
fhr(method: string, args: any[]): Promise<any>;
} bootstrapSimpleWorker(create);

Просмотреть файл

@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { ILink } from 'vs/editor/common/languages';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import * as extpath from 'vs/base/common/extpath'; 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 { Range } from 'vs/editor/common/core/range';
import { isWindows } from 'vs/base/common/platform'; import { isWindows } from 'vs/base/common/platform';
import { Schemas } from 'vs/base/common/network'; import { Schemas } from 'vs/base/common/network';
import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker';
export interface ICreateData { import { WorkerTextModelSyncServer, ICommonModel } from 'vs/editor/common/services/textModelSync/textModelSync.impl';
workspaceFolders: string[];
}
export interface IResourceCreator { export interface IResourceCreator {
toResource: (folderRelativePath: string) => URI | null; toResource: (folderRelativePath: string) => URI | null;
} }
export class OutputLinkComputer { export class OutputLinkComputer implements IRequestHandler {
_requestHandlerBrand: any;
private readonly workerTextModelSyncServer = new WorkerTextModelSyncServer();
private patterns = new Map<URI /* folder uri */, RegExp[]>(); private patterns = new Map<URI /* folder uri */, RegExp[]>();
constructor(private ctx: IWorkerContext, createData: ICreateData) { constructor(workerServer: IWorkerServer) {
this.computePatterns(createData); 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 // Produce patterns for each workspace root we are configured with
// This means that we will be able to detect links for paths that // This means that we will be able to detect links for paths that
// contain any of the workspace roots as segments. // 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) .sort((resourceStrA, resourceStrB) => resourceStrB.length - resourceStrA.length) // longest paths first (for https://github.com/microsoft/vscode/issues/88121)
.map(resourceStr => URI.parse(resourceStr)); .map(resourceStr => URI.parse(resourceStr));
@ -43,13 +47,11 @@ export class OutputLinkComputer {
} }
} }
private getModel(uri: string): IMirrorModel | undefined { private getModel(uri: string): ICommonModel | undefined {
const models = this.ctx.getMirrorModels(); return this.workerTextModelSyncServer.getModel(uri);
return models.find(model => model.uri.toString() === uri);
} }
computeLinks(uri: string): ILink[] { $computeLinks(uri: string): ILink[] {
const model = this.getModel(uri); const model = this.getModel(uri);
if (!model) { if (!model) {
return []; 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 { * Defines the worker entry point. Must be exported and named `create`.
return new OutputLinkComputer(ctx, createData); * @skipMangle
*/
export function create(workerServer: IWorkerServer): OutputLinkComputer {
return new OutputLinkComputer(workerServer);
} }

Просмотреть файл

@ -3,8 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { bootstrapSimpleWorker } from 'vs/base/common/worker/simpleWorkerBootstrap';
import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost';
bootstrapSimpleWorker<IEditorWorkerHost>(host => new LanguageDetectionSimpleWorker(host, () => { return {}; })); bootstrapSimpleWorker(create);

Просмотреть файл

@ -6,38 +6,48 @@
import type { ModelOperations, ModelResult } from '@vscode/vscode-languagedetection'; import type { ModelOperations, ModelResult } from '@vscode/vscode-languagedetection';
import { importAMDNodeModule } from 'vs/amdX'; import { importAMDNodeModule } from 'vs/amdX';
import { StopWatch } from 'vs/base/common/stopwatch'; import { StopWatch } from 'vs/base/common/stopwatch';
import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker';
import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; import { LanguageDetectionWorkerHost, ILanguageDetectionWorker } from 'vs/workbench/services/languageDetection/browser/languageDetectionWorker.protocol';
import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; import { WorkerTextModelSyncServer } from 'vs/editor/common/services/textModelSync/textModelSync.impl';
type RegexpModel = { detect: (inp: string, langBiases: Record<string, number>, supportedLangs?: string[]) => string | undefined }; type RegexpModel = { detect: (inp: string, langBiases: Record<string, number>, supportedLangs?: string[]) => string | undefined };
/** /**
* Called on the worker side * Defines the worker entry point. Must be exported and named `create`.
* @internal * @skipMangle
*/ */
export function create(host: IEditorWorkerHost): IRequestHandler { export function create(workerServer: IWorkerServer): IRequestHandler {
return new LanguageDetectionSimpleWorker(host, null); return new LanguageDetectionSimpleWorker(workerServer);
} }
/** /**
* @internal * @internal
*/ */
export class LanguageDetectionSimpleWorker extends EditorSimpleWorker { export class LanguageDetectionSimpleWorker implements ILanguageDetectionWorker {
_requestHandlerBrand: any;
private static readonly expectedRelativeConfidence = 0.2; private static readonly expectedRelativeConfidence = 0.2;
private static readonly positiveConfidenceCorrectionBucket1 = 0.05; private static readonly positiveConfidenceCorrectionBucket1 = 0.05;
private static readonly positiveConfidenceCorrectionBucket2 = 0.025; private static readonly positiveConfidenceCorrectionBucket2 = 0.025;
private static readonly negativeConfidenceCorrection = 0.5; private static readonly negativeConfidenceCorrection = 0.5;
private readonly _workerTextModelSyncServer = new WorkerTextModelSyncServer();
private readonly _host: LanguageDetectionWorkerHost;
private _regexpModel: RegexpModel | undefined; private _regexpModel: RegexpModel | undefined;
private _regexpLoadFailed: boolean = false; private _regexpLoadFailed: boolean = false;
private _modelOperations: ModelOperations | undefined; private _modelOperations: ModelOperations | undefined;
private _loadFailed: boolean = false; private _loadFailed: boolean = false;
private modelIdToCoreId = new Map<string, string>(); private modelIdToCoreId = new Map<string, string | undefined>();
public async detectLanguage(uri: string, langBiases: Record<string, number> | undefined, preferHistory: boolean, supportedLangs?: string[]): Promise<string | undefined> { constructor(workerServer: IWorkerServer) {
this._host = LanguageDetectionWorkerHost.getChannel(workerServer);
this._workerTextModelSyncServer.bindToServer(workerServer);
}
public async $detectLanguage(uri: string, langBiases: Record<string, number> | undefined, preferHistory: boolean, supportedLangs?: string[]): Promise<string | undefined> {
const languages: string[] = []; const languages: string[] = [];
const confidences: number[] = []; const confidences: number[] = [];
const stopWatch = new StopWatch(); const stopWatch = new StopWatch();
@ -47,7 +57,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker {
const neuralResolver = async () => { const neuralResolver = async () => {
for await (const language of this.detectLanguagesImpl(documentTextSample)) { for await (const language of this.detectLanguagesImpl(documentTextSample)) {
if (!this.modelIdToCoreId.has(language.languageId)) { 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); const coreId = this.modelIdToCoreId.get(language.languageId);
if (coreId && (!supportedLangs?.length || supportedLangs.includes(coreId))) { if (coreId && (!supportedLangs?.length || supportedLangs.includes(coreId))) {
@ -58,7 +68,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker {
stopWatch.stop(); stopWatch.stop();
if (languages.length) { if (languages.length) {
this._host.fhr('sendTelemetryEvent', [languages, confidences, stopWatch.elapsed()]); this._host.$sendTelemetryEvent(languages, confidences, stopWatch.elapsed());
return languages[0]; return languages[0];
} }
return undefined; return undefined;
@ -82,7 +92,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker {
} }
private getTextForDetection(uri: string): string | undefined { private getTextForDetection(uri: string): string | undefined {
const editorModel = this._getModel(uri); const editorModel = this._workerTextModelSyncServer.getModel(uri);
if (!editorModel) { return; } if (!editorModel) { return; }
const end = editorModel.positionAt(10000); const end = editorModel.positionAt(10000);
@ -102,7 +112,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker {
if (this._regexpModel) { if (this._regexpModel) {
return this._regexpModel; return this._regexpModel;
} }
const uri: string = await this._host.fhr('getRegexpModelUri', []); const uri: string = await this._host.$getRegexpModelUri();
try { try {
this._regexpModel = await importAMDNodeModule(uri, '') as RegexpModel; this._regexpModel = await importAMDNodeModule(uri, '') as RegexpModel;
return this._regexpModel; return this._regexpModel;
@ -137,11 +147,11 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker {
return this._modelOperations; 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'); const { ModelOperations } = await importAMDNodeModule(uri, '') as typeof import('@vscode/vscode-languagedetection');
this._modelOperations = new ModelOperations({ this._modelOperations = new ModelOperations({
modelJsonLoaderFunc: async () => { modelJsonLoaderFunc: async () => {
const response = await fetch(await this._host.fhr('getModelJsonUri', [])); const response = await fetch(await this._host.$getModelJsonUri());
try { try {
const modelJSON = await response.json(); const modelJSON = await response.json();
return modelJSON; return modelJSON;
@ -151,7 +161,7 @@ export class LanguageDetectionSimpleWorker extends EditorSimpleWorker {
} }
}, },
weightsLoaderFunc: async () => { weightsLoaderFunc: async () => {
const response = await fetch(await this._host.fhr('getWeightsUri', [])); const response = await fetch(await this._host.$getWeightsUri());
const buffer = await response.arrayBuffer(); const buffer = await response.arrayBuffer();
return buffer; return buffer;
} }

Просмотреть файл

@ -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>(LanguageDetectionWorkerHost.CHANNEL_NAME);
}
public static setChannel(workerClient: IWorkerClient<any>, obj: LanguageDetectionWorkerHost): void {
workerClient.setChannel<LanguageDetectionWorkerHost>(LanguageDetectionWorkerHost.CHANNEL_NAME, obj);
}
abstract $getIndexJsUri(): Promise<string>;
abstract $getLanguageId(languageIdOrExt: string | undefined): Promise<string | undefined>;
abstract $sendTelemetryEvent(languages: string[], confidences: number[], timeSpent: number): Promise<void>;
abstract $getRegexpModelUri(): Promise<string>;
abstract $getModelJsonUri(): Promise<string>;
abstract $getWeightsUri(): Promise<string>;
}
export interface ILanguageDetectionWorker {
$detectLanguage(uri: string, langBiases: Record<string, number> | undefined, preferHistory: boolean, supportedLangs?: string[]): Promise<string | undefined>;
}

Просмотреть файл

@ -12,12 +12,9 @@ import { ILanguageService } from 'vs/editor/common/languages/language';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { isWeb } from 'vs/base/common/platform'; import { isWeb } from 'vs/base/common/platform';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; 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 { 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 { 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 { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; 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 { LRUCache } from 'vs/base/common/map';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { canASAR } from 'vs/base/common/amd'; 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; const TOP_LANG_COUNTS = 12;
@ -62,8 +62,7 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet
@IEditorService private readonly _editorService: IEditorService, @IEditorService private readonly _editorService: IEditorService,
@ITelemetryService telemetryService: ITelemetryService, @ITelemetryService telemetryService: ITelemetryService,
@IStorageService storageService: IStorageService, @IStorageService storageService: IStorageService,
@ILogService private readonly _logService: ILogService, @ILogService private readonly _logService: ILogService
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService
) { ) {
super(); super();
@ -85,7 +84,6 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet
useAsar useAsar
? FileAccess.asBrowserUri(`${regexpModuleLocationAsar}/dist/index.js`).toString(true) ? FileAccess.asBrowserUri(`${regexpModuleLocationAsar}/dist/index.js`).toString(true)
: FileAccess.asBrowserUri(`${regexpModuleLocation}/dist/index.js`).toString(true), : FileAccess.asBrowserUri(`${regexpModuleLocation}/dist/index.js`).toString(true),
languageConfigurationService
)); ));
this.initEditorOpenedListeners(storageService); this.initEditorOpenedListeners(storageService);
@ -179,80 +177,45 @@ export class LanguageDetectionService extends Disposable implements ILanguageDet
} }
} }
export interface IWorkerClient<W> { export class LanguageDetectionWorkerClient extends Disposable {
getProxyObject(): Promise<W>; private worker: {
dispose(): void; workerClient: IWorkerClient<ILanguageDetectionWorker>;
} workerTextModelSyncClient: WorkerTextModelSyncClient;
} | undefined;
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<void> {
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<LanguageDetectionStats, LanguageDetectionStatsClassification>('automaticlanguagedetection.stats', {
languages: languages.join(','),
confidences: confidences.join(','),
timeSpent
});
}
}
export class LanguageDetectionWorkerClient extends EditorWorkerClient {
private workerPromise: Promise<IWorkerClient<LanguageDetectionSimpleWorker>> | undefined;
constructor( constructor(
modelService: IModelService, private readonly _modelService: IModelService,
private readonly _languageService: ILanguageService, private readonly _languageService: ILanguageService,
private readonly _telemetryService: ITelemetryService, private readonly _telemetryService: ITelemetryService,
private readonly _indexJsUri: string, private readonly _indexJsUri: string,
private readonly _modelJsonUri: string, private readonly _modelJsonUri: string,
private readonly _weightsUri: string, private readonly _weightsUri: string,
private readonly _regexpModelUri: string, private readonly _regexpModelUri: string,
languageConfigurationService: ILanguageConfigurationService,
) { ) {
super(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, true, 'languageDetectionWorkerService', languageConfigurationService); super();
} }
private _getOrCreateLanguageDetectionWorker(): Promise<IWorkerClient<LanguageDetectionSimpleWorker>> { private _getOrCreateLanguageDetectionWorker(): {
if (this.workerPromise) { workerClient: IWorkerClient<ILanguageDetectionWorker>;
return this.workerPromise; workerTextModelSyncClient: WorkerTextModelSyncClient;
} } {
if (!this.worker) {
this.workerPromise = new Promise((resolve, reject) => { const workerClient = this._register(createWebWorker<ILanguageDetectionWorker>(
resolve(this._register(new SimpleWorkerClient<LanguageDetectionSimpleWorker, EditorWorkerHost>(
this._workerFactory,
'vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker', 'vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker',
new EditorWorkerHost(this) 'languageDetectionWorkerService'
))); ));
}); LanguageDetectionWorkerHost.setChannel(workerClient, {
$getIndexJsUri: async () => this.getIndexJsUri(),
return this.workerPromise; $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 { private _guessLanguageIdByUri(uri: URI): string | undefined {
@ -263,30 +226,6 @@ export class LanguageDetectionWorkerClient extends EditorWorkerClient {
return undefined; return undefined;
} }
protected override async _getProxy(): Promise<LanguageDetectionSimpleWorker> {
return (await this._getOrCreateLanguageDetectionWorker()).getProxyObject();
}
// foreign host request
public override async fhr(method: string, args: any[]): Promise<any> {
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() { async getIndexJsUri() {
return this._indexJsUri; return this._indexJsUri;
} }
@ -332,8 +271,9 @@ export class LanguageDetectionWorkerClient extends EditorWorkerClient {
return quickGuess; return quickGuess;
} }
await this._withSyncedResources([resource]); const { workerClient, workerTextModelSyncClient } = this._getOrCreateLanguageDetectionWorker();
const modelId = await (await this._getProxy()).detectLanguage(resource.toString(), langBiases, preferHistory, supportedLangs); await workerTextModelSyncClient.ensureSyncedResources([resource]);
const modelId = await workerClient.proxy.$detectLanguage(resource.toString(), langBiases, preferHistory, supportedLangs);
const languageId = this.getLanguageId(modelId); const languageId = this.getLanguageId(modelId);
const LanguageDetectionStatsId = 'automaticlanguagedetection.perf'; const LanguageDetectionStatsId = 'automaticlanguagedetection.perf';

Просмотреть файл

@ -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 { 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 { SearchService } from 'vs/workbench/services/search/common/searchService';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; 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 { 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 { 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 { memoize } from 'vs/base/common/decorators';
import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider'; 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 { URI, UriComponents } from 'vs/base/common/uri';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import { localize } from 'vs/nls'; 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<ILocalFileSearchSimpleWorker> | null; protected _worker: IWorkerClient<ILocalFileSearchSimpleWorker> | null;
protected readonly _workerFactory: DefaultWorkerFactory;
private readonly _onDidReceiveTextSearchMatch = new Emitter<{ match: IFileMatch<UriComponents>; queryId: number }>(); private readonly _onDidReceiveTextSearchMatch = new Emitter<{ match: IFileMatch<UriComponents>; queryId: number }>();
readonly onDidReceiveTextSearchMatch: Event<{ match: IFileMatch<UriComponents>; queryId: number }> = this._onDidReceiveTextSearchMatch.event; readonly onDidReceiveTextSearchMatch: Event<{ match: IFileMatch<UriComponents>; queryId: number }> = this._onDidReceiveTextSearchMatch.event;
@ -64,7 +63,6 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe
) { ) {
super(); super();
this._worker = null; this._worker = null;
this._workerFactory = new DefaultWorkerFactory(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), 'localFileSearchWorker');
} }
sendTextSearchMatch(match: IFileMatch<UriComponents>, queryId: number): void { sendTextSearchMatch(match: IFileMatch<UriComponents>, queryId: number): void {
@ -77,15 +75,15 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe
} }
private async cancelQuery(queryId: number) { private async cancelQuery(queryId: number) {
const proxy = await this._getOrCreateWorker().getProxyObject(); const proxy = this._getOrCreateWorker().proxy;
proxy.cancelQuery(queryId); proxy.$cancelQuery(queryId);
} }
async textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): Promise<ISearchComplete> { async textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): Promise<ISearchComplete> {
try { try {
const queryDisposables = new DisposableStore(); const queryDisposables = new DisposableStore();
const proxy = await this._getOrCreateWorker().getProxyObject(); const proxy = this._getOrCreateWorker().proxy;
const results: IFileMatch[] = []; const results: IFileMatch[] = [];
let limitHit = false; let limitHit = false;
@ -114,7 +112,7 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe
})); }));
const ignorePathCasing = this.uriIdentityService.extUri.ignorePathCasing(fq.folder); 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) { for (const folderResult of folderResults.results) {
results.push(revive(folderResult)); results.push(revive(folderResult));
} }
@ -144,7 +142,7 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe
const queryDisposables = new DisposableStore(); const queryDisposables = new DisposableStore();
let limitHit = false; let limitHit = false;
const proxy = await this._getOrCreateWorker().getProxyObject(); const proxy = this._getOrCreateWorker().proxy;
const results: IFileMatch[] = []; const results: IFileMatch[] = [];
await Promise.all(query.folderQueries.map(async fq => { await Promise.all(query.folderQueries.map(async fq => {
const queryId = this.queryId++; const queryId = this.queryId++;
@ -156,7 +154,7 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe
return; return;
} }
const caseSensitive = this.uriIdentityService.extUri.ignorePathCasing(fq.folder); 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) { for (const folderResult of folderResults.results) {
results.push({ resource: URI.joinPath(fq.folder, folderResult) }); results.push({ resource: URI.joinPath(fq.folder, folderResult) });
} }
@ -185,11 +183,15 @@ export class LocalFileSearchWorkerClient extends Disposable implements ISearchRe
private _getOrCreateWorker(): IWorkerClient<ILocalFileSearchSimpleWorker> { private _getOrCreateWorker(): IWorkerClient<ILocalFileSearchSimpleWorker> {
if (!this._worker) { if (!this._worker) {
try { try {
this._worker = this._register(new SimpleWorkerClient<ILocalFileSearchSimpleWorker, ILocalFileSearchSimpleWorkerHost>( this._worker = this._register(createWebWorker<ILocalFileSearchSimpleWorker>(
this._workerFactory,
'vs/workbench/services/search/worker/localFileSearch', 'vs/workbench/services/search/worker/localFileSearch',
this, 'localFileSearchWorker'
)); ));
LocalFileSearchSimpleWorkerHost.setChannel(this._worker, {
$sendTextSearchMatch: (match, queryId) => {
return this.sendTextSearchMatch(match, queryId);
}
});
} catch (err) { } catch (err) {
logOnceWebWorkerWarning(err); logOnceWebWorkerWarning(err);
throw err; throw err;

Просмотреть файл

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { UriComponents } from 'vs/base/common/uri'; 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'; import { IFileMatch, IFileQueryProps, IFolderQuery, ITextQueryProps } from 'vs/workbench/services/search/common/search';
export interface IWorkerTextSearchComplete { export interface IWorkerTextSearchComplete {
@ -41,12 +42,20 @@ export interface IWorkerFileSystemFileHandle extends IWorkerFileSystemHandle {
export interface ILocalFileSearchSimpleWorker { export interface ILocalFileSearchSimpleWorker {
_requestHandlerBrand: any; _requestHandlerBrand: any;
cancelQuery(queryId: number): void; $cancelQuery(queryId: number): void;
listDirectory(handle: IWorkerFileSystemDirectoryHandle, queryProps: IFileQueryProps<UriComponents>, folderQuery: IFolderQuery, ignorePathCasing: boolean, queryId: number): Promise<IWorkerFileSearchComplete>; $listDirectory(handle: IWorkerFileSystemDirectoryHandle, queryProps: IFileQueryProps<UriComponents>, folderQuery: IFolderQuery, ignorePathCasing: boolean, queryId: number): Promise<IWorkerFileSearchComplete>;
searchDirectory(handle: IWorkerFileSystemDirectoryHandle, queryProps: ITextQueryProps<UriComponents>, folderQuery: IFolderQuery, ignorePathCasing: boolean, queryId: number): Promise<IWorkerTextSearchComplete>; $searchDirectory(handle: IWorkerFileSystemDirectoryHandle, queryProps: ITextQueryProps<UriComponents>, folderQuery: IFolderQuery, ignorePathCasing: boolean, queryId: number): Promise<IWorkerTextSearchComplete>;
} }
export interface ILocalFileSearchSimpleWorkerHost { export abstract class LocalFileSearchSimpleWorkerHost {
sendTextSearchMatch(match: IFileMatch<UriComponents>, queryId: number): void; public static CHANNEL_NAME = 'localFileSearchWorkerHost';
public static getChannel(workerServer: IWorkerServer): LocalFileSearchSimpleWorkerHost {
return workerServer.getChannel<LocalFileSearchSimpleWorkerHost>(LocalFileSearchSimpleWorkerHost.CHANNEL_NAME);
}
public static setChannel(workerClient: IWorkerClient<any>, obj: LocalFileSearchSimpleWorkerHost): void {
workerClient.setChannel<LocalFileSearchSimpleWorkerHost>(LocalFileSearchSimpleWorkerHost.CHANNEL_NAME, obj);
}
abstract $sendTextSearchMatch(match: IFileMatch<UriComponents>, queryId: number): void;
} }

Просмотреть файл

@ -5,8 +5,8 @@
import * as glob from 'vs/base/common/glob'; import * as glob from 'vs/base/common/glob';
import { UriComponents, URI } from 'vs/base/common/uri'; import { UriComponents, URI } from 'vs/base/common/uri';
import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker';
import { ILocalFileSearchSimpleWorker, ILocalFileSearchSimpleWorkerHost, IWorkerFileSearchComplete, IWorkerFileSystemDirectoryHandle, IWorkerFileSystemHandle, IWorkerTextSearchComplete } from 'vs/workbench/services/search/common/localFileSearchWorkerTypes'; 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 { ICommonQueryProps, IFileMatch, IFileQueryProps, IFolderQuery, IPatternInfo, ITextQueryProps, } from 'vs/workbench/services/search/common/search';
import * as paths from 'vs/base/common/path'; import * as paths from 'vs/base/common/path';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
@ -49,21 +49,24 @@ const time = async <T>(name: string, task: () => Promise<T> | T) => {
}; };
/** /**
* Called on the worker side * Defines the worker entry point. Must be exported and named `create`.
* @internal * @skipMangle
*/ */
export function create(host: ILocalFileSearchSimpleWorkerHost): IRequestHandler { export function create(workerServer: IWorkerServer): IRequestHandler {
return new LocalFileSearchSimpleWorker(host); return new LocalFileSearchSimpleWorker(workerServer);
} }
export class LocalFileSearchSimpleWorker implements ILocalFileSearchSimpleWorker, IRequestHandler { export class LocalFileSearchSimpleWorker implements ILocalFileSearchSimpleWorker, IRequestHandler {
_requestHandlerBrand: any; _requestHandlerBrand: any;
private readonly host: LocalFileSearchSimpleWorkerHost;
cancellationTokens: Map<number, CancellationTokenSource> = new Map(); cancellationTokens: Map<number, CancellationTokenSource> = 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(); this.cancellationTokens.get(queryId)?.cancel();
} }
@ -73,7 +76,7 @@ export class LocalFileSearchSimpleWorker implements ILocalFileSearchSimpleWorker
return source; return source;
} }
async listDirectory(handle: IWorkerFileSystemDirectoryHandle, query: IFileQueryProps<UriComponents>, folderQuery: IFolderQuery<UriComponents>, ignorePathCasing: boolean, queryId: number): Promise<IWorkerFileSearchComplete> { async $listDirectory(handle: IWorkerFileSystemDirectoryHandle, query: IFileQueryProps<UriComponents>, folderQuery: IFolderQuery<UriComponents>, ignorePathCasing: boolean, queryId: number): Promise<IWorkerFileSearchComplete> {
const revivedFolderQuery = reviveFolderQuery(folderQuery); const revivedFolderQuery = reviveFolderQuery(folderQuery);
const extUri = new ExtUri(() => ignorePathCasing); const extUri = new ExtUri(() => ignorePathCasing);
@ -108,7 +111,7 @@ export class LocalFileSearchSimpleWorker implements ILocalFileSearchSimpleWorker
}; };
} }
async searchDirectory(handle: IWorkerFileSystemDirectoryHandle, query: ITextQueryProps<UriComponents>, folderQuery: IFolderQuery<UriComponents>, ignorePathCasing: boolean, queryId: number): Promise<IWorkerTextSearchComplete> { async $searchDirectory(handle: IWorkerFileSystemDirectoryHandle, query: ITextQueryProps<UriComponents>, folderQuery: IFolderQuery<UriComponents>, ignorePathCasing: boolean, queryId: number): Promise<IWorkerTextSearchComplete> {
const revivedQuery = reviveFolderQuery(folderQuery); const revivedQuery = reviveFolderQuery(folderQuery);
const extUri = new ExtUri(() => ignorePathCasing); const extUri = new ExtUri(() => ignorePathCasing);
@ -153,7 +156,7 @@ export class LocalFileSearchSimpleWorker implements ILocalFileSearchSimpleWorker
resource: URI.joinPath(revivedQuery.folder, file.path), resource: URI.joinPath(revivedQuery.folder, file.path),
results: fileResults, results: fileResults,
}; };
this.host.sendTextSearchMatch(match, queryId); this.host.$sendTextSearchMatch(match, queryId);
results.push(match); results.push(match);
} }
}; };

Просмотреть файл

@ -6,6 +6,7 @@
import { importAMDNodeModule } from 'vs/amdX'; import { importAMDNodeModule } from 'vs/amdX';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { IObservable, autorun, keepObserved } from 'vs/base/common/observable'; 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 { countEOL } from 'vs/editor/common/core/eolCounter';
import { LineRange } from 'vs/editor/common/core/lineRange'; import { LineRange } from 'vs/editor/common/core/lineRange';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
@ -39,7 +40,7 @@ export class TextMateWorkerTokenizerController extends Disposable {
constructor( constructor(
private readonly _model: ITextModel, private readonly _model: ITextModel,
private readonly _worker: TextMateTokenizationWorker, private readonly _worker: Proxied<TextMateTokenizationWorker>,
private readonly _languageIdCodec: ILanguageIdCodec, private readonly _languageIdCodec: ILanguageIdCodec,
private readonly _backgroundTokenizationStore: IBackgroundTokenizationStore, private readonly _backgroundTokenizationStore: IBackgroundTokenizationStore,
private readonly _configurationService: IConfigurationService, private readonly _configurationService: IConfigurationService,
@ -56,7 +57,7 @@ export class TextMateWorkerTokenizerController extends Disposable {
changes: changesToString(e.changes), changes: changesToString(e.changes),
}); });
} }
this._worker.acceptModelChanged(this.controllerId, e); this._worker.$acceptModelChanged(this.controllerId, e);
this._pendingChanges.push(e); this._pendingChanges.push(e);
})); }));
@ -64,7 +65,7 @@ export class TextMateWorkerTokenizerController extends Disposable {
const languageId = this._model.getLanguageId(); const languageId = this._model.getLanguageId();
const encodedLanguageId = const encodedLanguageId =
this._languageIdCodec.encodeLanguageId(languageId); this._languageIdCodec.encodeLanguageId(languageId);
this._worker.acceptModelLanguageChanged( this._worker.$acceptModelLanguageChanged(
this.controllerId, this.controllerId,
languageId, languageId,
encodedLanguageId encodedLanguageId
@ -73,7 +74,7 @@ export class TextMateWorkerTokenizerController extends Disposable {
const languageId = this._model.getLanguageId(); const languageId = this._model.getLanguageId();
const encodedLanguageId = this._languageIdCodec.encodeLanguageId(languageId); const encodedLanguageId = this._languageIdCodec.encodeLanguageId(languageId);
this._worker.acceptNewModel({ this._worker.$acceptNewModel({
uri: this._model.uri, uri: this._model.uri,
versionId: this._model.getVersionId(), versionId: this._model.getVersionId(),
lines: this._model.getLinesContent(), lines: this._model.getLinesContent(),
@ -87,17 +88,17 @@ export class TextMateWorkerTokenizerController extends Disposable {
this._register(autorun(reader => { this._register(autorun(reader => {
/** @description update maxTokenizationLineLength */ /** @description update maxTokenizationLineLength */
const maxTokenizationLineLength = this._maxTokenizationLineLength.read(reader); const maxTokenizationLineLength = this._maxTokenizationLineLength.read(reader);
this._worker.acceptMaxTokenizationLineLength(this.controllerId, maxTokenizationLineLength); this._worker.$acceptMaxTokenizationLineLength(this.controllerId, maxTokenizationLineLength);
})); }));
} }
public override dispose(): void { public override dispose(): void {
super.dispose(); super.dispose();
this._worker.acceptRemovedModel(this.controllerId); this._worker.$acceptRemovedModel(this.controllerId);
} }
public requestTokens(startLineNumber: number, endLineNumberExclusive: number): void { public requestTokens(startLineNumber: number, endLineNumberExclusive: number): void {
this._worker.retokenize(this.controllerId, startLineNumber, endLineNumberExclusive); this._worker.$retokenize(this.controllerId, startLineNumber, endLineNumberExclusive);
} }
/** /**

Просмотреть файл

@ -9,28 +9,28 @@ import { AppResourcePath, FileAccess, nodeModulesAsarPath, nodeModulesPath } fro
import { IObservable } from 'vs/base/common/observable'; import { IObservable } from 'vs/base/common/observable';
import { isWeb } from 'vs/base/common/platform'; import { isWeb } from 'vs/base/common/platform';
import { URI, UriComponents } from 'vs/base/common/uri'; 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 { IBackgroundTokenizationStore, IBackgroundTokenizer } from 'vs/editor/common/languages';
import { ILanguageService } from 'vs/editor/common/languages/language'; import { ILanguageService } from 'vs/editor/common/languages/language';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ITextModel } from 'vs/editor/common/model'; import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/model';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; 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 { TextMateWorkerTokenizerController } from 'vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController';
import { IValidGrammarDefinition } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; import { IValidGrammarDefinition } from 'vs/workbench/services/textMate/common/TMScopeRegistry';
import type { IRawTheme } from 'vscode-textmate'; 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 { export class ThreadedBackgroundTokenizerFactory implements IDisposable {
private static _reportedMismatchingTokens = false; private static _reportedMismatchingTokens = false;
private _workerProxyPromise: Promise<TextMateTokenizationWorker | null> | null = null; private _workerProxyPromise: Promise<Proxied<TextMateTokenizationWorker> | null> | null = null;
private _worker: MonacoWebWorker<TextMateTokenizationWorker> | null = null; private _worker: IWorkerClient<TextMateTokenizationWorker> | null = null;
private _workerProxy: TextMateTokenizationWorker | null = null; private _workerProxy: Proxied<TextMateTokenizationWorker> | null = null;
private readonly _workerTokenizerControllers = new Map</* backgroundTokenizerId */number, TextMateWorkerTokenizerController>(); private readonly _workerTokenizerControllers = new Map</* backgroundTokenizerId */number, TextMateWorkerTokenizerController>();
private _currentTheme: IRawTheme | null = null; 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 _reportTokenizationTime: (timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean) => void,
private readonly _shouldTokenizeAsync: () => boolean, private readonly _shouldTokenizeAsync: () => boolean,
@IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, @IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService,
@IModelService private readonly _modelService: IModelService,
@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService,
@IConfigurationService private readonly _configurationService: IConfigurationService, @IConfigurationService private readonly _configurationService: IConfigurationService,
@ILanguageService private readonly _languageService: ILanguageService, @ILanguageService private readonly _languageService: ILanguageService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService, @IEnvironmentService private readonly _environmentService: IEnvironmentService,
@ -116,18 +114,18 @@ export class ThreadedBackgroundTokenizerFactory implements IDisposable {
this._currentTheme = theme; this._currentTheme = theme;
this._currentTokenColorMap = colorMap; this._currentTokenColorMap = colorMap;
if (this._currentTheme && this._currentTokenColorMap && this._workerProxy) { if (this._currentTheme && this._currentTokenColorMap && this._workerProxy) {
this._workerProxy.acceptTheme(this._currentTheme, this._currentTokenColorMap); this._workerProxy.$acceptTheme(this._currentTheme, this._currentTokenColorMap);
} }
} }
private _getWorkerProxy(): Promise<TextMateTokenizationWorker | null> { private _getWorkerProxy(): Promise<Proxied<TextMateTokenizationWorker> | null> {
if (!this._workerProxyPromise) { if (!this._workerProxyPromise) {
this._workerProxyPromise = this._createWorkerProxy(); this._workerProxyPromise = this._createWorkerProxy();
} }
return this._workerProxyPromise; return this._workerProxyPromise;
} }
private async _createWorkerProxy(): Promise<TextMateTokenizationWorker | null> { private async _createWorkerProxy(): Promise<Proxied<TextMateTokenizationWorker> | null> {
const onigurumaModuleLocation: AppResourcePath = `${nodeModulesPath}/vscode-oniguruma`; const onigurumaModuleLocation: AppResourcePath = `${nodeModulesPath}/vscode-oniguruma`;
const onigurumaModuleLocationAsar: AppResourcePath = `${nodeModulesAsarPath}/vscode-oniguruma`; const onigurumaModuleLocationAsar: AppResourcePath = `${nodeModulesAsarPath}/vscode-oniguruma`;
@ -139,12 +137,16 @@ export class ThreadedBackgroundTokenizerFactory implements IDisposable {
grammarDefinitions: this._grammarDefinitions, grammarDefinitions: this._grammarDefinitions,
onigurumaWASMUri: FileAccess.asBrowserUri(onigurumaWASM).toString(true), onigurumaWASMUri: FileAccess.asBrowserUri(onigurumaWASM).toString(true),
}; };
const host: ITextMateWorkerHost = { const worker = this._worker = createWebWorker<TextMateTokenizationWorker>(
readFile: async (_resource: UriComponents): Promise<string> => { 'vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker',
'textMateWorker'
);
TextMateWorkerHost.setChannel(worker, {
$readFile: async (_resource: UriComponents): Promise<string> => {
const resource = URI.revive(_resource); const resource = URI.revive(_resource);
return this._extensionResourceLoaderService.readExtensionResource(resource); return this._extensionResourceLoaderService.readExtensionResource(resource);
}, },
setTokensAndStates: async (controllerId: number, versionId: number, tokens: Uint8Array, lineEndStateDeltas: StateDeltas[]): Promise<void> => { $setTokensAndStates: async (controllerId: number, versionId: number, tokens: Uint8Array, lineEndStateDeltas: StateDeltas[]): Promise<void> => {
const controller = this._workerTokenizerControllers.get(controllerId); const controller = this._workerTokenizerControllers.get(controllerId);
// When a model detaches, it is removed synchronously from the map. // When a model detaches, it is removed synchronously from the map.
// However, the worker might still be sending tokens for that model, // 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); 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); this._reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, isRandomSample);
} }
};
const worker = this._worker = createWebWorker<TextMateTokenizationWorker>(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) { if (this._worker !== worker) {
// disposed in the meantime // disposed in the meantime
return null; return null;
} }
this._workerProxy = proxy; this._workerProxy = worker.proxy;
if (this._currentTheme && this._currentTokenColorMap) { 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 { private _disposeWorker(): void {

Просмотреть файл

@ -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);

Просмотреть файл

@ -6,24 +6,20 @@
import { URI, UriComponents } from 'vs/base/common/uri'; import { URI, UriComponents } from 'vs/base/common/uri';
import { LanguageId } from 'vs/editor/common/encodedTokenAttributes'; import { LanguageId } from 'vs/editor/common/encodedTokenAttributes';
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; 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 { ICreateGrammarResult, TMGrammarFactory } from 'vs/workbench/services/textMate/common/TMGrammarFactory';
import { IValidEmbeddedLanguagesMap, IValidGrammarDefinition, IValidTokenTypeMap } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; import { IValidEmbeddedLanguagesMap, IValidGrammarDefinition, IValidTokenTypeMap } from 'vs/workbench/services/textMate/common/TMScopeRegistry';
import type { IOnigLib, IRawTheme, StackDiff } from 'vscode-textmate'; import type { IOnigLib, IRawTheme, StackDiff } from 'vscode-textmate';
import { TextMateWorkerTokenizer } from './textMateWorkerTokenizer'; import { TextMateWorkerTokenizer } from './textMateWorkerTokenizer';
import { importAMDNodeModule } from 'vs/amdX'; 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`. * Defines the worker entry point. Must be exported and named `create`.
* @skipMangle
*/ */
export function create(ctx: IWorkerContext<ITextMateWorkerHost>, createData: ICreateData): TextMateTokenizationWorker { export function create(workerServer: IWorkerServer): TextMateTokenizationWorker {
return new TextMateTokenizationWorker(ctx, createData); return new TextMateTokenizationWorker(workerServer);
}
export interface ITextMateWorkerHost {
readFile(_resource: UriComponents): Promise<string>;
setTokensAndStates(controllerId: number, versionId: number, tokens: Uint8Array, lineEndStateDeltas: StateDeltas[]): Promise<void>;
reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void;
} }
export interface ICreateData { export interface ICreateData {
@ -49,17 +45,19 @@ export interface StateDeltas {
stateDeltas: (StackDiff | null)[]; stateDeltas: (StackDiff | null)[];
} }
export class TextMateTokenizationWorker { export class TextMateTokenizationWorker implements IRequestHandler {
private readonly _host: ITextMateWorkerHost; _requestHandlerBrand: any;
private readonly _host: TextMateWorkerHost;
private readonly _models = new Map</* controllerId */ number, TextMateWorkerTokenizer>(); private readonly _models = new Map</* controllerId */ number, TextMateWorkerTokenizer>();
private readonly _grammarCache: Promise<ICreateGrammarResult>[] = []; private readonly _grammarCache: Promise<ICreateGrammarResult>[] = [];
private readonly _grammarFactory: Promise<TMGrammarFactory | null>; private _grammarFactory: Promise<TMGrammarFactory | null> = Promise.resolve(null);
constructor( constructor(workerServer: IWorkerServer) {
ctx: IWorkerContext<ITextMateWorkerHost>, this._host = TextMateWorkerHost.getChannel(workerServer);
private readonly _createData: ICreateData }
) {
this._host = ctx.host; public async $init(_createData: ICreateData): Promise<void> {
const grammarDefinitions = _createData.grammarDefinitions.map<IValidGrammarDefinition>((def) => { const grammarDefinitions = _createData.grammarDefinitions.map<IValidGrammarDefinition>((def) => {
return { return {
location: URI.revive(def.location), location: URI.revive(def.location),
@ -73,13 +71,13 @@ export class TextMateTokenizationWorker {
sourceExtensionId: def.sourceExtensionId, sourceExtensionId: def.sourceExtensionId,
}; };
}); });
this._grammarFactory = this._loadTMGrammarFactory(grammarDefinitions); this._grammarFactory = this._loadTMGrammarFactory(grammarDefinitions, _createData.onigurumaWASMUri);
} }
private async _loadTMGrammarFactory(grammarDefinitions: IValidGrammarDefinition[]): Promise<TMGrammarFactory> { private async _loadTMGrammarFactory(grammarDefinitions: IValidGrammarDefinition[], onigurumaWASMUri: string): Promise<TMGrammarFactory> {
const vscodeTextmate = await importAMDNodeModule<typeof import('vscode-textmate')>('vscode-textmate', 'release/main.js'); const vscodeTextmate = await importAMDNodeModule<typeof import('vscode-textmate')>('vscode-textmate', 'release/main.js');
const vscodeOniguruma = await importAMDNodeModule<typeof import('vscode-oniguruma')>('vscode-oniguruma', 'release/main.js'); const vscodeOniguruma = await importAMDNodeModule<typeof import('vscode-oniguruma')>('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'. // 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. // Otherwise, a TypeError is thrown when using the streaming compiler.
@ -95,13 +93,13 @@ export class TextMateTokenizationWorker {
return new TMGrammarFactory({ return new TMGrammarFactory({
logTrace: (msg: string) => {/* console.log(msg) */ }, logTrace: (msg: string) => {/* console.log(msg) */ },
logError: (msg: string, err: any) => console.error(msg, err), 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); }, grammarDefinitions, vscodeTextmate, onigLib);
} }
// These methods are called by the renderer // These methods are called by the renderer
public acceptNewModel(data: IRawModelData): void { public $acceptNewModel(data: IRawModelData): void {
const uri = URI.revive(data.uri); const uri = URI.revive(data.uri);
const that = this; const that = this;
this._models.set(data.controllerId, new TextMateWorkerTokenizer(uri, data.lines, data.EOL, data.versionId, { 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]; return that._grammarCache[encodedLanguageId];
}, },
setTokensAndStates(versionId: number, tokens: Uint8Array, stateDeltas: StateDeltas[]): void { 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 { 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)); }, 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); 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); 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); this._models.get(controllerId)!.onLanguageId(newLanguageId, newEncodedLanguageId);
} }
public acceptRemovedModel(controllerId: number): void { public $acceptRemovedModel(controllerId: number): void {
const model = this._models.get(controllerId); const model = this._models.get(controllerId);
if (model) { if (model) {
model.dispose(); model.dispose();
@ -144,12 +142,12 @@ export class TextMateTokenizationWorker {
} }
} }
public async acceptTheme(theme: IRawTheme, colorMap: string[]): Promise<void> { public async $acceptTheme(theme: IRawTheme, colorMap: string[]): Promise<void> {
const grammarFactory = await this._grammarFactory; const grammarFactory = await this._grammarFactory;
grammarFactory?.setTheme(theme, colorMap); grammarFactory?.setTheme(theme, colorMap);
} }
public acceptMaxTokenizationLineLength(controllerId: number, value: number): void { public $acceptMaxTokenizationLineLength(controllerId: number, value: number): void {
this._models.get(controllerId)!.acceptMaxTokenizationLineLength(value); this._models.get(controllerId)!.acceptMaxTokenizationLineLength(value);
} }
} }

Просмотреть файл

@ -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>(TextMateWorkerHost.CHANNEL_NAME);
}
public static setChannel(workerClient: IWorkerClient<any>, obj: TextMateWorkerHost): void {
workerClient.setChannel<TextMateWorkerHost>(TextMateWorkerHost.CHANNEL_NAME, obj);
}
abstract $readFile(_resource: UriComponents): Promise<string>;
abstract $setTokensAndStates(controllerId: number, versionId: number, tokens: Uint8Array, lineEndStateDeltas: StateDeltas[]): Promise<void>;
abstract $reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void;
}

3
test/monaco/dist/core.html поставляемый
Просмотреть файл

@ -5,9 +5,6 @@
</head> </head>
<body> <body>
<div id="container" style="width:800px;height:600px;border:1px solid #ccc"></div> <div id="container" style="width:800px;height:600px;border:1px solid #ccc"></div>
<script>
globalThis._VSCODE_FILE_ROOT = 'http://127.0.0.1:8563/dist/';
</script>
<script src="./core.bundle.js"></script> <script src="./core.bundle.js"></script>
</body> </body>
</html> </html>

Просмотреть файл

@ -7,6 +7,7 @@ import * as playwright from '@playwright/test';
import { assert } from 'chai'; import { assert } from 'chai';
const PORT = 8563; const PORT = 8563;
const TIMEOUT = 20 * 1000;
const APP = `http://127.0.0.1:${PORT}/dist/core.html`; 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'; const browserType: BrowserType = process.env.BROWSER as BrowserType || 'chromium';
before(async function () { before(async function () {
this.timeout(20 * 1000); this.timeout(TIMEOUT);
console.log(`Starting browser: ${browserType}`); console.log(`Starting browser: ${browserType}`);
browser = await playwright[browserType].launch({ browser = await playwright[browserType].launch({
headless: process.argv.includes('--headless'), headless: process.argv.includes('--headless'),
@ -26,13 +27,13 @@ before(async function () {
}); });
after(async function () { after(async function () {
this.timeout(20 * 1000); this.timeout(TIMEOUT);
await browser.close(); await browser.close();
}); });
const pageErrors: any[] = []; const pageErrors: any[] = [];
beforeEach(async function () { beforeEach(async function () {
this.timeout(20 * 1000); this.timeout(TIMEOUT);
page = await browser.newPage({ page = await browser.newPage({
viewport: { viewport: {
width: 800, width: 800,
@ -59,7 +60,7 @@ afterEach(async () => {
}); });
describe('API Integration Tests', function (): void { describe('API Integration Tests', function (): void {
this.timeout(20000); this.timeout(TIMEOUT);
beforeEach(async () => { beforeEach(async () => {
await page.goto(APP); await page.goto(APP);