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