Hook up shared process sorta
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import "./fill/require";
|
||||
import "./fill/storageDatabase";
|
||||
import "./fill/windowsService";
|
||||
import * as paths from "./fill/paths";
|
||||
import "./fill/dom";
|
||||
import "./vscode.scss";
|
||||
|
||||
import { fork } from "child_process";
|
||||
import { createConnection } from "net";
|
||||
import { Client as IDEClient, IURI, IURIFactory } from "@coder/ide";
|
||||
import { logger } from "@coder/logger";
|
||||
|
||||
import { registerContextMenuListener } from "vs/base/parts/contextmenu/electron-main/contextmenu";
|
||||
import { LogLevel } from "vs/platform/log/common/log";
|
||||
@@ -12,36 +14,42 @@ import { toLocalISOString } from "vs/base/common/date";
|
||||
// import { RawContextKey, IContextKeyService } from "vs/platform/contextkey/common/contextkey";
|
||||
import { URI } from "vs/base/common/uri";
|
||||
|
||||
import { Protocol, ISharedProcessInitData } from "./protocol";
|
||||
import * as paths from "./fill/paths";
|
||||
import "./firefox";
|
||||
import { Protocol } from "vs/base/parts/ipc/node/ipc.net";
|
||||
|
||||
export class Client extends IDEClient {
|
||||
|
||||
private readonly sharedProcessLogger = logger.named("shr proc");
|
||||
private readonly windowId = parseInt(toLocalISOString(new Date()).replace(/[-:.TZ]/g, ""), 10);
|
||||
private readonly version = "hello"; // TODO: pull from package.json probably
|
||||
private readonly bootstrapForkLocation = "/bootstrap"; // TODO: location.
|
||||
|
||||
public readonly protocolPromise: Promise<Protocol>;
|
||||
private protoResolve: ((protocol: Protocol) => void) | undefined;
|
||||
public protoResolve: ((protocol: Protocol) => void) | undefined;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
process.env.VSCODE_LOGS = "/tmp/vscode-online/logs"; // TODO: use tmpdir or get log directory from init data.
|
||||
this.protocolPromise = new Promise((resolve): void => {
|
||||
this.protoResolve = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
protected initialize(): Promise<void> {
|
||||
this.task("Start shared process", 5, async () => {
|
||||
const protocol = await this.forkSharedProcess();
|
||||
this.protoResolve!(protocol);
|
||||
this.task("Connect to shared process", 5, async () => {
|
||||
await new Promise((resolve, reject): void => {
|
||||
const listener = this.onSharedProcessActive((data) => {
|
||||
listener.dispose();
|
||||
const socket = createConnection(data.socketPath, resolve);
|
||||
socket.once("error", () => {
|
||||
reject();
|
||||
});
|
||||
this.protoResolve!(new Protocol(socket));
|
||||
});
|
||||
});
|
||||
}).catch(() => undefined);
|
||||
|
||||
registerContextMenuListener();
|
||||
|
||||
return this.task("Start workbench", 1000, async (initData) => {
|
||||
paths.paths.appData = initData.dataDirectory;
|
||||
paths.paths.defaultUserData = initData.dataDirectory;
|
||||
|
||||
const { startup } = require("./startup");
|
||||
await startup({
|
||||
machineId: "1",
|
||||
@@ -57,6 +65,33 @@ export class Client extends IDEClient {
|
||||
folderUri: URI.file(initData.dataDirectory),
|
||||
});
|
||||
|
||||
// TODO: Set notification service for retrying.
|
||||
// this.retry.setNotificationService({
|
||||
// prompt: (severity, message, buttons, onCancel) => {
|
||||
// const handle = getNotificationService().prompt(severity, message, buttons, onCancel);
|
||||
// return {
|
||||
// close: () => handle.close(),
|
||||
// updateMessage: (message) => handle.updateMessage(message),
|
||||
// updateButtons: (buttons) => handle.updateActions({
|
||||
// primary: buttons.map((button) => ({
|
||||
// id: undefined,
|
||||
// label: button.label,
|
||||
// tooltip: undefined,
|
||||
// class: undefined,
|
||||
// enabled: true,
|
||||
// checked: false,
|
||||
// radio: false,
|
||||
// dispose: () => undefined,
|
||||
// run: () => {
|
||||
// button.run();
|
||||
// return Promise.resolve();
|
||||
// },
|
||||
// })),
|
||||
// }),
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
|
||||
// TODO: Set up clipboard context.
|
||||
// const workbench = workbenchShell.workbench;
|
||||
// const contextKeys = workbench.workbenchParams.serviceCollection.get(IContextKeyService) as IContextKeyService;
|
||||
@@ -69,50 +104,6 @@ export class Client extends IDEClient {
|
||||
}, this.initData);
|
||||
}
|
||||
|
||||
public async forkSharedProcess(): Promise<Protocol> {
|
||||
const childProcess = fork(this.bootstrapForkLocation, ["--shared"], {
|
||||
env: {
|
||||
"VSCODE_ALLOW_IO": "true",
|
||||
"AMD_ENTRYPOINT": "vs/code/electron-browser/sharedProcess/sharedProcessClient",
|
||||
},
|
||||
});
|
||||
|
||||
childProcess.stderr.on("data", (data) => {
|
||||
this.sharedProcessLogger.error("stderr: " + data);
|
||||
});
|
||||
|
||||
const protocol = Protocol.fromProcess(childProcess);
|
||||
await new Promise((resolve, reject): void => {
|
||||
protocol.onClose(() => {
|
||||
reject(new Error("unable to establish connection to shared process"));
|
||||
});
|
||||
|
||||
const listener = protocol.onMessage((message) => {
|
||||
const messageStr = message.toString();
|
||||
this.sharedProcessLogger.debug(messageStr);
|
||||
switch (messageStr) {
|
||||
case "handshake:hello":
|
||||
protocol.send(Buffer.from(JSON.stringify({
|
||||
// Using the version so if we get a new mount, it spins up a new
|
||||
// shared process.
|
||||
socketPath: `/tmp/vscode-online/shared-${this.version}.sock`,
|
||||
serviceUrl: "", // TODO
|
||||
logsDir: process.env.VSCODE_LOGS,
|
||||
windowId: this.windowId,
|
||||
logLevel: LogLevel.Info,
|
||||
} as ISharedProcessInitData)));
|
||||
break;
|
||||
case "handshake:ready":
|
||||
listener.dispose();
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return protocol;
|
||||
}
|
||||
|
||||
protected createUriFactory(): IURIFactory {
|
||||
return {
|
||||
// TODO: not sure why this is an error.
|
||||
@@ -126,8 +117,3 @@ export class Client extends IDEClient {
|
||||
}
|
||||
|
||||
export const client = new Client();
|
||||
|
||||
client.initData.then((initData) => {
|
||||
paths.appData = initData.dataDirectory;
|
||||
paths.defaultUserData = initData.dataDirectory;
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import "./firefox.scss";
|
||||
|
||||
// Firefox has no implementation of toElement.
|
||||
if (!("toElement" in MouseEvent.prototype)) {
|
||||
Object.defineProperty(MouseEvent.prototype, "toElement", {
|
||||
get: function (): EventTarget | null {
|
||||
@@ -1,4 +1,4 @@
|
||||
const paths = {
|
||||
export const paths = {
|
||||
appData: "/tmp",
|
||||
defaultUserData: "/tmp",
|
||||
};
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import { ChildProcess } from "child_process";
|
||||
import { EventEmitter } from "events";
|
||||
import { Protocol as VSProtocol } from "vs/base/parts/ipc/node/ipc.net";
|
||||
import { LogLevel } from "vs/platform/log/common/log";
|
||||
|
||||
export interface ISharedProcessInitData {
|
||||
socketPath: string;
|
||||
serviceUrl: string;
|
||||
logsDir: string;
|
||||
windowId: number;
|
||||
logLevel: LogLevel;
|
||||
}
|
||||
|
||||
export interface IStdio {
|
||||
onMessage: (cb: (data: string | Buffer) => void) => void;
|
||||
sendMessage: (data: string | Buffer) => void;
|
||||
onExit?: (cb: () => void) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of net.Socket that uses stdio streams.
|
||||
*/
|
||||
class Socket {
|
||||
|
||||
private readonly emitter: EventEmitter;
|
||||
|
||||
public constructor(private readonly stdio: IStdio, ignoreFirst: boolean = false) {
|
||||
this.emitter = new EventEmitter();
|
||||
|
||||
let first = true;
|
||||
stdio.onMessage((data) => {
|
||||
if (ignoreFirst && first) {
|
||||
first = false;
|
||||
|
||||
return;
|
||||
}
|
||||
this.emitter.emit("data", Buffer.from(data.toString()));
|
||||
});
|
||||
if (stdio.onExit) {
|
||||
stdio.onExit(() => {
|
||||
this.emitter.emit("close");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public removeListener(event: string, listener: () => void): void {
|
||||
this.emitter.removeListener(event, listener);
|
||||
}
|
||||
|
||||
public once(event: string, listener: () => void): void {
|
||||
this.emitter.once(event, listener);
|
||||
}
|
||||
|
||||
public on(event: string, listener: () => void): void {
|
||||
this.emitter.on(event, listener);
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
// TODO: figure it out
|
||||
}
|
||||
|
||||
public get destroyed(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public write(data: string | Buffer): void {
|
||||
this.stdio.sendMessage(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A protocol around a process, stream, or worker.
|
||||
*/
|
||||
export class Protocol extends VSProtocol {
|
||||
|
||||
public static fromProcess(childProcess: ChildProcess): Protocol {
|
||||
return Protocol.fromStdio({
|
||||
onMessage: (cb): void => {
|
||||
childProcess.stdout.on("data", (data: string | Buffer) => {
|
||||
cb(data);
|
||||
});
|
||||
},
|
||||
sendMessage: (data): void => {
|
||||
childProcess.stdin.write(data);
|
||||
},
|
||||
onExit: (cb): void => {
|
||||
childProcess.on("exit", cb);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public static fromStream(
|
||||
inStream: { on: (event: "data", cb: (b: string | Buffer) => void) => void },
|
||||
outStream: { write: (b: string | Buffer) => void },
|
||||
): Protocol {
|
||||
return Protocol.fromStdio({
|
||||
onMessage: (cb): void => {
|
||||
inStream.on("data", (data) => {
|
||||
cb(data);
|
||||
});
|
||||
},
|
||||
sendMessage: (data): void => {
|
||||
outStream.write(data);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public static fromWorker(worker: {
|
||||
onmessage: (event: MessageEvent) => void;
|
||||
postMessage: (data: string, origin?: string | string[]) => void;
|
||||
}, ignoreFirst: boolean = false): Protocol {
|
||||
return Protocol.fromStdio({
|
||||
onMessage: (cb): void => {
|
||||
worker.onmessage = (event: MessageEvent): void => {
|
||||
cb(event.data);
|
||||
};
|
||||
},
|
||||
sendMessage: (data): void => {
|
||||
worker.postMessage(data.toString());
|
||||
},
|
||||
}, ignoreFirst);
|
||||
}
|
||||
|
||||
public static fromStdio(stdio: IStdio, ignoreFirst?: boolean): Protocol {
|
||||
return new Protocol(new Socket(stdio, ignoreFirst));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
// These use -webkit-margin-before/after which don't work.
|
||||
.monaco-workbench > .part > .title > .title-label h2,
|
||||
.monaco-panel-view .panel > .panel-header h3.title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// Using @supports to keep the Firefox fixes completely separate from vscode's
|
||||
// CSS that is tailored for Chrome.
|
||||
@supports (-moz-appearance:none) {
|
||||
/* Fix buttons getting cut off on notifications. */
|
||||
// Fix buttons getting cut off on notifications.
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-button.monaco-text-button {
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
Reference in New Issue
Block a user