diff --git a/CHANGELOG.md b/CHANGELOG.md index 841e0f9b0..9a44ba8df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ This changelog records changes to stable releases since 1.50.2. "TBA" changes he ## Nightly (only) -Nothing (yet) +- feat: support ETX in stdio console endings ([vscode#175763](https://github.com/microsoft/vscode/issues/175763)) ## v1.77 (March 2023) diff --git a/src/targets/node/subprocessProgramLauncher.ts b/src/targets/node/subprocessProgramLauncher.ts index 0b3e5e837..f2ad5c05a 100644 --- a/src/targets/node/subprocessProgramLauncher.ts +++ b/src/targets/node/subprocessProgramLauncher.ts @@ -4,6 +4,8 @@ import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; import { inject, injectable } from 'inversify'; +import split from 'split2'; +import { Transform } from 'stream'; import { EnvironmentVars } from '../../common/environmentVars'; import { ILogger } from '../../common/logging'; import * as urlUtils from '../../common/urlUtils'; @@ -61,10 +63,14 @@ export class SubprocessProgramLauncher implements IProgramLauncher { * Called for a child process when the stdio should be written over DAP. */ private captureStdio(dap: Dap.Api, child: ChildProcessWithoutNullStreams) { - child.stdout.on('data', data => dap.output({ category: 'stdout', output: data.toString() })); - child.stderr.on('data', data => dap.output({ category: 'stderr', output: data.toString() })); - child.stdout.resume(); - child.stderr.resume(); + child.stdout + .pipe(EtxSplitter.stream()) + .on('data', output => dap.output({ category: 'stdout', output })) + .resume(); + child.stderr + .pipe(EtxSplitter.stream()) + .on('data', output => dap.output({ category: 'stderr', output })) + .resume(); } /** @@ -150,3 +156,33 @@ const formatArguments = (executable: string, args: ReadonlyArray, cwd: s return { executable, args, shell: false, cwd }; }; + +const enum Char { + ETX = '\u0003', + LF = '\n', +} + +/** + * The ETX character is used to signal the end of a record. + * + * This EtxSplitter will look out for ETX characters in the stream. If it + * finds any, it will switch from splitting the stream on newlines to + * splitting on ETX characters. + */ +export class EtxSplitter { + private etxSpotted = false; + + public static stream(): Transform { + // needs https://github.com/mcollina/split2/pull/59 and DT update + // to be done without workarounds + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (split as any)(new EtxSplitter(), undefined, undefined); + } + + [Symbol.split](str: string) { + this.etxSpotted ||= str.includes(Char.ETX); + const split = str.split(this.etxSpotted ? Char.ETX : Char.LF); + // restore or add new lines between each record for proper debug console display + return split.length > 1 ? split.map((s, i) => (i < split.length - 1 ? `${s}\n` : s)) : split; + } +} diff --git a/src/test/browser/blazorSourcePathResolverTest.ts b/src/test/browser/blazorSourcePathResolverTest.ts index 21a90ca49..61ff67b06 100644 --- a/src/test/browser/blazorSourcePathResolverTest.ts +++ b/src/test/browser/blazorSourcePathResolverTest.ts @@ -58,7 +58,7 @@ describe('BlazorSourcePathResolver.absolutePathToUrlRegexp', () => { expect(regexp).to.equal( 'file:\\/\\/\\/c\\/Users\\/digeff\\/source\\/repos\\/MyBlazorApp\\/MyBlazorApp\\/Pages\\/Counter\\.razor($|\\?)' + '|\\/c\\/Users\\/digeff\\/source\\/repos\\/MyBlazorApp\\/MyBlazorApp\\/Pages\\/Counter\\.razor($|\\?)' + - '|http:\\/\\/localhost:1234\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/c\\/Users\\/digeff\\/source\\/repos\\/MyBlazorApp\\/MyBlazorApp\\/Pages\\/Counter.razor($|\\?)', + '|http:\\/\\/localhost:1234\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/\\.\\.\\/c\\/Users\\/digeff\\/source\\/repos\\/MyBlazorApp\\/MyBlazorApp\\/Pages\\/Counter\\.razor($|\\?)', ); } else { // This regexp was generated from running the real scenario, verifying that the breakpoint with this regexp works, and then copying it here