Skip to content

Commit c6c2b73

Browse files
committed
fix(terminal): wait for the shell integration to be ready
1 parent c3dde64 commit c6c2b73

File tree

6 files changed

+90
-13
lines changed

6 files changed

+90
-13
lines changed

src/api/child_process.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export async function createVitestProcess(pkg: VitestPackage) {
9090
vitest.on('exit', onExit)
9191
vitest.on('error', onError)
9292

93-
waitForWsConnection(wss, pkg, false, 'child_process')
93+
waitForWsConnection(wss, pkg, false, 'child_process', false)
9494
.then((resolved) => {
9595
resolved.handlers.onStdout = (callback: (data: string) => void) => {
9696
stdoutCallbacks.add(callback)

src/api/terminal.ts

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@ import type { WebSocket } from 'ws'
33
import type { ResolvedMeta } from '../api'
44
import type { VitestPackage } from './pkg'
55
import type { ExtensionWorkerProcess } from './types'
6+
import type { WsConnectionMetadata } from './ws'
67
import { createServer } from 'node:http'
78
import { pathToFileURL } from 'node:url'
9+
import { stripVTControlCharacters } from 'node:util'
810
import getPort from 'get-port'
911
import * as vscode from 'vscode'
1012
import { WebSocketServer } from 'ws'
1113
import { getConfig } from '../config'
1214
import { workerPath } from '../constants'
1315
import { createErrorLogger, log } from '../log'
14-
import { formatPkg, showVitestError } from '../utils'
15-
import { waitForWsConnection, WsConnectionMetadata } from './ws'
16+
import { formatPkg } from '../utils'
17+
import { waitForWsConnection } from './ws'
1618

1719
export async function createVitestTerminalProcess(pkg: VitestPackage): Promise<ResolvedMeta> {
1820
const pnpLoader = pkg.loader
@@ -42,27 +44,80 @@ export async function createVitestTerminalProcess(pkg: VitestPackage): Promise<R
4244
NODE_ENV: env.NODE_ENV ?? process.env.NODE_ENV ?? 'test',
4345
},
4446
})
47+
// TODO: make sure it is desposed even if it throws, the same for child_process
48+
49+
const shellIntegration = await new Promise<vscode.TerminalShellIntegration | undefined>((resolve) => {
50+
const disposable = vscode.window.onDidChangeTerminalShellIntegration((e) => {
51+
const timeout = setTimeout(() => {
52+
disposable.dispose()
53+
resolve(undefined)
54+
}, 3_000)
55+
56+
if (e.terminal === terminal) {
57+
disposable.dispose()
58+
clearTimeout(timeout)
59+
resolve(e.shellIntegration)
60+
}
61+
})
62+
})
63+
64+
const processId = await terminal.processId
65+
if (terminal.exitStatus && terminal.exitStatus.code != null) {
66+
throw new Error(`Terminal was ${getExitReason(terminal.exitStatus.reason)} with code ${terminal.exitStatus.code}`)
67+
}
68+
4569
let command = 'node'
4670
if (pnpLoader && pnp) {
4771
command += ` --require ${pnp} --experimental-loader ${pathToFileURL(pnpLoader).toString()}`
4872
}
49-
command += ` ${workerPath}`
50-
log.info('[API]', `Initiated ws connection via ${wsAddress}`)
51-
log.info('[API]', `Starting ${formatPkg(pkg)} in the terminal: ${command}`)
52-
terminal.sendText(command, true)
73+
command += ` ${workerPath};`
74+
75+
log.info('[TERMINAL]', `Initiated ws connection via ${wsAddress}`)
76+
log.info('[TERMINAL]', `Starting ${formatPkg(pkg)} in the terminal: ${command}`)
77+
78+
if (shellIntegration) {
79+
log.info('[TERMINAL] Shell integration is initiated.')
80+
let execution: vscode.TerminalShellExecution
81+
const onWriteShell = vscode.window.onDidStartTerminalShellExecution(async (e) => {
82+
if (e.execution !== execution) {
83+
return
84+
}
85+
86+
log.info('[TERMINAL] Reporting the shell output.')
87+
for await (const line of e.execution.read()) {
88+
log.worker('info', stripVTControlCharacters(line))
89+
onWriteShell.dispose()
90+
}
91+
})
92+
93+
const onEndShell = vscode.window.onDidEndTerminalShellExecution((e) => {
94+
if (e.execution === execution) {
95+
log.info('[TERMINAL] The shell execution was finished.')
96+
onWriteShell.dispose()
97+
onEndShell.dispose()
98+
}
99+
})
100+
execution = shellIntegration.executeCommand(command)
101+
}
102+
else {
103+
log.info('[TERMINAL] Shell integration is not initiated, fallback to `terminal.sendText`.')
104+
terminal.sendText(command, true)
105+
}
106+
53107
const meta = await new Promise<WsConnectionMetadata>((resolve, reject) => {
54108
const timeout = setTimeout(() => {
55109
terminal.show(false)
56110
reject(new Error(`The extension could not connect to the terminal in 5 seconds. See the "vitest" terminal output for more details.`))
57-
}, 5000)
58-
waitForWsConnection(wss, pkg, false, 'terminal').then(resolve, reject).finally(() => {
111+
}, 5_000)
112+
wss.once('connection', () => {
59113
clearTimeout(timeout)
60114
})
115+
waitForWsConnection(wss, pkg, false, 'terminal', !!shellIntegration).then(resolve, reject)
61116
})
62-
const processId = (await terminal.processId) ?? Math.random()
117+
63118
log.info('[API]', `${formatPkg(pkg)} terminal process ${processId} created`)
64119
const vitestProcess = new ExtensionTerminalProcess(
65-
processId,
120+
processId ?? Math.random(),
66121
terminal,
67122
server,
68123
meta.ws,
@@ -77,6 +132,22 @@ export async function createVitestTerminalProcess(pkg: VitestPackage): Promise<R
77132
}
78133
}
79134

135+
function getExitReason(reason: vscode.TerminalExitReason) {
136+
switch (reason) {
137+
case vscode.TerminalExitReason.Extension:
138+
return 'clsoed by extension'
139+
case vscode.TerminalExitReason.Process:
140+
return 'closed by the process'
141+
case vscode.TerminalExitReason.Shutdown:
142+
return 'reloaded or closed'
143+
case vscode.TerminalExitReason.User:
144+
return 'closed by the user'
145+
case vscode.TerminalExitReason.Unknown:
146+
default:
147+
return 'unexpectedly closed'
148+
}
149+
}
150+
80151
export class ExtensionTerminalProcess implements ExtensionWorkerProcess {
81152
private _onDidExit = new vscode.EventEmitter<number | null>()
82153

src/api/ws.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export function waitForWsConnection(
1717
pkg: VitestPackage,
1818
debug: boolean,
1919
shellType: 'terminal' | 'child_process',
20+
hasShellIntegration: boolean,
2021
) {
2122
return new Promise<WsConnectionMetadata>((resolve, reject) => {
2223
wss.once('connection', (ws) => {
@@ -25,6 +26,7 @@ export function waitForWsConnection(
2526
pkg,
2627
debug,
2728
shellType,
29+
hasShellIntegration,
2830
meta => resolve(meta),
2931
err => reject(err),
3032
)
@@ -51,6 +53,7 @@ export function onWsConnection(
5153
pkg: VitestPackage,
5254
debug: boolean,
5355
shellType: 'terminal' | 'child_process',
56+
hasShellIntegration: boolean,
5457
onStart: (meta: WsConnectionMetadata) => unknown,
5558
onFail: (err: Error) => unknown,
5659
) {
@@ -116,6 +119,7 @@ export function onWsConnection(
116119
type: 'init',
117120
meta: {
118121
shellType,
122+
hasShellIntegration,
119123
vitestNodePath: pkg.vitestNodePath,
120124
env: getConfig(pkg.folder).env || undefined,
121125
configFile: pkg.configFile,

src/debug.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export async function debugTests(
5555
smartStep: true,
5656
...(config.shellType === 'terminal'
5757
? {
58-
command: `${runtimeExecutable} ${workerPath}`,
58+
command: `${runtimeExecutable} ${workerPath};exit`,
5959
}
6060
: {
6161
program: workerPath,
@@ -116,6 +116,7 @@ export async function debugTests(
116116
pkg,
117117
true,
118118
config.shellType,
119+
false,
119120
async (metadata) => {
120121
try {
121122
const api = new VitestFolderAPI(pkg, {

src/worker/init.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export async function initVitest(meta: WorkerInitMetadata, options?: UserConfig)
1212
let stdout: Writable | undefined
1313
let stderr: Writable | undefined
1414

15-
if (meta.shellType === 'terminal') {
15+
if (meta.shellType === 'terminal' && !meta.hasShellIntegration) {
1616
stdout = new Writable({
1717
write(chunk, __, callback) {
1818
const log = chunk.toString()

src/worker/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface WorkerInitMetadata {
77
workspaceFile?: string
88
env: Record<string, any> | undefined
99
shellType: 'terminal' | 'child_process'
10+
hasShellIntegration: boolean
1011
pnpApi?: string
1112
pnpLoader?: string
1213
}

0 commit comments

Comments
 (0)