mirror of
https://github.com/coder/code-server.git
synced 2026-05-08 21:37:27 +02:00
Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IRemoteConsoleLog } from 'vs/base/common/console';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
|
||||
export const IExtensionHostDebugService = createDecorator<IExtensionHostDebugService>('extensionHostDebugService');
|
||||
|
||||
export interface IAttachSessionEvent {
|
||||
sessionId: string;
|
||||
subId?: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export interface ILogToSessionEvent {
|
||||
sessionId: string;
|
||||
log: IRemoteConsoleLog;
|
||||
}
|
||||
|
||||
export interface ITerminateSessionEvent {
|
||||
sessionId: string;
|
||||
subId?: string;
|
||||
}
|
||||
|
||||
export interface IReloadSessionEvent {
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
export interface ICloseSessionEvent {
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
export interface IOpenExtensionWindowResult {
|
||||
rendererDebugPort?: number;
|
||||
}
|
||||
|
||||
export interface IExtensionHostDebugService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
reload(sessionId: string): void;
|
||||
readonly onReload: Event<IReloadSessionEvent>;
|
||||
|
||||
close(sessionId: string): void;
|
||||
readonly onClose: Event<ICloseSessionEvent>;
|
||||
|
||||
attachSession(sessionId: string, port: number, subId?: string): void;
|
||||
readonly onAttachSession: Event<IAttachSessionEvent>;
|
||||
|
||||
logToSession(sessionId: string, log: IRemoteConsoleLog): void;
|
||||
readonly onLogToSession: Event<ILogToSessionEvent>;
|
||||
|
||||
terminateSession(sessionId: string, subId?: string): void;
|
||||
readonly onTerminateSession: Event<ITerminateSessionEvent>;
|
||||
|
||||
openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment, debugRenderer: boolean): Promise<IOpenExtensionWindowResult>;
|
||||
}
|
||||
107
lib/vscode/src/vs/platform/debug/common/extensionHostDebugIpc.ts
Normal file
107
lib/vscode/src/vs/platform/debug/common/extensionHostDebugIpc.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IReloadSessionEvent, ICloseSessionEvent, IAttachSessionEvent, ILogToSessionEvent, ITerminateSessionEvent, IExtensionHostDebugService, IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IRemoteConsoleLog } from 'vs/base/common/console';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
|
||||
export class ExtensionHostDebugBroadcastChannel<TContext> implements IServerChannel<TContext> {
|
||||
|
||||
static readonly ChannelName = 'extensionhostdebugservice';
|
||||
|
||||
private readonly _onCloseEmitter = new Emitter<ICloseSessionEvent>();
|
||||
private readonly _onReloadEmitter = new Emitter<IReloadSessionEvent>();
|
||||
private readonly _onTerminateEmitter = new Emitter<ITerminateSessionEvent>();
|
||||
private readonly _onLogToEmitter = new Emitter<ILogToSessionEvent>();
|
||||
private readonly _onAttachEmitter = new Emitter<IAttachSessionEvent>();
|
||||
|
||||
call(ctx: TContext, command: string, arg?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'close':
|
||||
return Promise.resolve(this._onCloseEmitter.fire({ sessionId: arg[0] }));
|
||||
case 'reload':
|
||||
return Promise.resolve(this._onReloadEmitter.fire({ sessionId: arg[0] }));
|
||||
case 'terminate':
|
||||
return Promise.resolve(this._onTerminateEmitter.fire({ sessionId: arg[0] }));
|
||||
case 'log':
|
||||
return Promise.resolve(this._onLogToEmitter.fire({ sessionId: arg[0], log: arg[1] }));
|
||||
case 'attach':
|
||||
return Promise.resolve(this._onAttachEmitter.fire({ sessionId: arg[0], port: arg[1], subId: arg[2] }));
|
||||
}
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
listen(ctx: TContext, event: string, arg?: any): Event<any> {
|
||||
switch (event) {
|
||||
case 'close':
|
||||
return this._onCloseEmitter.event;
|
||||
case 'reload':
|
||||
return this._onReloadEmitter.event;
|
||||
case 'terminate':
|
||||
return this._onTerminateEmitter.event;
|
||||
case 'log':
|
||||
return this._onLogToEmitter.event;
|
||||
case 'attach':
|
||||
return this._onAttachEmitter.event;
|
||||
}
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionHostDebugChannelClient extends Disposable implements IExtensionHostDebugService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(private channel: IChannel) {
|
||||
super();
|
||||
}
|
||||
|
||||
reload(sessionId: string): void {
|
||||
this.channel.call('reload', [sessionId]);
|
||||
}
|
||||
|
||||
get onReload(): Event<IReloadSessionEvent> {
|
||||
return this.channel.listen('reload');
|
||||
}
|
||||
|
||||
close(sessionId: string): void {
|
||||
this.channel.call('close', [sessionId]);
|
||||
}
|
||||
|
||||
get onClose(): Event<ICloseSessionEvent> {
|
||||
return this.channel.listen('close');
|
||||
}
|
||||
|
||||
attachSession(sessionId: string, port: number, subId?: string): void {
|
||||
this.channel.call('attach', [sessionId, port, subId]);
|
||||
}
|
||||
|
||||
get onAttachSession(): Event<IAttachSessionEvent> {
|
||||
return this.channel.listen('attach');
|
||||
}
|
||||
|
||||
logToSession(sessionId: string, log: IRemoteConsoleLog): void {
|
||||
this.channel.call('log', [sessionId, log]);
|
||||
}
|
||||
|
||||
get onLogToSession(): Event<ILogToSessionEvent> {
|
||||
return this.channel.listen('log');
|
||||
}
|
||||
|
||||
terminateSession(sessionId: string, subId?: string): void {
|
||||
this.channel.call('terminate', [sessionId, subId]);
|
||||
}
|
||||
|
||||
get onTerminateSession(): Event<ITerminateSessionEvent> {
|
||||
return this.channel.listen('terminate');
|
||||
}
|
||||
|
||||
openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment, debugRenderer: boolean): Promise<IOpenExtensionWindowResult> {
|
||||
return this.channel.call('openExtensionDevelopmentHostWindow', [args, env, debugRenderer]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { createServer, AddressInfo } from 'net';
|
||||
import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { OpenContext } from 'vs/platform/windows/node/window';
|
||||
|
||||
export class ElectronExtensionHostDebugBroadcastChannel<TContext> extends ExtensionHostDebugBroadcastChannel<TContext> {
|
||||
|
||||
constructor(private windowsMainService: IWindowsMainService) {
|
||||
super();
|
||||
}
|
||||
|
||||
call(ctx: TContext, command: string, arg?: any): Promise<any> {
|
||||
if (command === 'openExtensionDevelopmentHostWindow') {
|
||||
return this.openExtensionDevelopmentHostWindow(arg[0], arg[1], arg[2]);
|
||||
} else {
|
||||
return super.call(ctx, command, arg);
|
||||
}
|
||||
}
|
||||
|
||||
private async openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment, debugRenderer: boolean): Promise<IOpenExtensionWindowResult> {
|
||||
const pargs = parseArgs(args, OPTIONS);
|
||||
pargs.debugRenderer = debugRenderer;
|
||||
|
||||
const extDevPaths = pargs.extensionDevelopmentPath;
|
||||
if (!extDevPaths) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const [codeWindow] = this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, {
|
||||
context: OpenContext.API,
|
||||
cli: pargs,
|
||||
userEnv: Object.keys(env).length > 0 ? env : undefined
|
||||
});
|
||||
|
||||
if (!debugRenderer) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const debug = codeWindow.win.webContents.debugger;
|
||||
|
||||
let listeners = debug.isAttached() ? Infinity : 0;
|
||||
const server = createServer(listener => {
|
||||
if (listeners++ === 0) {
|
||||
debug.attach();
|
||||
}
|
||||
|
||||
let closed = false;
|
||||
const writeMessage = (message: object) => {
|
||||
if (!closed) { // in case sendCommand promises settle after closed
|
||||
listener.write(JSON.stringify(message) + '\0'); // null-delimited, CDP-compatible
|
||||
}
|
||||
};
|
||||
|
||||
const onMessage = (_event: Event, method: string, params: unknown, sessionId?: string) =>
|
||||
writeMessage(({ method, params, sessionId }));
|
||||
|
||||
codeWindow.win.on('close', () => {
|
||||
debug.removeListener('message', onMessage);
|
||||
listener.end();
|
||||
closed = true;
|
||||
});
|
||||
|
||||
debug.addListener('message', onMessage);
|
||||
|
||||
let buf = Buffer.alloc(0);
|
||||
listener.on('data', data => {
|
||||
buf = Buffer.concat([buf, data]);
|
||||
for (let delimiter = buf.indexOf(0); delimiter !== -1; delimiter = buf.indexOf(0)) {
|
||||
let data: { id: number; sessionId: string; params: {} };
|
||||
try {
|
||||
const contents = buf.slice(0, delimiter).toString('utf8');
|
||||
buf = buf.slice(delimiter + 1);
|
||||
data = JSON.parse(contents);
|
||||
} catch (e) {
|
||||
console.error('error reading cdp line', e);
|
||||
}
|
||||
|
||||
// depends on a new API for which electron.d.ts has not been updated:
|
||||
// @ts-ignore
|
||||
debug.sendCommand(data.method, data.params, data.sessionId)
|
||||
.then((result: object) => writeMessage({ id: data.id, sessionId: data.sessionId, result }))
|
||||
.catch((error: Error) => writeMessage({ id: data.id, sessionId: data.sessionId, error: { code: 0, message: error.message } }));
|
||||
}
|
||||
});
|
||||
|
||||
listener.on('error', err => {
|
||||
console.error('error on cdp pipe:', err);
|
||||
});
|
||||
|
||||
listener.on('close', () => {
|
||||
closed = true;
|
||||
if (--listeners === 0) {
|
||||
debug.detach();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>(r => server.listen(0, r));
|
||||
codeWindow.win.on('close', () => server.close());
|
||||
|
||||
return { rendererDebugPort: (server.address() as AddressInfo).port };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user