Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "browser.cpp",
"short_name": "browser.cpp",
"description": "In-browser C++20 IDE powered by Monaco Editor and WASM Clang",
"version": "0.4.8",
"version": "0.4.9",
"minimum_chrome_version": "105",
"icons": {
"16": "icons/icon16.png",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "browser.cpp",
"version": "0.4.8",
"version": "0.4.9",
"description": "In-browser C++20 IDE with WASM Clang toolchain",
"private": true,
"scripts": {
Expand Down
43 changes: 39 additions & 4 deletions scripts/e2e-terminal-stop.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -149,20 +149,55 @@ test('e2e: prompt restoration follows newline-less program output on a new line'
assert.match(ctx.writes.join(''), /program output\r\n.*browser\.cpp.*:~\$ /);
});

test('e2e: Ctrl+C while running stops the program once and restores the prompt', async () => {
test('e2e: Ctrl+C while running resets to one pristine prompt', async () => {
const ctx = setupTerminalHarness();

assert.equal(await startRun(), true);
onRunStart({ stdinMode: 'interactive' });
__handleTerminalKeyForTesting('', ctrlCEvent());
writeStdout('runaway output');
const writesBeforeStop = ctx.writes.length;
__handleTerminalKeyForTesting('', ctrlCEvent());

assert.equal(ctx.runCalls.length, 1);
assert.deepEqual(ctx.stopCalls, ['stop']);
assert.deepEqual(ctx.runStateChanges, [true, false]);
assert.equal(__getTerminalStateForTesting().running, false);
assert.ok(ctx.writes.join('').includes('^C'));
assert.ok(ctx.writes.join('').includes('Process interrupted.'));
const resetWrites = ctx.writes.slice(writesBeforeStop).join('');
assert.ok(resetWrites.includes('browser.cpp'));
assert.equal(resetWrites.match(/browser\.cpp/g)?.length, 1);
assert.ok(!resetWrites.includes('^C'));
assert.ok(!resetWrites.includes('Process interrupted.'));
assert.equal(ctx.clearCalls.length, 1);
});

test('e2e: STOP discards stdout queued after a stopped run', async () => {
const ctx = setupTerminalHarness();

showInitialPrompt();
assert.equal(await startRun(), true);
onRunStart({ stdinMode: 'interactive' });
writeStdout('runaway output');
assert.equal(stopRun(), true);
const writesAfterStop = ctx.writes.length;

writeStdout('late output');

assert.equal(ctx.writes.length, writesAfterStop);
assert.ok(!ctx.writes.join('').includes('Process interrupted.'));
});

test('e2e: STOP ignores a late nonzero run result', async () => {
const ctx = setupTerminalHarness();

showInitialPrompt();
assert.equal(await startRun(), true);
onRunStart({ stdinMode: 'interactive' });
assert.equal(stopRun(), true);
const writesAfterStop = ctx.writes.length;

onRunResult({ exitCode: 1 });

assert.equal(ctx.writes.length, writesAfterStop);
});

test('e2e: stopRun is idempotent for repeated button presses during one run', async () => {
Expand Down
8 changes: 6 additions & 2 deletions src/ui/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,12 @@ window.addEventListener('DOMContentLoaded', async () => {
if (terminalPanel) resizeObserver.observe(terminalPanel);
initPanelResizers();

// 9. Persist session on unload
window.addEventListener('beforeunload', () => persistenceGate.persist());
// 9. Persist session on unload. Worker teardown is synchronous: browser
// unload handlers cannot safely wait for terminal or worker cleanup.
window.addEventListener('beforeunload', () => {
worker.terminate();
persistenceGate.persist();
});

editorAPI.focus();
});
Expand Down
16 changes: 6 additions & 10 deletions src/ui/terminal.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,17 +466,11 @@ export function onRunStart({ stdinMode = 'none', stdinSessionId = null } = {}) {
* CPU-bound WASM cannot observe stdin EOF, so the main thread terminates the
* worker via _onStopRun after terminal state has been reset.
*
* @param {{ echoCtrlC?: boolean }} [options]
* @returns {boolean} true when a running program was stopped
*/
export function stopRun({ echoCtrlC = false } = {}) {
export function stopRun() {
if (!running) return false;

if (echoCtrlC) {
term?.write('^C' + CRLF);
} else {
term?.write(CRLF);
}
inputBuffer = '';
_clearSAB();
setRunPreparationState(false);
Expand All @@ -486,7 +480,7 @@ export function stopRun({ echoCtrlC = false } = {}) {
busy = false;
runDone?.();
runDone = null;
term?.write(`${C.yellow}Process interrupted.${C.reset}${CRLF}`);
clearScreen();
writePrompt();
_onStopRun?.();
return true;
Expand All @@ -496,6 +490,7 @@ export function stopRun({ echoCtrlC = false } = {}) {

/** Write stdout text from the running program. */
export function writeStdout(text) {
if (!running) return;
term?.write(text.replace(/\n/g, CRLF));
}

Expand Down Expand Up @@ -531,6 +526,7 @@ export function onCompileResult({ success, diagnostics, outputPath }) {
* @param {{ exitCode:number }} result
*/
export function onRunResult({ exitCode }) {
if (!running && !preparingRun) return;
const shouldRestorePrompt = running || preparingRun;
if (exitCode !== 0) {
term?.write(`${CRLF}${C.yellow}Process exited with code ${exitCode}.${C.reset}${CRLF}`);
Expand Down Expand Up @@ -609,7 +605,7 @@ function handleKey({ key, domEvent }) {
if (activeStdinMode === 'interactive' || activeStdinMode === 'interactive-message') {
handleStdinKey(key, domEvent);
} else if (domEvent.ctrlKey && domEvent.key === 'c') {
stopRun({ echoCtrlC: true });
stopRun();
}
return;
}
Expand Down Expand Up @@ -740,7 +736,7 @@ function handleStdinKey(key, domEvent) {

// Ctrl+C – interrupt the running program
if (domEvent.ctrlKey && code === 'c') {
stopRun({ echoCtrlC: true });
stopRun();
return;
}

Expand Down
Loading