diff --git a/packages/extensions/js-runner-and-debugger/src/web/utils/cancel-manager.ts b/packages/extensions/js-runner-and-debugger/src/web/utils/cancel-manager.ts new file mode 100644 index 0000000..b92a8f6 --- /dev/null +++ b/packages/extensions/js-runner-and-debugger/src/web/utils/cancel-manager.ts @@ -0,0 +1,54 @@ +type CancelHandler = () => void; + +class CancelSignal { + private _cancelled: boolean = false; + private _handlers: CancelHandler[] = []; + + get cancelled(): boolean { + return this._cancelled; + } + + addEventListener(type: 'cancel', handler: CancelHandler) { + if (type === 'cancel') { + this._handlers.push(handler); + } + } + + removeEventListener(type: 'cancel', handler: CancelHandler) { + if (type === 'cancel') { + this._handlers = this._handlers.filter(h => h !== handler); + } + } + + dispatchEvent(type: 'cancel') { + if (type === 'cancel' && !this._cancelled) { + this._cancelled = true; + this._handlers.forEach(handler => handler()); + } + } +} + +export class CancelManager { + private _signal: CancelSignal; + + constructor() { + this._signal = new CancelSignal(); + } + + get signal(): CancelSignal { + return this._signal; + } + + async runCancellable(promise: Promise): Promise { + return new Promise((resolve, reject) => { + this._signal.addEventListener('cancel', () => + reject(new Error('Operation cancelled')) + ); + promise.then(resolve).catch(reject); + }); + } + + cancel() { + this._signal.dispatchEvent('cancel'); + } +} diff --git a/packages/extensions/js-runner-and-debugger/src/web/utils/wasi.ts b/packages/extensions/js-runner-and-debugger/src/web/utils/wasi.ts index 403d544..13ba7eb 100644 --- a/packages/extensions/js-runner-and-debugger/src/web/utils/wasi.ts +++ b/packages/extensions/js-runner-and-debugger/src/web/utils/wasi.ts @@ -7,7 +7,9 @@ import { WasmProcess, PseudoterminalState, } from '@vscode/wasm-wasi'; +import { logger } from './logger'; import { ansiColor } from './ansi-color'; +import { CancelManager } from './cancel-manager'; export interface WasiEnv { wasm: Wasm; @@ -180,6 +182,8 @@ export async function runWasiCommand( lifecycle.postCommandFn(); return; } + // 取消管理器 + const cancelManager = new CancelManager(); // Use coreutils as default command let commandName = 'coreutils'; @@ -262,6 +266,10 @@ export async function runWasiCommand( if (!wasiTerminal.state.isReadlineMode) { wasiTerminal.state.isReadlineMode = true; + pty.onDidCloseTerminal(() => { + cancelManager.cancel(); + wasiTerminal.terminal.dispose(); + }); const orignalHandleInput = pty.handleInput; pty.handleInput = function (data: string) { if ( @@ -284,14 +292,18 @@ export async function runWasiCommand( return orignalHandleInput?.apply(this, [data]); }; let nextCommand: string = ''; - while (true) { - nextCommand = await pty.readline(); - if (nextCommand) { - await runWasiCommand( - context, - wasiTerminal, - nextCommand ? nextCommand.trim().split(' ') : [] - ); + while (!cancelManager.signal.cancelled) { + try { + nextCommand = await cancelManager.runCancellable(pty.readline()); + if (nextCommand) { + await runWasiCommand( + context, + wasiTerminal, + nextCommand ? nextCommand.trim().split(' ') : [] + ); + } + } catch (error) { + logger.error('Terminal run command failed', error); } } }