From 98f2b447136282805478cd5dddb329599c01ae69 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 5 May 2022 09:51:14 -0700 Subject: [PATCH 1/2] Use built-in `DebugAdapterNamedPipeServer` and small clean-ups --- src/debugAdapter.ts | 119 ----------------------------------- src/features/DebugSession.ts | 28 +++------ src/session.ts | 8 +-- 3 files changed, 14 insertions(+), 141 deletions(-) delete mode 100644 src/debugAdapter.ts diff --git a/src/debugAdapter.ts b/src/debugAdapter.ts deleted file mode 100644 index 260d42ddfd..0000000000 --- a/src/debugAdapter.ts +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { connect, Socket } from "net"; -import { DebugAdapter, Event, DebugProtocolMessage, EventEmitter } from "vscode"; -import { Logger } from "./logging"; - -export class NamedPipeDebugAdapter implements DebugAdapter { - private static readonly TWO_CRLF = '\r\n\r\n'; - private static readonly HEADER_LINESEPARATOR = /\r?\n/; // allow for non-RFC 2822 conforming line separators - private static readonly HEADER_FIELDSEPARATOR = /: */; - - private readonly _logger: Logger; - private readonly _namedPipe: string; - - private _rawData = Buffer.allocUnsafe(0); - private _contentLength = -1; - private _isConnected: boolean = false; - private _debugMessageQueue: DebugProtocolMessage[] = []; - - private _debugServiceSocket: Socket; - - // The event that VS Code-proper will listen for. - private _sendMessage: EventEmitter = new EventEmitter(); - onDidSendMessage: Event = this._sendMessage.event; - - constructor(namedPipe: string, logger: Logger) { - this._namedPipe = namedPipe; - this._logger = logger; - } - - public start(): void { - this._debugServiceSocket = connect(this._namedPipe); - - this._debugServiceSocket.on("error", (e) => { - this._logger.writeError("Error on Debug Adapter: " + e); - this.dispose(); - }); - - // Route any output from the socket through to VS Code. - this._debugServiceSocket.on("data", (data: Buffer) => this.handleData(data)); - - // Wait for the connection to complete. - this._debugServiceSocket.on("connect", () => { - while(this._debugMessageQueue.length) { - this.writeMessageToDebugAdapter(this._debugMessageQueue.shift()); - } - - this._isConnected = true; - this._logger.writeVerbose("Connected to socket!"); - }); - - // When the socket closes, end the session. - this._debugServiceSocket.on("close", () => { this.dispose(); }); - this._debugServiceSocket.on("end", () => { this.dispose(); }); - } - - public handleMessage(message: DebugProtocolMessage): void { - if (!this._isConnected) { - this._debugMessageQueue.push(message); - return; - } - - this.writeMessageToDebugAdapter(message); - } - - public dispose() { - this._debugServiceSocket.destroy(); - this._sendMessage.fire({ type: 'event', event: 'terminated' }); - this._sendMessage.dispose(); - } - - private writeMessageToDebugAdapter(message: DebugProtocolMessage): void { - const msg = JSON.stringify(message); - const messageWrapped = `Content-Length: ${Buffer.byteLength(msg, "utf8")}${NamedPipeDebugAdapter.TWO_CRLF}${msg}`; - this._logger.writeDiagnostic(`SENDING TO DEBUG ADAPTER: ${messageWrapped}`); - this._debugServiceSocket.write(messageWrapped, "utf8"); - } - - // Shamelessly stolen from VS Code's implementation with slight modification by using public types and our logger: - // https://github.com/microsoft/vscode/blob/ff1b513fbca1acad4467dfd768997e9e0b9c5735/src/vs/workbench/contrib/debug/node/debugAdapter.ts#L55-L92 - private handleData(data: Buffer): void { - this._rawData = Buffer.concat([this._rawData, data]); - - while (true) { - if (this._contentLength >= 0) { - if (this._rawData.length >= this._contentLength) { - const message = this._rawData.toString('utf8', 0, this._contentLength); - this._rawData = this._rawData.slice(this._contentLength); - this._contentLength = -1; - if (message.length > 0) { - try { - this._logger.writeDiagnostic(`RECEIVED FROM DEBUG ADAPTER: ${message}`); - this._sendMessage.fire(JSON.parse(message) as DebugProtocolMessage); - } catch (e) { - this._logger.writeError("Error firing event in VS Code: ", (e.message || e), message); - } - } - continue; // there may be more complete messages to process - } - } else { - const idx = this._rawData.indexOf(NamedPipeDebugAdapter.TWO_CRLF); - if (idx !== -1) { - const header = this._rawData.toString('utf8', 0, idx); - const lines = header.split(NamedPipeDebugAdapter.HEADER_LINESEPARATOR); - for (const h of lines) { - const kvPair = h.split(NamedPipeDebugAdapter.HEADER_FIELDSEPARATOR); - if (kvPair[0] === 'Content-Length') { - this._contentLength = Number(kvPair[1]); - } - } - this._rawData = this._rawData.slice(idx + NamedPipeDebugAdapter.TWO_CRLF.length); - continue; - } - } - break; - } - } -} diff --git a/src/features/DebugSession.ts b/src/features/DebugSession.ts index ce1951802f..9ebfbc243d 100644 --- a/src/features/DebugSession.ts +++ b/src/features/DebugSession.ts @@ -11,7 +11,6 @@ import { PowerShellProcess} from "../process"; import { SessionManager, SessionStatus } from "../session"; import Settings = require("../settings"); import utils = require("../utils"); -import { NamedPipeDebugAdapter } from "../debugAdapter"; import { Logger } from "../logging"; import { LanguageClientConsumer } from "../languageClientConsumer"; @@ -37,20 +36,16 @@ export class DebugSessionFeature extends LanguageClientConsumer createDebugAdapterDescriptor( session: vscode.DebugSession, - _executable: vscode.DebugAdapterExecutable): vscode.ProviderResult { + _executable: vscode.DebugAdapterExecutable | undefined): vscode.ProviderResult { const sessionDetails = session.configuration.createTemporaryIntegratedConsole ? this.tempSessionDetails : this.sessionManager.getSessionDetails(); - // Establish connection before setting up the session this.logger.writeVerbose(`Connecting to pipe: ${sessionDetails.debugServicePipeName}`); this.logger.writeVerbose(`Debug configuration: ${JSON.stringify(session.configuration)}`); - const debugAdapter = new NamedPipeDebugAdapter(sessionDetails.debugServicePipeName, this.logger); - debugAdapter.start(); - - return new vscode.DebugAdapterInlineImplementation(debugAdapter); + return new vscode.DebugAdapterNamedPipeServer(sessionDetails.debugServicePipeName); } // tslint:disable-next-line:no-empty @@ -60,19 +55,16 @@ export class DebugSessionFeature extends LanguageClientConsumer public setLanguageClient(languageClient: LanguageClient) { languageClient.onNotification( StartDebuggerNotificationType, - () => - // TODO: Use a named debug configuration. - vscode.debug.startDebugging(undefined, { - request: "launch", - type: "PowerShell", - name: "PowerShell: Interactive Session", - })); + // TODO: Use a named debug configuration. + () => vscode.debug.startDebugging(undefined, { + request: "launch", + type: "PowerShell", + name: "PowerShell: Interactive Session" + })); languageClient.onNotification( StopDebuggerNotificationType, - () => - vscode.debug.stopDebugging(undefined) - ); + () => vscode.debug.stopDebugging(undefined)); } public async provideDebugConfigurations( @@ -374,7 +366,7 @@ export class SpecifyScriptArgsFeature implements vscode.Disposable { const text = await vscode.window.showInputBox(options); // When user cancel's the input box (by pressing Esc), the text value is undefined. - // Let's not blow away the previous settting. + // Let's not blow away the previous setting. if (text !== undefined) { this.context.workspaceState.update(powerShellDbgScriptArgsKey, text); } diff --git a/src/session.ts b/src/session.ts index 3a89464d17..5e93f13eb9 100644 --- a/src/session.ts +++ b/src/session.ts @@ -34,9 +34,6 @@ export enum SessionStatus { Failed, } -export const SendKeyPressNotificationType = - new NotificationType("powerShell/sendKeyPress"); - export class SessionManager implements Middleware { public HostName: string; public HostVersion: string; @@ -797,7 +794,7 @@ export class SessionManager implements Middleware { new SessionMenuItem( "Restart Current Session", () => { - // We pass in the display name so we guarentee that the session + // We pass in the display name so we guarantee that the session // will be the same PowerShell. this.restartSession(this.PowerShellExeDetails.displayName); }), @@ -828,6 +825,9 @@ class SessionMenuItem implements vscode.QuickPickItem { } } +export const SendKeyPressNotificationType = + new NotificationType("powerShell/sendKeyPress"); + export const PowerShellVersionRequestType = new RequestType0( "powerShell/getVersion"); From f2d8cf1c8fb4eb36dc2aa1d6e48a948569236420 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 5 May 2022 11:02:33 -0700 Subject: [PATCH 2/2] Handle `sendKeyPress` events for temporary integrated consoles --- src/session.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/session.ts b/src/session.ts index 5e93f13eb9..93dc28c173 100644 --- a/src/session.ts +++ b/src/session.ts @@ -270,6 +270,20 @@ export class SessionManager implements Middleware { sessionPath, sessionSettings); + // Similar to the regular integrated console, we need to send a key + // press to the process spawned for temporary integrated consoles when + // the server requests a cancellation os Console.ReadKey. + // + // TODO: There may be multiple sessions running in parallel, so we need + // to track a process per session, but that already isn't being done. + vscode.debug.onDidReceiveDebugSessionCustomEvent( + e => { + if (e.event === "powerShell/sendKeyPress") { + this.debugSessionProcess.sendKeyPress(); + } + } + ); + return this.debugSessionProcess; }