code-server/packages/vscode/src/client.ts
Asher 4a80bcb42c
Make everything use active evals (#30)
* Add trace log level

* Use active eval to implement spdlog

* Split server/client active eval interfaces

Since all properties are *not* valid on both sides

* +200% fire resistance

* Implement exec using active evaluations

* Fully implement child process streams

* Watch impl, move child_process back to explicitly adding events

Automatically forwarding all events might be the right move, but wanna
think/discuss it a bit more because it didn't come out very cleanly.

* Would you like some args with that callback?

* Implement the rest of child_process using active evals

* Rampant memory leaks

Emit "kill" to active evaluations when client disconnects in order to
kill processes. Most likely won't be the final solution.

* Resolve some minor issues with output panel

* Implement node-pty with active evals

* Provide clearTimeout to vm sandbox

* Implement socket with active evals

* Extract some callback logic

Also remove some eval interfaces, need to re-think those.

* Implement net.Server and remainder of net.Socket using active evals

* Implement dispose for active evaluations

* Use trace for express requests

* Handle sending buffers through evaluation events

* Make event logging a bit more clear

* Fix some errors due to us not actually instantiating until connect/listen

* is this a commit message?

* We can just create the evaluator in the ctor

Not sure what I was thinking.

* memory leak for you, memory leak for everyone

* it's a ternary now

* Don't dispose automatically on close or error

The code may or may not be disposable at that point.

* Handle parsing buffers on the client side as well

* Remove unused protobuf

* Remove TypedValue

* Remove unused forkProvider and test

* Improve dispose pattern for active evals

* Socket calls close after error; no need to bind both

* Improve comment

* Comment is no longer wishy washy due to explicit boolean

* Simplify check for sendHandle and options

* Replace _require with __non_webpack_require__

Webpack will then replace this with `require` which we then provide to
the vm sandbox.

* Provide path.parse

* Prevent original-fs from loading

* Start with a pid of -1

vscode immediately checks the PID to see if the debug process launch
correctly, but of course we don't get the pid synchronously.

* Pass arguments to bootstrap-fork

* Fully implement streams

Was causing errors because internally the stream would set this.writing
to true and it would never become false, so subsequent messages would
never send.

* Fix serializing errors and streams emitting errors multiple times

* Was emitting close to data

* Fix missing path for spawned processes

* Move evaluation onDispose call

Now it's accurate and runs when the active evaluation has actually
disposed.

* Fix promisifying fs.exists

* Fix some active eval callback issues

* Patch existsSync in debug adapter
2019-02-19 10:17:03 -06:00

190 lines
6.4 KiB
TypeScript

import * as paths from "./fill/paths";
import "./fill/platform";
import "./fill/storageDatabase";
import "./fill/windowsService";
import "./fill/environmentService";
import "./fill/vscodeTextmate";
import "./fill/codeEditor";
import "./fill/mouseEvent";
import "./fill/menuRegistry";
import "./fill/workbenchRegistry";
import { PasteAction } from "./fill/paste";
import "./fill/dom";
import "./vscode.scss";
import { IdeClient, IProgress, INotificationHandle } from "@coder/ide";
import { registerContextMenuListener } from "vs/base/parts/contextmenu/electron-main/contextmenu";
import { LogLevel } from "vs/platform/log/common/log";
import { URI } from "vs/base/common/uri";
import { INotificationService } from "vs/platform/notification/common/notification";
import { IProgressService2, ProgressLocation } from "vs/platform/progress/common/progress";
import { ExplorerItem, Model } from "vs/workbench/parts/files/common/explorerModel";
import { DragMouseEvent } from "vs/base/browser/mouseEvent";
import { IEditorService, IResourceEditor } from "vs/workbench/services/editor/common/editorService";
import { IEditorGroup } from "vs/workbench/services/group/common/editorGroupsService";
import { IWindowsService } from "vs/platform/windows/common/windows";
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
import { RawContextKey, IContextKeyService } from "vs/platform/contextkey/common/contextkey";
export class Client extends IdeClient {
private readonly windowId = parseInt(new Date().toISOString().replace(/[-:.TZ]/g, ""), 10);
private _serviceCollection: ServiceCollection | undefined;
private _clipboardContextKey: RawContextKey<boolean> | undefined;
private _builtInExtensionsDirectory: string | undefined;
public get builtInExtensionsDirectory(): string {
if (!this._builtInExtensionsDirectory) {
throw new Error("trying to access builtin extensions directory before it has been set");
}
return this._builtInExtensionsDirectory;
}
public async handleExternalDrop(target: ExplorerItem | Model, originalEvent: DragMouseEvent): Promise<void> {
await this.upload.uploadDropped(
originalEvent.browserEvent as DragEvent,
(target instanceof ExplorerItem ? target : target.roots[0]).resource,
);
}
public handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup, afterDrop: (targetGroup: IEditorGroup) => void, targetIndex?: number): void {
this.initData.then((d) => {
this.upload.uploadDropped(event, URI.file(d.workingDirectory)).then((paths) => {
const uris = paths.map((p) => URI.file(p));
if (uris.length) {
(this.serviceCollection.get(IWindowsService) as IWindowsService).addRecentlyOpened(uris);
}
const editors: IResourceEditor[] = uris.map(uri => ({
resource: uri,
options: {
pinned: true,
index: targetIndex,
},
}));
const targetGroup = resolveTargetGroup();
(this.serviceCollection.get(IEditorService) as IEditorService).openEditors(editors, targetGroup).then(() => {
afterDrop(targetGroup);
});
});
});
}
/**
* Use to toggle the paste option inside editors based on the native clipboard.
*/
public get clipboardContextKey(): RawContextKey<boolean> {
if (!this._clipboardContextKey) {
throw new Error("Trying to access clipboard context key before it has been set");
}
return this._clipboardContextKey;
}
public get clipboardText(): Promise<string> {
return this.clipboard.readText();
}
/**
* Create a paste action for use in text inputs.
*/
public get pasteAction(): PasteAction {
return new PasteAction();
}
public get serviceCollection(): ServiceCollection {
if (!this._serviceCollection) {
throw new Error("Trying to access service collection before it has been set");
}
return this._serviceCollection;
}
public set serviceCollection(collection: ServiceCollection) {
this._serviceCollection = collection;
this.progressService = {
start: <T>(title: string, task: (progress: IProgress) => Promise<T>, onCancel: () => void): Promise<T> => {
let lastProgress = 0;
return (this.serviceCollection.get(IProgressService2) as IProgressService2).withProgress({
location: ProgressLocation.Notification,
title,
cancellable: true,
}, (progress) => {
return task({
report: (p): void => {
progress.report({ increment: p - lastProgress });
lastProgress = p;
},
});
}, () => {
onCancel();
});
},
};
this.notificationService = {
error: (error: Error): void => (this.serviceCollection.get(INotificationService) as INotificationService).error(error),
prompt: (severity, message, buttons, onCancel): INotificationHandle => {
const handle = (this.serviceCollection.get(INotificationService) as INotificationService).prompt(
severity, message, buttons, { onCancel },
);
return {
close: (): void => handle.close(),
updateMessage: (message): void => handle.updateMessage(message),
updateButtons: (buttons): void => handle.updateActions({
primary: buttons.map((button) => ({
id: "",
label: button.label,
tooltip: "",
class: undefined,
enabled: true,
checked: false,
radio: false,
dispose: (): void => undefined,
run: (): Promise<void> => Promise.resolve(button.run()),
})),
}),
};
},
};
}
protected initialize(): Promise<void> {
registerContextMenuListener();
this._clipboardContextKey = new RawContextKey("nativeClipboard", this.clipboard.isEnabled);
return this.task("Start workbench", 1000, async (data, sharedData) => {
paths._paths.initialize(data, sharedData);
this._builtInExtensionsDirectory = data.builtInExtensionsDirectory;
process.env.SHELL = data.shell;
const { startup } = require("./startup");
await startup({
machineId: "1",
windowId: this.windowId,
logLevel: LogLevel.Info,
mainPid: 1,
appRoot: data.dataDirectory,
execPath: data.tmpDirectory,
userEnv: {},
nodeCachedDataDir: data.tmpDirectory,
perfEntries: [],
_: [],
folderUri: URI.file(data.workingDirectory),
});
const contextKeys = this.serviceCollection.get(IContextKeyService) as IContextKeyService;
const bounded = this.clipboardContextKey.bindTo(contextKeys);
this.clipboard.onPermissionChange((enabled) => {
bounded.set(enabled);
});
this.clipboard.initialize();
}, this.initData, this.sharedProcessData);
}
}
export const client = new Client();