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):
#include(vs/editor/standalone/common/standaloneTheme): BuiltinTheme, IStandaloneThemeData, IColors
#include(vs/editor/common/languages/supports/tokenization): ITokenThemeRule
#include(vs/editor/browser/services/webWorker): MonacoWebWorker, IWebWorkerOptions
#include(vs/editor/standalone/browser/standaloneWebWorker): MonacoWebWorker, IWebWorkerOptions
#include(vs/editor/standalone/browser/standaloneCodeEditor): IActionDescriptor, IGlobalEditorOptions, IStandaloneEditorConstructionOptions, IStandaloneDiffEditorConstructionOptions, IStandaloneCodeEditor, IStandaloneDiffEditor
export interface ICommandHandler {
(...args: any[]): void;

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

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

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

@ -7,7 +7,7 @@ import { createTrustedTypesPolicy } from 'vs/base/browser/trustedTypes';
import { onUnexpectedError } from 'vs/base/common/errors';
import { AppResourcePath, COI, FileAccess } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { IWorker, IWorkerCallback, IWorkerFactory, logOnceWebWorkerWarning } from 'vs/base/common/worker/simpleWorker';
import { IWorker, IWorkerCallback, IWorkerClient, IWorkerDescriptor, IWorkerFactory, logOnceWebWorkerWarning, SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { coalesce } from 'vs/base/common/arrays';
@ -35,7 +35,7 @@ export function createBlobWorker(blobUrl: string, options?: WorkerOptions): Work
return new Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) as unknown as string : blobUrl, { ...options, type: isESM ? 'module' : undefined });
}
function getWorker(workerMainLocation: URI | undefined, label: string): Worker | Promise<Worker> {
function getWorker(esmWorkerLocation: URI | undefined, label: string): Worker | Promise<Worker> {
// Option for hosts to overwrite the worker script (used in the standalone editor)
interface IMonacoEnvironment {
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 });
}
// ESM-comment-end
if (workerMainLocation) {
const workerUrl = getWorkerBootstrapUrl(label, workerMainLocation.toString(true));
if (esmWorkerLocation) {
const workerUrl = getWorkerBootstrapUrl(label, esmWorkerLocation.toString(true));
const worker = new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label, type: isESM ? 'module' : undefined });
if (isESM) {
return whenESMWorkerReady(worker);
@ -136,17 +136,17 @@ class WebWorker extends Disposable implements IWorker {
private readonly label: string;
private worker: Promise<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();
this.id = id;
this.label = label;
const workerOrPromise = getWorker(workerMainLocation, label);
const workerOrPromise = getWorker(esmWorkerLocation, label);
if (isPromiseLike(workerOrPromise)) {
this.worker = workerOrPromise;
} else {
this.worker = Promise.resolve(workerOrPromise);
}
this.postMessage(moduleId, []);
this.postMessage(amdModuleId, []);
this.worker.then((w) => {
w.onmessage = function (ev) {
onMessageCallback(ev.data);
@ -183,36 +183,45 @@ class WebWorker extends Disposable implements IWorker {
}
}
export class DefaultWorkerFactory implements IWorkerFactory {
export class WorkerDescriptor implements IWorkerDescriptor {
public readonly esmModuleLocation: URI | undefined;
constructor(
public readonly amdModuleId: string,
readonly label: string | undefined,
) {
this.esmModuleLocation = (isESM ? FileAccess.asBrowserUri(`${amdModuleId}.esm.js` as AppResourcePath) : undefined);
}
}
class DefaultWorkerFactory implements IWorkerFactory {
private static LAST_WORKER_ID = 0;
private _label: string | undefined;
private _webWorkerFailedBeforeError: any;
constructor(private readonly workerMainLocation: URI | undefined, label: string | undefined) {
this._label = label;
constructor() {
this._webWorkerFailedBeforeError = false;
}
public create(modules: { moduleId: string; esmModuleId: string }, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker {
public create(desc: IWorkerDescriptor, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker {
const workerId = (++DefaultWorkerFactory.LAST_WORKER_ID);
if (this._webWorkerFailedBeforeError) {
throw this._webWorkerFailedBeforeError;
}
let workerMainLocation = this.workerMainLocation;
const moduleId = modules.moduleId;
if (isESM) {
workerMainLocation = FileAccess.asBrowserUri(`${modules.esmModuleId}.esm.js` as AppResourcePath);
}
return new WebWorker(workerMainLocation, moduleId, workerId, this._label || 'anonymous' + workerId, onMessageCallback, (err) => {
return new WebWorker(desc.esmModuleLocation, desc.amdModuleId, workerId, desc.label || 'anonymous' + workerId, onMessageCallback, (err) => {
logOnceWebWorkerWarning(err);
this._webWorkerFailedBeforeError = err;
onErrorCallback(err);
});
}
}
export function createWebWorker<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.
*--------------------------------------------------------------------------------------------*/
import { transformErrorForSerialization } from 'vs/base/common/errors';
import { CharCode } from 'vs/base/common/charCode';
import { onUnexpectedError, transformErrorForSerialization } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { AppResourcePath, FileAccess } from 'vs/base/common/network';
import { getAllMethodNames } from 'vs/base/common/objects';
import { isWeb } from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
// ESM-comment-begin
const isESM = false;
@ -18,6 +19,7 @@ const isESM = false;
// const isESM = true;
// ESM-uncomment-end
const DEFAULT_CHANNEL = 'default';
const INITIALIZE = '$initialize';
export interface IWorker extends IDisposable {
@ -30,7 +32,13 @@ export interface IWorkerCallback {
}
export interface IWorkerFactory {
create(modules: { moduleId: string; esmModuleId: string }, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker;
create(modules: IWorkerDescriptor, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker;
}
export interface IWorkerDescriptor {
readonly amdModuleId: string;
readonly esmModuleLocation: URI | undefined;
readonly label: string | undefined;
}
let webWorkerWarningLogged = false;
@ -58,6 +66,7 @@ class RequestMessage {
constructor(
public readonly vsWorker: number,
public readonly req: string,
public readonly channel: string,
public readonly method: string,
public readonly args: any[]
) { }
@ -76,6 +85,7 @@ class SubscribeEventMessage {
constructor(
public readonly vsWorker: number,
public readonly req: string,
public readonly channel: string,
public readonly eventName: string,
public readonly arg: any
) { }
@ -104,8 +114,8 @@ interface IMessageReply {
interface IMessageHandler {
sendMessage(msg: any, transfer?: ArrayBuffer[]): void;
handleMessage(method: string, args: any[]): Promise<any>;
handleEvent(eventName: string, arg: any): Event<any>;
handleMessage(channel: string, method: string, args: any[]): Promise<any>;
handleEvent(channel: string, eventName: string, arg: any): Event<any>;
}
class SimpleWorkerProtocol {
@ -130,24 +140,24 @@ class SimpleWorkerProtocol {
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);
return new Promise<any>((resolve, reject) => {
this._pendingReplies[req] = {
resolve: resolve,
reject: reject
};
this._send(new RequestMessage(this._workerId, req, method, args));
this._send(new RequestMessage(this._workerId, req, channel, method, args));
});
}
public listen(eventName: string, arg: any): Event<any> {
public listen(channel: string, eventName: string, arg: any): Event<any> {
let req: string | null = null;
const emitter = new Emitter<any>({
onWillAddFirstListener: () => {
req = String(++this._lastSentReq);
this._pendingEmitters.set(req, emitter);
this._send(new SubscribeEventMessage(this._workerId, req, eventName, arg));
this._send(new SubscribeEventMessage(this._workerId, req, channel, eventName, arg));
},
onDidRemoveLastListener: () => {
this._pendingEmitters.delete(req!);
@ -168,6 +178,29 @@ class SimpleWorkerProtocol {
this._handleMessage(message);
}
public createProxyToRemoteChannel<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 {
switch (msg.type) {
case MessageType.Reply:
@ -209,7 +242,7 @@ class SimpleWorkerProtocol {
private _handleRequestMessage(requestMessage: RequestMessage): void {
const req = requestMessage.req;
const result = this._handler.handleMessage(requestMessage.method, requestMessage.args);
const result = this._handler.handleMessage(requestMessage.channel, requestMessage.method, requestMessage.args);
result.then((r) => {
this._send(new ReplyMessage(this._workerId, req, r, undefined));
}, (e) => {
@ -223,7 +256,7 @@ class SimpleWorkerProtocol {
private _handleSubscribeEventMessage(msg: SubscribeEventMessage): void {
const req = msg.req;
const disposable = this._handler.handleEvent(msg.eventName, msg.arg)((event) => {
const disposable = this._handler.handleEvent(msg.channel, msg.eventName, msg.arg)((event) => {
this._send(new EventMessage(this._workerId, req, event));
});
this._pendingEvents.set(req, disposable);
@ -263,35 +296,60 @@ class SimpleWorkerProtocol {
}
}
type ProxiedMethodName = (`$${string}` | `on${string}`);
export type Proxied<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> {
getProxyObject(): Promise<W>;
proxy: Proxied<W>;
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
*/
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 _onModuleLoaded: Promise<string[]>;
private readonly _onModuleLoaded: Promise<void>;
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();
let lazyProxyReject: ((err: any) => void) | null = null;
this._worker = this._register(workerFactory.create(
{ moduleId: 'vs/base/common/worker/simpleWorker', esmModuleId: moduleId },
{
amdModuleId: 'vs/base/common/worker/simpleWorker',
esmModuleLocation: workerDescriptor.esmModuleLocation,
label: workerDescriptor.label
},
(msg: Message) => {
this._protocol.handleMessage(msg);
},
(err: any) => {
// in Firefox, web workers fail lazily :(
// we will reject the proxy
lazyProxyReject?.(err);
onUnexpectedError(err);
}
));
@ -299,33 +357,11 @@ export class SimpleWorkerClient<W extends object, H extends object> extends Disp
sendMessage: (msg: any, transfer: ArrayBuffer[]): void => {
this._worker.postMessage(msg, transfer);
},
handleMessage: (method: string, args: any[]): Promise<any> => {
if (typeof (host as any)[method] !== 'function') {
return Promise.reject(new Error('Missing method ' + method + ' on main thread host.'));
}
try {
return Promise.resolve((host as any)[method].apply(host, args));
} catch (e) {
return Promise.reject(e);
}
handleMessage: (channel: string, method: string, args: any[]): Promise<any> => {
return this._handleMessage(channel, method, args);
},
handleEvent: (eventName: string, arg: any): Event<any> => {
if (propertyIsDynamicEvent(eventName)) {
const event = (host as any)[eventName].call(host, arg);
if (typeof event !== 'function') {
throw new Error(`Missing dynamic event ${eventName} on main thread host.`);
}
return event;
}
if (propertyIsEvent(eventName)) {
const event = (host as any)[eventName];
if (typeof event !== 'function') {
throw new Error(`Missing event ${eventName} on main thread host.`);
}
return event;
}
throw new Error(`Malformed event name ${eventName}`);
handleEvent: (channel: string, eventName: string, arg: any): Event<any> => {
return this._handleEvent(channel, eventName, arg);
}
});
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;
}
const hostMethods = getAllMethodNames(host);
// Send initialize message
this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [
this._onModuleLoaded = this._protocol.sendMessage(DEFAULT_CHANNEL, INITIALIZE, [
this._worker.getId(),
JSON.parse(JSON.stringify(loaderConfiguration)),
moduleId,
hostMethods,
workerDescriptor.amdModuleId,
]);
// Create proxy to loaded code
const proxyMethodRequest = (method: string, args: any[]): Promise<any> => {
return this._request(method, args);
};
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);
});
this.proxy = this._protocol.createProxyToRemoteChannel(DEFAULT_CHANNEL, async () => { await this._onModuleLoaded; });
this._onModuleLoaded.catch((e) => {
this._onError('Worker failed to load ' + workerDescriptor.amdModuleId, e);
});
}
public getProxyObject(): Promise<W> {
return this._lazyProxy;
private _handleMessage(channelName: string, method: string, args: any[]): Promise<any> {
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> {
return new Promise<any>((resolve, reject) => {
this._onModuleLoaded.then(() => {
this._protocol.sendMessage(method, args).then(resolve, reject);
}, reject);
});
private _handleEvent(channelName: string, eventName: string, arg: any): Event<any> {
const channel: object | undefined = this._localChannels.get(channelName);
if (!channel) {
throw new Error(`Missing channel ${channelName} on main thread`);
}
if (propertyIsDynamicEvent(eventName)) {
const event = (channel as any)[eventName].call(channel, arg);
if (typeof event !== 'function') {
throw new Error(`Missing dynamic event ${eventName} on main thread channel ${channelName}.`);
}
return event;
}
if (propertyIsEvent(eventName)) {
const event = (channel as any)[eventName];
if (typeof event !== 'function') {
throw new Error(`Missing event ${eventName} on main thread channel ${channelName}.`);
}
return event;
}
throw new Error(`Malformed event name ${eventName}`);
}
public setChannel<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 {
@ -399,65 +457,35 @@ function propertyIsDynamicEvent(name: string): boolean {
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 {
_requestHandlerBrand: any;
[prop: string]: any;
}
export interface IRequestHandlerFactory<H> {
(host: H): IRequestHandler;
export interface IRequestHandlerFactory {
(workerServer: IWorkerServer): IRequestHandler;
}
/**
* 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 _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._requestHandler = null;
this._protocol = new SimpleWorkerProtocol({
sendMessage: (msg: any, transfer: ArrayBuffer[]): void => {
postMessage(msg, transfer);
},
handleMessage: (method: string, args: any[]): Promise<any> => this._handleMessage(method, args),
handleEvent: (eventName: string, arg: any): Event<any> => this._handleEvent(eventName, arg)
handleMessage: (channel: string, method: string, args: any[]): Promise<any> => this._handleMessage(channel, method, args),
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);
}
private _handleMessage(method: string, args: any[]): Promise<any> {
if (method === INITIALIZE) {
return this.initialize(<number>args[0], <any>args[1], <string>args[2], <string[]>args[3]);
private _handleMessage(channel: string, method: string, args: any[]): Promise<any> {
if (channel === DEFAULT_CHANNEL && method === INITIALIZE) {
return this.initialize(<number>args[0], <any>args[1], <string>args[2]);
}
if (!this._requestHandler || typeof this._requestHandler[method] !== 'function') {
return Promise.reject(new Error('Missing requestHandler or method: ' + method));
const requestHandler: object | null | undefined = (channel === DEFAULT_CHANNEL ? this._requestHandler : this._localChannels.get(channel));
if (!requestHandler) {
return Promise.reject(new Error(`Missing channel ${channel} on worker thread`));
}
if (typeof (requestHandler as any)[method] !== 'function') {
return Promise.reject(new Error(`Missing method ${method} on worker thread channel ${channel}`));
}
try {
return Promise.resolve(this._requestHandler[method].apply(this._requestHandler, args));
return Promise.resolve((requestHandler as any)[method].apply(requestHandler, args));
} catch (e) {
return Promise.reject(e);
}
}
private _handleEvent(eventName: string, arg: any): Event<any> {
if (!this._requestHandler) {
throw new Error(`Missing requestHandler`);
private _handleEvent(channel: string, eventName: string, arg: any): Event<any> {
const requestHandler: object | null | undefined = (channel === DEFAULT_CHANNEL ? this._requestHandler : this._localChannels.get(channel));
if (!requestHandler) {
throw new Error(`Missing channel ${channel} on worker thread`);
}
if (propertyIsDynamicEvent(eventName)) {
const event = (this._requestHandler as any)[eventName].call(this._requestHandler, arg);
const event = (requestHandler as any)[eventName].call(requestHandler, arg);
if (typeof event !== 'function') {
throw new Error(`Missing dynamic event ${eventName} on request handler.`);
}
return event;
}
if (propertyIsEvent(eventName)) {
const event = (this._requestHandler as any)[eventName];
const event = (requestHandler as any)[eventName];
if (typeof event !== 'function') {
throw new Error(`Missing event ${eventName} on request handler.`);
}
@ -502,22 +535,25 @@ export class SimpleWorkerServer<H extends object> {
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);
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) {
// static request handler
this._requestHandler = this._requestHandlerFactory(hostProxy);
return Promise.resolve(getAllMethodNames(this._requestHandler));
this._requestHandler = this._requestHandlerFactory(this);
return;
}
if (loaderConfig) {
@ -542,18 +578,16 @@ export class SimpleWorkerServer<H extends object> {
if (isESM) {
const url = FileAccess.asBrowserUri(`${moduleId}.js` as AppResourcePath).toString(true);
return import(`${url}`).then((module: { create: IRequestHandlerFactory<H> }) => {
this._requestHandler = module.create(hostProxy);
return import(`${url}`).then((module: { create: IRequestHandlerFactory }) => {
this._requestHandler = module.create(this);
if (!this._requestHandler) {
throw new Error(`No RequestHandler!`);
}
return getAllMethodNames(this._requestHandler);
});
}
return new Promise<string[]>((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
// Use the global require to be sure to get the global config
// ESM-comment-begin
@ -563,24 +597,24 @@ export class SimpleWorkerServer<H extends object> {
// const req = globalThis.require;
// ESM-uncomment-end
req([moduleId], (module: { create: IRequestHandlerFactory<H> }) => {
this._requestHandler = module.create(hostProxy);
req([moduleId], (module: { create: IRequestHandlerFactory }) => {
this._requestHandler = module.create(this);
if (!this._requestHandler) {
reject(new Error(`No RequestHandler!`));
return;
}
resolve(getAllMethodNames(this._requestHandler));
resolve();
}, reject);
});
}
}
/**
* Called on the worker side
* Defines the worker entry point. Must be exported and named `create`.
* @skipMangle
*/
export function create(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void): SimpleWorkerServer<any> {
export function create(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void): SimpleWorkerServer {
return new SimpleWorkerServer(postMessage, null);
}

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

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

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

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

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

@ -6,18 +6,17 @@
import { stringDiff } from 'vs/base/common/diff/diff';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IRequestHandler } from 'vs/base/common/worker/simpleWorker';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model';
import { IMirrorTextModel, IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel';
import { ensureValidWordDefinition, getWordAtText, IWordAtPosition } from 'vs/editor/common/core/wordHelper';
import { IMirrorTextModel, IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
import { IColorInformation, IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/languages';
import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/languages/linkComputer';
import { computeLinks } from 'vs/editor/common/languages/linkComputer';
import { BasicInplaceReplace } from 'vs/editor/common/languages/supports/inplaceReplaceSupport';
import { DiffAlgorithmName, IDiffComputationResult, ILineChange, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker';
import { createMonacoBaseAPI } from 'vs/editor/common/services/editorBaseApi';
import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost';
import { EditorWorkerHost } from './editorWorkerHost';
import { StopWatch } from 'vs/base/common/stopwatch';
import { UnicodeTextModelHighlighter, UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter';
import { DiffComputer, IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer';
@ -28,8 +27,10 @@ import { createProxyObject, getAllMethodNames } from 'vs/base/common/objects';
import { IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider';
import { AppResourcePath, FileAccess } from 'vs/base/common/network';
import { BugIndicatingError } from 'vs/base/common/errors';
import { IDocumentColorComputerTarget, computeDefaultDocumentColors } from 'vs/editor/common/languages/defaultDocumentColorsComputer';
import { computeDefaultDocumentColors } from 'vs/editor/common/languages/defaultDocumentColorsComputer';
import { FindSectionHeaderOptions, SectionHeader, findSectionHeaders } from 'vs/editor/common/services/findSectionHeaders';
import { IRawModelData, IWorkerTextModelSyncChannelServer } from './textModelSync/textModelSync.protocol';
import { ICommonModel, WorkerTextModelSyncServer } from 'vs/editor/common/services/textModelSync/textModelSync.impl';
// ESM-comment-begin
const isESM = false;
@ -55,43 +56,11 @@ export interface IWorkerContext<H = undefined> {
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.
* @internal
*/
interface IWordRange {
export interface IWordRange {
/**
* The index where the word starts.
*/
@ -102,246 +71,6 @@ interface IWordRange {
readonly end: number;
}
/**
* @internal
*/
class MirrorModel extends BaseMirrorModel implements ICommonModel {
public get uri(): URI {
return this._uri;
}
public get eol(): string {
return this._eol;
}
public getValue(): string {
return this.getText();
}
public findMatches(regex: RegExp): RegExpMatchArray[] {
const matches = [];
for (let i = 0; i < this._lines.length; i++) {
const line = this._lines[i];
const offsetToAdd = this.offsetAt(new Position(i + 1, 1));
const iteratorOverMatches = line.matchAll(regex);
for (const match of iteratorOverMatches) {
if (match.index || match.index === 0) {
match.index = match.index + offsetToAdd;
}
matches.push(match);
}
}
return matches;
}
public getLinesContent(): string[] {
return this._lines.slice(0);
}
public getLineCount(): number {
return this._lines.length;
}
public getLineContent(lineNumber: number): string {
return this._lines[lineNumber - 1];
}
public getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range | null {
const wordAtText = getWordAtText(
position.column,
ensureValidWordDefinition(wordDefinition),
this._lines[position.lineNumber - 1],
0
);
if (wordAtText) {
return new Range(position.lineNumber, wordAtText.startColumn, position.lineNumber, wordAtText.endColumn);
}
return null;
}
public getWordUntilPosition(position: IPosition, wordDefinition: RegExp): IWordAtPosition {
const wordAtPosition = this.getWordAtPosition(position, wordDefinition);
if (!wordAtPosition) {
return {
word: '',
startColumn: position.column,
endColumn: position.column
};
}
return {
word: this._lines[position.lineNumber - 1].substring(wordAtPosition.startColumn - 1, position.column - 1),
startColumn: wordAtPosition.startColumn,
endColumn: position.column
};
}
public words(wordDefinition: RegExp): Iterable<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
*/
@ -354,55 +83,38 @@ declare const require: any;
/**
* @internal
*/
export class EditorSimpleWorker implements IRequestHandler, IDisposable {
export class BaseEditorSimpleWorker implements IDisposable, IWorkerTextModelSyncChannelServer, IRequestHandler {
_requestHandlerBrand: any;
protected readonly _host: IEditorWorkerHost;
private _models: { [uri: string]: MirrorModel };
private readonly _foreignModuleFactory: IForeignModuleFactory | null;
private _foreignModule: any;
private readonly _workerTextModelSyncServer = new WorkerTextModelSyncServer();
constructor(host: IEditorWorkerHost, foreignModuleFactory: IForeignModuleFactory | null) {
this._host = host;
this._models = Object.create(null);
this._foreignModuleFactory = foreignModuleFactory;
this._foreignModule = null;
constructor() {
}
public dispose(): void {
this._models = Object.create(null);
dispose(): void {
}
protected _getModel(uri: string): ICommonModel {
return this._models[uri];
protected _getModel(uri: string): ICommonModel | undefined {
return this._workerTextModelSyncServer.getModel(uri);
}
private _getModels(): ICommonModel[] {
const all: MirrorModel[] = [];
Object.keys(this._models).forEach((key) => all.push(this._models[key]));
return all;
protected _getModels(): ICommonModel[] {
return this._workerTextModelSyncServer.getModels();
}
public acceptNewModel(data: IRawModelData): void {
this._models[data.url] = new MirrorModel(URI.parse(data.url), data.lines, data.EOL, data.versionId);
public $acceptNewModel(data: IRawModelData): void {
this._workerTextModelSyncServer.$acceptNewModel(data);
}
public acceptModelChanged(strURL: string, e: IModelChangedEvent): void {
if (!this._models[strURL]) {
return;
}
const model = this._models[strURL];
model.onEvents(e);
public $acceptModelChanged(uri: string, e: IModelChangedEvent): void {
this._workerTextModelSyncServer.$acceptModelChanged(uri, e);
}
public acceptRemovedModel(strURL: string): void {
if (!this._models[strURL]) {
return;
}
delete this._models[strURL];
public $acceptRemovedModel(uri: string): void {
this._workerTextModelSyncServer.$acceptRemovedModel(uri);
}
public async computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult> {
public async $computeUnicodeHighlights(url: string, options: UnicodeHighlighterOptions, range?: IRange): Promise<IUnicodeHighlightsResult> {
const model = this._getModel(url);
if (!model) {
return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 };
@ -410,7 +122,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
return UnicodeTextModelHighlighter.computeUnicodeHighlights(model, options, range);
}
public async findSectionHeaders(url: string, options: FindSectionHeaderOptions): Promise<SectionHeader[]> {
public async $findSectionHeaders(url: string, options: FindSectionHeaderOptions): Promise<SectionHeader[]> {
const model = this._getModel(url);
if (!model) {
return [];
@ -420,7 +132,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
// ---- BEGIN diff --------------------------------------------------------------------------
public async computeDiff(originalUrl: string, modifiedUrl: string, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise<IDiffComputationResult | null> {
public async $computeDiff(originalUrl: string, modifiedUrl: string, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise<IDiffComputationResult | null> {
const original = this._getModel(originalUrl);
const modified = this._getModel(modifiedUrl);
if (!original || !modified) {
@ -484,7 +196,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
return true;
}
public async computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise<IChange[] | null> {
public async $computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise<IChange[] | null> {
const original = this._getModel(originalUrl);
const modified = this._getModel(modifiedUrl);
if (!original || !modified) {
@ -510,7 +222,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
private static readonly _diffLimit = 100000;
public async computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): Promise<TextEdit[]> {
public async $computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): Promise<TextEdit[]> {
const model = this._getModel(modelUrl);
if (!model) {
return edits;
@ -592,7 +304,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
return result;
}
public computeHumanReadableDiff(modelUrl: string, edits: TextEdit[], options: ILinesDiffComputerOptions): TextEdit[] {
public $computeHumanReadableDiff(modelUrl: string, edits: TextEdit[], options: ILinesDiffComputerOptions): TextEdit[] {
const model = this._getModel(modelUrl);
if (!model) {
return edits;
@ -692,7 +404,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
// ---- END minimal edits ---------------------------------------------------------------
public async computeLinks(modelUrl: string): Promise<ILink[] | null> {
public async $computeLinks(modelUrl: string): Promise<ILink[] | null> {
const model = this._getModel(modelUrl);
if (!model) {
return null;
@ -703,7 +415,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
// --- BEGIN default document colors -----------------------------------------------------------
public async computeDefaultDocumentColors(modelUrl: string): Promise<IColorInformation[] | null> {
public async $computeDefaultDocumentColors(modelUrl: string): Promise<IColorInformation[] | null> {
const model = this._getModel(modelUrl);
if (!model) {
return null;
@ -715,7 +427,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
private static readonly _suggestionsLimit = 10000;
public async textualSuggest(modelUrls: string[], leadingWord: string | undefined, wordDef: string, wordDefFlags: string): Promise<{ words: string[]; duration: number } | null> {
public async $textualSuggest(modelUrls: string[], leadingWord: string | undefined, wordDef: string, wordDefFlags: string): Promise<{ words: string[]; duration: number } | null> {
const sw = new StopWatch();
const wordDefRegExp = new RegExp(wordDef, wordDefFlags);
@ -746,7 +458,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
//#region -- word ranges --
public async computeWordRanges(modelUrl: string, range: IRange, wordDef: string, wordDefFlags: string): Promise<{ [word: string]: IRange[] }> {
public async $computeWordRanges(modelUrl: string, range: IRange, wordDef: string, wordDefFlags: string): Promise<{ [word: string]: IRange[] }> {
const model = this._getModel(modelUrl);
if (!model) {
return Object.create(null);
@ -777,7 +489,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
//#endregion
public async navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): Promise<IInplaceReplaceSupportResult | null> {
public async $navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): Promise<IInplaceReplaceSupportResult | null> {
const model = this._getModel(modelUrl);
if (!model) {
return null;
@ -804,12 +516,31 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
const result = BasicInplaceReplace.INSTANCE.navigateValueSet(range, selectionText, wordRange, word, up);
return result;
}
}
/**
* @internal
*/
export class EditorSimpleWorker extends BaseEditorSimpleWorker {
private _foreignModule: any = null;
constructor(
private readonly _host: EditorWorkerHost,
private readonly _foreignModuleFactory: IForeignModuleFactory | null
) {
super();
}
public async $ping() {
return 'pong';
}
// ---- BEGIN foreign module support --------------------------------------------------------------------------
public loadForeignModule(moduleId: string, createData: any, foreignHostMethods: string[]): Promise<string[]> {
public $loadForeignModule(moduleId: string, createData: any, foreignHostMethods: string[]): Promise<string[]> {
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);
@ -844,7 +575,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
}
// 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') {
return Promise.reject(new Error('Missing requestHandler or method: ' + method));
}
@ -860,11 +591,12 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
}
/**
* Called on the worker side
* Defines the worker entry point. Must be exported and named `create`.
* @skipMangle
* @internal
*/
export function create(host: IEditorWorkerHost): IRequestHandler {
return new EditorSimpleWorker(host, null);
export function create(workerServer: IWorkerServer): IRequestHandler {
return new EditorSimpleWorker(EditorWorkerHost.getChannel(workerServer), null);
}
// This is only available in a Web Worker

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

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

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

@ -3,9 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SimpleWorkerServer } from 'vs/base/common/worker/simpleWorker';
import { IWorkerServer, SimpleWorkerServer } from 'vs/base/common/worker/simpleWorker';
import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker';
import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost';
import { EditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost';
type MessageEvent = {
data: any;
@ -26,7 +26,7 @@ export function initialize(factory: any) {
const simpleWorker = new SimpleWorkerServer((msg) => {
globalThis.postMessage(msg);
}, (host: IEditorWorkerHost) => new EditorSimpleWorker(host, null));
}, (workerServer: IWorkerServer) => new EditorSimpleWorker(EditorWorkerHost.getChannel(workerServer), null));
globalThis.onmessage = (e: MessageEvent) => {
simpleWorker.onmessage(e.data);

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

@ -3,7 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface IEditorWorkerHost {
import { IWorkerServer, IWorkerClient } from 'vs/base/common/worker/simpleWorker';
export abstract class EditorWorkerHost {
public static CHANNEL_NAME = 'editorWorkerHost';
public static getChannel(workerServer: IWorkerServer): EditorWorkerHost {
return workerServer.getChannel<EditorWorkerHost>(EditorWorkerHost.CHANNEL_NAME);
}
public static setChannel(workerClient: IWorkerClient<any>, obj: EditorWorkerHost): void {
workerClient.setChannel<EditorWorkerHost>(EditorWorkerHost.CHANNEL_NAME, obj);
}
// 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 { ITextModel } from 'vs/editor/common/model';
import { DocumentColorProvider, IColor, IColorInformation, IColorPresentation } from 'vs/editor/common/languages';
import { EditorWorkerClient } from 'vs/editor/browser/services/editorWorkerService';
import { IModelService } from 'vs/editor/common/services/model';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { registerEditorFeature } from 'vs/editor/common/editorFeatures';
import { FileAccess } from 'vs/base/common/network';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
export class DefaultDocumentColorProvider implements DocumentColorProvider {
private _editorWorkerClient: EditorWorkerClient;
constructor(
modelService: IModelService,
languageConfigurationService: ILanguageConfigurationService,
) {
this._editorWorkerClient = new EditorWorkerClient(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, false, 'editorWorkerService', languageConfigurationService);
}
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
) { }
async provideDocumentColors(model: ITextModel, _token: CancellationToken): Promise<IColorInformation[] | null> {
return this._editorWorkerClient.computeDefaultDocumentColors(model.uri);
return this._editorWorkerService.computeDefaultDocumentColors(model.uri);
}
provideColorPresentations(_model: ITextModel, colorInfo: IColorInformation, _token: CancellationToken): IColorPresentation[] {
@ -50,12 +42,11 @@ export class DefaultDocumentColorProvider implements DocumentColorProvider {
class DefaultDocumentColorProviderFeature extends Disposable {
constructor(
@IModelService _modelService: IModelService,
@ILanguageConfigurationService _languageConfigurationService: ILanguageConfigurationService,
@ILanguageFeaturesService _languageFeaturesService: ILanguageFeaturesService,
@IEditorWorkerService editorWorkerService: IEditorWorkerService,
) {
super();
this._register(_languageFeaturesService.colorProvider.register('*', new DefaultDocumentColorProvider(_modelService, _languageConfigurationService)));
this._register(_languageFeaturesService.colorProvider.register('*', new DefaultDocumentColorProvider(editorWorkerService)));
}
}

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

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

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

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

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

@ -12,7 +12,7 @@ import { FontMeasurements } from 'vs/editor/browser/config/fontMeasurements';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IWebWorkerOptions, MonacoWebWorker, createWebWorker as actualCreateWebWorker } from 'vs/editor/browser/services/webWorker';
import { IWebWorkerOptions, MonacoWebWorker, createWebWorker as actualCreateWebWorker } from 'vs/editor/standalone/browser/standaloneWebWorker';
import { ApplyUpdateResult, ConfigurationChangedEvent, EditorOptions } from 'vs/editor/common/config/editorOptions';
import { EditorZoom } from 'vs/editor/common/config/editorZoom';
import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo';
@ -21,7 +21,6 @@ import { IRange } from 'vs/editor/common/core/range';
import { EditorType, IDiffEditor } from 'vs/editor/common/editorCommon';
import * as languages from 'vs/editor/common/languages';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
import { NullState, nullTokenize } from 'vs/editor/common/languages/nullTokenize';
import { FindMatch, ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model';
@ -333,7 +332,7 @@ export function onDidChangeModelLanguage(listener: (e: { readonly model: ITextMo
* Specify an AMD module to load that will `create` an object that will be proxied.
*/
export function createWebWorker<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 { ITreeSitterParserService } from 'vs/editor/common/services/treeSitterParserService';
import { StandaloneTreeSitterParserService } from 'vs/editor/standalone/browser/standaloneTreeSitterService';
import { IWorkerDescriptor } from 'vs/base/common/worker/simpleWorker';
class SimpleModel implements IResolvedTextEditorModel {
@ -1075,6 +1076,12 @@ class StandaloneContextMenuService extends ContextMenuService {
}
}
export const standaloneEditorWorkerDescriptor: IWorkerDescriptor = {
amdModuleId: 'vs/editor/common/services/editorSimpleWorker',
esmModuleLocation: undefined,
label: 'editorWorkerService'
};
class StandaloneEditorWorkerService extends EditorWorkerService {
constructor(
@IModelService modelService: IModelService,
@ -1083,7 +1090,7 @@ class StandaloneEditorWorkerService extends EditorWorkerService {
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService,
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
) {
super(undefined, modelService, configurationService, logService, languageConfigurationService, languageFeaturesService);
super(standaloneEditorWorkerDescriptor, modelService, configurationService, logService, languageConfigurationService, languageFeaturesService);
}
}

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

@ -3,26 +3,19 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { FileAccess } from 'vs/base/common/network';
import { getAllMethodNames } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { IWorkerDescriptor } from 'vs/base/common/worker/simpleWorker';
import { EditorWorkerClient } from 'vs/editor/browser/services/editorWorkerService';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { IModelService } from 'vs/editor/common/services/model';
import { standaloneEditorWorkerDescriptor } from 'vs/editor/standalone/browser/standaloneServices';
/**
* Create a new web worker that has model syncing capabilities built in.
* Specify an AMD module to load that will `create` an object that will be proxied.
*/
export function createWebWorker<T extends object>(modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions): MonacoWebWorker<T> {
return new MonacoWebWorkerImpl<T>(undefined, modelService, languageConfigurationService, 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);
export function createWebWorker<T extends object>(modelService: IModelService, opts: IWebWorkerOptions): MonacoWebWorker<T> {
return new MonacoWebWorkerImpl<T>(modelService, opts);
}
/**
@ -76,8 +69,13 @@ class MonacoWebWorkerImpl<T extends object> extends EditorWorkerClient implement
private _foreignModuleCreateData: any | null;
private _foreignProxy: Promise<T> | null;
constructor(workerMainLocation: URI | undefined, modelService: IModelService, languageConfigurationService: ILanguageConfigurationService, opts: IWebWorkerOptions) {
super(workerMainLocation, modelService, opts.keepIdleModels || false, opts.label, languageConfigurationService);
constructor(modelService: IModelService, opts: IWebWorkerOptions) {
const workerDescriptor: IWorkerDescriptor = {
amdModuleId: standaloneEditorWorkerDescriptor.amdModuleId,
esmModuleLocation: standaloneEditorWorkerDescriptor.esmModuleLocation,
label: opts.label,
};
super(workerDescriptor, opts.keepIdleModels || false, modelService);
this._foreignModuleId = opts.moduleId;
this._foreignModuleCreateData = opts.createData || null;
this._foreignModuleHost = opts.host || null;
@ -101,11 +99,11 @@ class MonacoWebWorkerImpl<T extends object> extends EditorWorkerClient implement
if (!this._foreignProxy) {
this._foreignProxy = this._getProxy().then((proxy) => {
const foreignHostMethods = this._foreignModuleHost ? getAllMethodNames(this._foreignModuleHost) : [];
return proxy.loadForeignModule(this._foreignModuleId, this._foreignModuleCreateData, foreignHostMethods).then((foreignMethods) => {
return proxy.$loadForeignModule(this._foreignModuleId, this._foreignModuleCreateData, foreignHostMethods).then((foreignMethods) => {
this._foreignModuleCreateData = null;
const proxyMethodRequest = (method: string, args: any[]): Promise<any> => {
return proxy.fmr(method, args);
return proxy.$fmr(method, args);
};
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> {
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 { IRange, Range } from 'vs/editor/common/core/range';
import { TextEdit } from 'vs/editor/common/languages';
import { EditorSimpleWorker, ICommonModel } from 'vs/editor/common/services/editorSimpleWorker';
import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost';
import { BaseEditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker';
import { ICommonModel } from 'vs/editor/common/services/textModelSync/textModelSync.impl';
suite('EditorSimpleWorker', () => {
ensureNoDisposablesAreLeakedInTestSuite();
class WorkerWithModels extends EditorSimpleWorker {
class WorkerWithModels extends BaseEditorSimpleWorker {
getModel(uri: string) {
return this._getModel(uri);
@ -23,13 +23,13 @@ suite('EditorSimpleWorker', () => {
addModel(lines: string[], eol: string = '\n') {
const uri = 'test:file#' + Date.now();
this.acceptNewModel({
this.$acceptNewModel({
url: uri,
versionId: 1,
lines: lines,
EOL: eol
});
return this._getModel(uri);
return this._getModel(uri)!;
}
}
@ -37,7 +37,7 @@ suite('EditorSimpleWorker', () => {
let model: ICommonModel;
setup(() => {
worker = new WorkerWithModels(<IEditorWorkerHost>null!, null);
worker = new WorkerWithModels();
model = worker.addModel([
'This is line one', //16
'and this is line number two', //27
@ -93,7 +93,7 @@ suite('EditorSimpleWorker', () => {
test('MoreMinimal', () => {
return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: 'This is line One', range: new Range(1, 1, 1, 17) }], false).then(edits => {
return worker.$computeMoreMinimalEdits(model.uri.toString(), [{ text: 'This is line One', range: new Range(1, 1, 1, 17) }], false).then(edits => {
assert.strictEqual(edits.length, 1);
const [first] = edits;
assert.strictEqual(first.text, 'O');
@ -112,7 +112,7 @@ suite('EditorSimpleWorker', () => {
], '\n');
const newEdits = await worker.computeMoreMinimalEdits(model.uri.toString(), [
const newEdits = await worker.$computeMoreMinimalEdits(model.uri.toString(), [
{
range: new Range(1, 1, 2, 1),
text: 'one\ntwo\nthree\n',
@ -144,7 +144,7 @@ suite('EditorSimpleWorker', () => {
'}'
], '\n');
return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"a":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => {
return worker.$computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"a":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => {
assert.strictEqual(edits.length, 0);
});
});
@ -157,7 +157,7 @@ suite('EditorSimpleWorker', () => {
'}'
], '\n');
return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"b":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => {
return worker.$computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"b":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => {
assert.strictEqual(edits.length, 1);
const [first] = edits;
assert.strictEqual(first.text, 'b');
@ -173,7 +173,7 @@ suite('EditorSimpleWorker', () => {
'}' // 3
]);
return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '\n', range: new Range(3, 2, 4, 1000) }], false).then(edits => {
return worker.$computeMoreMinimalEdits(model.uri.toString(), [{ text: '\n', range: new Range(3, 2, 4, 1000) }], false).then(edits => {
assert.strictEqual(edits.length, 1);
const [first] = edits;
assert.strictEqual(first.text, '\n');
@ -184,7 +184,7 @@ suite('EditorSimpleWorker', () => {
async function testEdits(lines: string[], edits: TextEdit[]): Promise<unknown> {
const model = worker.addModel(lines);
const smallerEdits = await worker.computeHumanReadableDiff(
const smallerEdits = await worker.$computeHumanReadableDiff(
model.uri.toString(),
edits,
{ ignoreTrimWhitespace: false, maxComputationTimeMs: 0, computeMoves: false }
@ -286,7 +286,7 @@ suite('EditorSimpleWorker', () => {
'f f' // 2
]);
return worker.textualSuggest([model.uri.toString()], 'f', '[a-z]+', 'img').then((result) => {
return worker.$textualSuggest([model.uri.toString()], 'f', '[a-z]+', 'img').then((result) => {
if (!result) {
assert.ok(false);
}

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

@ -6,7 +6,7 @@
import { URI } from 'vs/base/common/uri';
import { IRange } from 'vs/editor/common/core/range';
import { DiffAlgorithmName, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker';
import { TextEdit, IInplaceReplaceSupportResult } from 'vs/editor/common/languages';
import { TextEdit, IInplaceReplaceSupportResult, IColorInformation } from 'vs/editor/common/languages';
import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider';
import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer';
import { SectionHeader } from 'vs/editor/common/services/findSectionHeaders';
@ -27,4 +27,5 @@ export class TestEditorWorkerService implements IEditorWorkerService {
canNavigateValueSet(resource: URI): boolean { return false; }
async navigateValueSet(resource: URI, range: IRange, up: boolean): Promise<IInplaceReplaceSupportResult | null> { return null; }
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 { TernarySearchTree } from 'vs/base/common/ternarySearchTree';
import { URI } from 'vs/base/common/uri';
import { IRequestHandler } from 'vs/base/common/worker/simpleWorker';
import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker';
import { IV8Profile, Utils } from 'vs/platform/profiling/common/profiling';
import { IProfileModel, BottomUpSample, buildModel, BottomUpNode, processNode, CdpCallFrame } from 'vs/platform/profiling/common/profilingModel';
import { BottomUpAnalysis, IProfileAnalysisWorker, ProfilingOutput } from 'vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService';
export function create(): IRequestHandler {
/**
* Defines the worker entry point. Must be exported and named `create`.
* @skipMangle
*/
export function create(workerServer: IWorkerServer): IRequestHandler {
return new ProfileAnalysisWorker();
}
@ -19,7 +23,7 @@ class ProfileAnalysisWorker implements IRequestHandler, IProfileAnalysisWorker {
_requestHandlerBrand: any;
analyseBottomUp(profile: IV8Profile): BottomUpAnalysis {
$analyseBottomUp(profile: IV8Profile): BottomUpAnalysis {
if (!Utils.isValidProfile(profile)) {
return { kind: ProfilingOutput.Irrelevant, samples: [] };
}
@ -37,7 +41,7 @@ class ProfileAnalysisWorker implements IRequestHandler, IProfileAnalysisWorker {
return { kind: ProfilingOutput.Interesting, samples };
}
analyseByUrlCategory(profile: IV8Profile, categories: [url: URI, category: string][]): [category: string, aggregated: number][] {
$analyseByUrlCategory(profile: IV8Profile, categories: [url: URI, category: string][]): [category: string, aggregated: number][] {
// build search tree
const searchTree = TernarySearchTree.forUris<string>();

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

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

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

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { FileAccess } from 'vs/base/common/network';
import { WorkerDescriptor } from 'vs/base/browser/defaultWorkerFactory';
import { EditorWorkerService } from 'vs/editor/browser/services/editorWorkerService';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
@ -19,6 +19,7 @@ export class WorkbenchEditorWorkerService extends EditorWorkerService {
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService,
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
) {
super(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), modelService, configurationService, logService, languageConfigurationService, languageFeaturesService);
const workerDescriptor = new WorkerDescriptor('vs/editor/common/services/editorSimpleWorker', 'editorWorkerService');
super(workerDescriptor, modelService, configurationService, logService, languageConfigurationService, languageFeaturesService);
}
}

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

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

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

@ -5,15 +5,13 @@
import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker';
import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory';
import { IWorkerClient, Proxied } from 'vs/base/common/worker/simpleWorker';
import { createWebWorker } from 'vs/base/browser/defaultWorkerFactory';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { IMainCellDto, INotebookDiffResult, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { NotebookEditorSimpleWorker } from 'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker';
import { INotebookWorkerHost } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerHost';
import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService';
import { FileAccess } from 'vs/base/common/network';
export class NotebookEditorWorkerServiceImpl extends Disposable implements INotebookEditorWorkerService {
declare readonly _serviceBrand: undefined;
@ -59,23 +57,18 @@ class WorkerManager extends Disposable {
withWorker(): Promise<NotebookWorkerClient> {
// this._lastWorkerUsedTime = (new Date()).getTime();
if (!this._editorWorkerClient) {
this._editorWorkerClient = new NotebookWorkerClient(this._notebookService, 'notebookEditorWorkerService');
this._editorWorkerClient = new NotebookWorkerClient(this._notebookService);
}
return Promise.resolve(this._editorWorkerClient);
}
}
interface IWorkerClient<W> {
getProxyObject(): Promise<W>;
dispose(): void;
}
class NotebookEditorModelManager extends Disposable {
private _syncedModels: { [modelUrl: string]: IDisposable } = Object.create(null);
private _syncedModelsLastUsedTime: { [modelUrl: string]: number } = Object.create(null);
constructor(
private readonly _proxy: NotebookEditorSimpleWorker,
private readonly _proxy: Proxied<NotebookEditorSimpleWorker>,
private readonly _notebookService: INotebookService
) {
super();
@ -102,7 +95,7 @@ class NotebookEditorModelManager extends Disposable {
const modelUrl = resource.toString();
this._proxy.acceptNewModel(
this._proxy.$acceptNewModel(
model.uri.toString(),
{
cells: model.cells.map(cell => ({
@ -162,7 +155,7 @@ class NotebookEditorModelManager extends Disposable {
return data;
});
this._proxy.acceptModelChanged(modelUrl.toString(), {
this._proxy.$acceptModelChanged(modelUrl.toString(), {
rawEvents: dto,
versionId: event.versionId
});
@ -172,7 +165,7 @@ class NotebookEditorModelManager extends Disposable {
this._stopModelSync(modelUrl);
}));
toDispose.add(toDisposable(() => {
this._proxy.acceptRemovedModel(modelUrl);
this._proxy.$acceptRemovedModel(modelUrl);
}));
this._syncedModels[modelUrl] = toDispose;
@ -186,72 +179,47 @@ class NotebookEditorModelManager extends Disposable {
}
}
class NotebookWorkerHost implements INotebookWorkerHost {
private readonly _workerClient: NotebookWorkerClient;
constructor(workerClient: NotebookWorkerClient) {
this._workerClient = workerClient;
}
// foreign host request
public fhr(method: string, args: any[]): Promise<any> {
return this._workerClient.fhr(method, args);
}
}
class NotebookWorkerClient extends Disposable {
private _worker: IWorkerClient<NotebookEditorSimpleWorker> | null;
private readonly _workerFactory: DefaultWorkerFactory;
private _modelManager: NotebookEditorModelManager | null;
constructor(private readonly _notebookService: INotebookService, label: string) {
constructor(private readonly _notebookService: INotebookService) {
super();
this._workerFactory = new DefaultWorkerFactory(FileAccess.asBrowserUri('vs/base/worker/workerMain.js'), label);
this._worker = null;
this._modelManager = null;
}
// foreign host request
public fhr(method: string, args: any[]): Promise<any> {
throw new Error(`Not implemented!`);
}
computeDiff(original: URI, modified: URI) {
return this._withSyncedResources([original, modified]).then(proxy => {
return proxy.computeDiff(original.toString(), modified.toString());
});
const proxy = this._ensureSyncedResources([original, modified]);
return proxy.$computeDiff(original.toString(), modified.toString());
}
canPromptRecommendation(modelUri: URI) {
return this._withSyncedResources([modelUri]).then(proxy => {
return proxy.canPromptRecommendation(modelUri.toString());
});
const proxy = this._ensureSyncedResources([modelUri]);
return proxy.$canPromptRecommendation(modelUri.toString());
}
private _getOrCreateModelManager(proxy: NotebookEditorSimpleWorker): NotebookEditorModelManager {
private _getOrCreateModelManager(proxy: Proxied<NotebookEditorSimpleWorker>): NotebookEditorModelManager {
if (!this._modelManager) {
this._modelManager = this._register(new NotebookEditorModelManager(proxy, this._notebookService));
}
return this._modelManager;
}
protected _withSyncedResources(resources: URI[]): Promise<NotebookEditorSimpleWorker> {
return this._getProxy().then((proxy) => {
this._getOrCreateModelManager(proxy).ensureSyncedResources(resources);
return proxy;
});
protected _ensureSyncedResources(resources: URI[]): Proxied<NotebookEditorSimpleWorker> {
const proxy = this._getOrCreateWorker().proxy;
this._getOrCreateModelManager(proxy).ensureSyncedResources(resources);
return proxy;
}
private _getOrCreateWorker(): IWorkerClient<NotebookEditorSimpleWorker> {
if (!this._worker) {
try {
this._worker = this._register(new SimpleWorkerClient<NotebookEditorSimpleWorker, NotebookWorkerHost>(
this._workerFactory,
this._worker = this._register(createWebWorker<NotebookEditorSimpleWorker>(
'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker',
new NotebookWorkerHost(this)
'notebookEditorWorkerService'
));
} catch (err) {
// logOnceWebWorkerWarning(err);
@ -261,15 +229,4 @@ class NotebookWorkerClient extends Disposable {
}
return this._worker;
}
protected _getProxy(): Promise<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 { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IRequestHandler } from 'vs/base/common/worker/simpleWorker';
import { IRequestHandler, IWorkerServer } from 'vs/base/common/worker/simpleWorker';
import * as model from 'vs/editor/common/model';
import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder';
import { CellKind, ICellDto2, IMainCellDto, INotebookDiffResult, IOutputDto, NotebookCellInternalMetadata, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellTextModelSplice, NotebookData, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { Range } from 'vs/editor/common/core/range';
import { INotebookWorkerHost } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerHost';
import { VSBuffer } from 'vs/base/common/buffer';
import { SearchParams } from 'vs/editor/common/model/textModelSearch';
@ -191,7 +190,7 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable
dispose(): void {
}
public acceptNewModel(uri: string, data: NotebookData): void {
public $acceptNewModel(uri: string, data: NotebookData): void {
this._models[uri] = new MirrorNotebookDocument(URI.parse(uri), data.cells.map(dto => new MirrorCell(
(dto as unknown as IMainCellDto).handle,
dto.source,
@ -202,19 +201,19 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable
)), data.metadata);
}
public acceptModelChanged(strURL: string, event: NotebookCellsChangedEventDto) {
public $acceptModelChanged(strURL: string, event: NotebookCellsChangedEventDto) {
const model = this._models[strURL];
model?.acceptModelChanged(event);
}
public acceptRemovedModel(strURL: string): void {
public $acceptRemovedModel(strURL: string): void {
if (!this._models[strURL]) {
return;
}
delete this._models[strURL];
}
computeDiff(originalUrl: string, modifiedUrl: string): INotebookDiffResult {
$computeDiff(originalUrl: string, modifiedUrl: string): INotebookDiffResult {
const original = this._getModel(originalUrl);
const modified = this._getModel(modifiedUrl);
@ -276,7 +275,7 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable
};
}
canPromptRecommendation(modelUrl: string): boolean {
$canPromptRecommendation(modelUrl: string): boolean {
const model = this._getModel(modelUrl);
const cells = model.cells;
@ -315,9 +314,9 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable
}
/**
* Called on the worker side
* @internal
* Defines the worker entry point. Must be exported and named `create`.
* @skipMangle
*/
export function create(host: INotebookWorkerHost): IRequestHandler {
export function create(workerServer: IWorkerServer): IRequestHandler {
return new NotebookEditorSimpleWorker();
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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>
<body>
<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>
</body>
</html>

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

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