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:
Родитель
5a8d73cebb
Коммит
4e0de3a8f4
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче