Spaces:
Running
Running
Commit ·
f4f6dff
1
Parent(s): 47e3df3
commit initial 09-12-2025 32
Browse files- src/App.js +124 -90
src/App.js
CHANGED
|
@@ -140,6 +140,9 @@ function App() {
|
|
| 140 |
const [isRunning, setIsRunning] = useState(false);
|
| 141 |
const [isFixing, setIsFixing] = useState(false);
|
| 142 |
const [isExplaining, setIsExplaining] = useState(false);
|
|
|
|
|
|
|
|
|
|
| 143 |
|
| 144 |
useEffect(() => {
|
| 145 |
saveTree(tree);
|
|
@@ -271,106 +274,130 @@ function App() {
|
|
| 271 |
|
| 272 |
// Core run with updated input (used by XTerm onData and Send button)
|
| 273 |
const runCodeWithUpdatedInput = async (inputLine) => {
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
return;
|
| 283 |
-
}
|
| 284 |
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
|
|
|
|
|
|
|
|
|
| 307 |
setAwaitingInput(false);
|
| 308 |
-
} finally {
|
| 309 |
-
setIsRunning(false);
|
| 310 |
}
|
| 311 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
|
| 313 |
// Initial Run: do NOT run if code needs interactive input and there's no accumulated stdin.
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
const haveLegacyStdin = !!(stdin && stdin.length > 0);
|
| 329 |
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
setAwaitingInput(true);
|
| 334 |
focusXtermHelper();
|
| 335 |
-
|
|
|
|
|
|
|
| 336 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
const res = await runCode(node.content, selectedLang, stdinToSend);
|
| 346 |
-
const out = res.output ?? "";
|
| 347 |
-
setOutput(out);
|
| 348 |
-
setProblems(res.error ? parseProblems(res.output) : []);
|
| 349 |
-
if (outputLooksForInput(out)) {
|
| 350 |
-
setAwaitingInput(true);
|
| 351 |
-
focusXtermHelper();
|
| 352 |
-
} else {
|
| 353 |
-
setAwaitingInput(false);
|
| 354 |
-
}
|
| 355 |
-
} catch (err) {
|
| 356 |
-
setOutput(String(err));
|
| 357 |
-
// allow interactive continuation after EOF
|
| 358 |
-
setAwaitingInput(true);
|
| 359 |
focusXtermHelper();
|
| 360 |
-
} finally {
|
| 361 |
-
setIsRunning(false);
|
| 362 |
}
|
| 363 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
|
| 365 |
-
// Send from the small input box: delegate to runCodeWithUpdatedInput to keep behavior uniform
|
| 366 |
-
const sendTerminalInput = async () => {
|
| 367 |
-
if (!terminalInput || terminalInput.length === 0) return;
|
| 368 |
-
// echo in output for small box then run
|
| 369 |
-
setOutput((prev) => prev + `\n> ${terminalInput}`);
|
| 370 |
-
const line = terminalInput;
|
| 371 |
-
setTerminalInput("");
|
| 372 |
-
await runCodeWithUpdatedInput(line);
|
| 373 |
-
};
|
| 374 |
|
| 375 |
// handle press Enter for small input
|
| 376 |
const onTerminalKeyDown = (e) => {
|
|
@@ -554,12 +581,19 @@ function App() {
|
|
| 554 |
<div style={{ fontSize: 12, color: "#ccc", marginBottom: 6 }}>Terminal</div>
|
| 555 |
|
| 556 |
<XTerm
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
|
| 564 |
{/* When interactive program detected and waiting for user input */}
|
| 565 |
{awaitingInput && (
|
|
|
|
| 140 |
const [isRunning, setIsRunning] = useState(false);
|
| 141 |
const [isFixing, setIsFixing] = useState(false);
|
| 142 |
const [isExplaining, setIsExplaining] = useState(false);
|
| 143 |
+
// near other useState declarations
|
| 144 |
+
const [interactivePromptShown, setInteractivePromptShown] = useState(false);
|
| 145 |
+
|
| 146 |
|
| 147 |
useEffect(() => {
|
| 148 |
saveTree(tree);
|
|
|
|
| 274 |
|
| 275 |
// Core run with updated input (used by XTerm onData and Send button)
|
| 276 |
const runCodeWithUpdatedInput = async (inputLine) => {
|
| 277 |
+
if (typeof inputLine !== "string") inputLine = String(inputLine || "");
|
| 278 |
+
const trimmed = inputLine.replace(/\r$/, ""); // normalize
|
| 279 |
+
if (trimmed.length === 0 && !accumStdin) {
|
| 280 |
+
// nothing to send
|
| 281 |
+
return;
|
| 282 |
+
}
|
| 283 |
|
| 284 |
+
// append newline like a real console
|
| 285 |
+
const newAccum = (accumStdin || "") + trimmed + "\n";
|
| 286 |
+
setAccumStdin(newAccum);
|
| 287 |
+
setInteractivePromptShown(false); // first input arrived -> clear prompt-shown flag
|
|
|
|
|
|
|
| 288 |
|
| 289 |
+
const node = getNodeByPath(tree, activePath);
|
| 290 |
+
if (!node || node.type !== "file") {
|
| 291 |
+
setOutput((prev) => prev + "\n[Error] No file selected to run.");
|
| 292 |
+
setAwaitingInput(false);
|
| 293 |
+
return;
|
| 294 |
+
}
|
| 295 |
|
| 296 |
+
const selectedLang = LANGUAGE_OPTIONS.find((l) => node.name.endsWith(l.ext))?.id;
|
| 297 |
+
if (!selectedLang || !RUNNABLE_LANGS.includes(selectedLang)) {
|
| 298 |
+
setOutput((prev) => prev + `\n[Error] Run not supported for ${node.name}`);
|
| 299 |
+
setAwaitingInput(false);
|
| 300 |
+
return;
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
setIsRunning(true);
|
| 304 |
+
try {
|
| 305 |
+
const res = await runCode(node.content, selectedLang, newAccum);
|
| 306 |
+
const out = res.output ?? "";
|
| 307 |
+
setOutput(out);
|
| 308 |
+
setProblems(res.error ? parseProblems(res.output) : []);
|
| 309 |
+
if (outputLooksForInput(out)) {
|
| 310 |
+
// program still waits for more input
|
| 311 |
+
setAwaitingInput(true);
|
| 312 |
+
focusXtermHelper();
|
| 313 |
+
} else {
|
| 314 |
setAwaitingInput(false);
|
|
|
|
|
|
|
| 315 |
}
|
| 316 |
+
} catch (err) {
|
| 317 |
+
setOutput(String(err));
|
| 318 |
+
setAwaitingInput(true);
|
| 319 |
+
} finally {
|
| 320 |
+
setIsRunning(false);
|
| 321 |
+
}
|
| 322 |
+
};
|
| 323 |
+
|
| 324 |
|
| 325 |
// Initial Run: do NOT run if code needs interactive input and there's no accumulated stdin.
|
| 326 |
+
const handleRun = async () => {
|
| 327 |
+
const node = getNodeByPath(tree, activePath);
|
| 328 |
+
if (!node || node.type !== "file") {
|
| 329 |
+
setOutput("Select a file to run.");
|
| 330 |
+
return;
|
| 331 |
+
}
|
| 332 |
+
const selectedLang = LANGUAGE_OPTIONS.find((l) => node.name.endsWith(l.ext))?.id;
|
| 333 |
+
if (!selectedLang || !RUNNABLE_LANGS.includes(selectedLang)) {
|
| 334 |
+
setOutput(`⚠️ Run not supported for this file type.`);
|
| 335 |
+
return;
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
const needs = codeNeedsInput(node.content, selectedLang);
|
| 339 |
+
const haveAccum = !!(accumStdin && accumStdin.length > 0);
|
| 340 |
+
const haveLegacyStdin = !!(stdin && stdin.length > 0);
|
| 341 |
+
|
| 342 |
+
if (needs && !haveAccum && !haveLegacyStdin) {
|
| 343 |
+
// Show helpful message in terminal and enable awaitingInput so user can type into XTerm.
|
| 344 |
+
setOutput("[Interactive program detected — type input directly into the terminal]");
|
| 345 |
+
setAwaitingInput(true);
|
| 346 |
+
setInteractivePromptShown(true);
|
| 347 |
+
focusXtermHelper();
|
| 348 |
+
return; // do not run now (avoids EOFError)
|
| 349 |
+
}
|
| 350 |
|
| 351 |
+
// Otherwise run immediately with whichever stdin we have (accumStdin or legacy stdin)
|
| 352 |
+
const stdinToSend = accumStdin || stdin || "";
|
|
|
|
| 353 |
|
| 354 |
+
resetTerminal(true); // keep accumStdin
|
| 355 |
+
setOutput(`[Running with stdin length=${stdinToSend ? stdinToSend.length : 0}]\n`);
|
| 356 |
+
setIsRunning(true);
|
| 357 |
+
setProblems([]);
|
| 358 |
+
try {
|
| 359 |
+
const res = await runCode(node.content, selectedLang, stdinToSend);
|
| 360 |
+
const out = res.output ?? "";
|
| 361 |
+
setOutput(out);
|
| 362 |
+
setProblems(res.error ? parseProblems(res.output) : []);
|
| 363 |
+
if (outputLooksForInput(out)) {
|
| 364 |
setAwaitingInput(true);
|
| 365 |
focusXtermHelper();
|
| 366 |
+
} else {
|
| 367 |
+
setAwaitingInput(false);
|
| 368 |
+
setInteractivePromptShown(false);
|
| 369 |
}
|
| 370 |
+
} catch (err) {
|
| 371 |
+
setOutput(String(err));
|
| 372 |
+
// Allow user to type into terminal after EOF errors
|
| 373 |
+
setAwaitingInput(true);
|
| 374 |
+
setInteractivePromptShown(true);
|
| 375 |
+
focusXtermHelper();
|
| 376 |
+
} finally {
|
| 377 |
+
setIsRunning(false);
|
| 378 |
+
}
|
| 379 |
+
};
|
| 380 |
|
| 381 |
+
|
| 382 |
+
// Send from the small input box: delegate to runCodeWithUpdatedInput to keep behavior uniform
|
| 383 |
+
const sendTerminalInput = async () => {
|
| 384 |
+
const line = (terminalInput || "").replace(/\r$/, "");
|
| 385 |
+
if (!line) {
|
| 386 |
+
// If there is no typed small-input but interactivePromptShown is true, just focus terminal
|
| 387 |
+
if (interactivePromptShown) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
focusXtermHelper();
|
|
|
|
|
|
|
| 389 |
}
|
| 390 |
+
return;
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
// show echo in output
|
| 394 |
+
setOutput((prev) => (prev ? prev + `\n> ${line}` : `> ${line}`));
|
| 395 |
+
setTerminalInput("");
|
| 396 |
+
|
| 397 |
+
// Use the unified runner that appends input and runs
|
| 398 |
+
await runCodeWithUpdatedInput(line);
|
| 399 |
+
};
|
| 400 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 401 |
|
| 402 |
// handle press Enter for small input
|
| 403 |
const onTerminalKeyDown = (e) => {
|
|
|
|
| 581 |
<div style={{ fontSize: 12, color: "#ccc", marginBottom: 6 }}>Terminal</div>
|
| 582 |
|
| 583 |
<XTerm
|
| 584 |
+
output={output}
|
| 585 |
+
onData={(line) => {
|
| 586 |
+
const text = (line || "").replace(/\r$/, "");
|
| 587 |
+
if (!text) return;
|
| 588 |
+
// only auto-send when we previously showed a prompt OR are awaiting input
|
| 589 |
+
if (interactivePromptShown || awaitingInput) {
|
| 590 |
+
runCodeWithUpdatedInput(text);
|
| 591 |
+
} else {
|
| 592 |
+
// not an interactive session — you can still echo into small input if desired
|
| 593 |
+
setTerminalInput((prev) => (prev ? prev + text : text));
|
| 594 |
+
}
|
| 595 |
+
}}
|
| 596 |
+
/>
|
| 597 |
|
| 598 |
{/* When interactive program detected and waiting for user input */}
|
| 599 |
{awaitingInput && (
|