Spaces:
Running
Running
Commit ·
a405934
1
Parent(s): 0a3b0ff
commit initial 09-12-2025 021
Browse files- package-lock.json +20 -1
- package.json +3 -1
- src/App.js +14 -29
- src/Terminal.js +60 -0
package-lock.json
CHANGED
|
@@ -19,7 +19,9 @@
|
|
| 19 |
"react-dom": "^19.1.0",
|
| 20 |
"react-hot-toast": "^2.6.0",
|
| 21 |
"react-scripts": "5.0.1",
|
| 22 |
-
"web-vitals": "^2.1.4"
|
|
|
|
|
|
|
| 23 |
}
|
| 24 |
},
|
| 25 |
"node_modules/@adobe/css-tools": {
|
|
@@ -17561,6 +17563,23 @@
|
|
| 17561 |
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
| 17562 |
"license": "MIT"
|
| 17563 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17564 |
"node_modules/y18n": {
|
| 17565 |
"version": "5.0.8",
|
| 17566 |
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
|
|
|
| 19 |
"react-dom": "^19.1.0",
|
| 20 |
"react-hot-toast": "^2.6.0",
|
| 21 |
"react-scripts": "5.0.1",
|
| 22 |
+
"web-vitals": "^2.1.4",
|
| 23 |
+
"xterm": "^5.3.0",
|
| 24 |
+
"xterm-addon-fit": "^0.8.0"
|
| 25 |
}
|
| 26 |
},
|
| 27 |
"node_modules/@adobe/css-tools": {
|
|
|
|
| 17563 |
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
| 17564 |
"license": "MIT"
|
| 17565 |
},
|
| 17566 |
+
"node_modules/xterm": {
|
| 17567 |
+
"version": "5.3.0",
|
| 17568 |
+
"resolved": "https://registry.npmjs.org/xterm/-/xterm-5.3.0.tgz",
|
| 17569 |
+
"integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==",
|
| 17570 |
+
"deprecated": "This package is now deprecated. Move to @xterm/xterm instead.",
|
| 17571 |
+
"license": "MIT"
|
| 17572 |
+
},
|
| 17573 |
+
"node_modules/xterm-addon-fit": {
|
| 17574 |
+
"version": "0.8.0",
|
| 17575 |
+
"resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz",
|
| 17576 |
+
"integrity": "sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==",
|
| 17577 |
+
"deprecated": "This package is now deprecated. Move to @xterm/addon-fit instead.",
|
| 17578 |
+
"license": "MIT",
|
| 17579 |
+
"peerDependencies": {
|
| 17580 |
+
"xterm": "^5.0.0"
|
| 17581 |
+
}
|
| 17582 |
+
},
|
| 17583 |
"node_modules/y18n": {
|
| 17584 |
"version": "5.0.8",
|
| 17585 |
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
package.json
CHANGED
|
@@ -14,7 +14,9 @@
|
|
| 14 |
"react-dom": "^19.1.0",
|
| 15 |
"react-hot-toast": "^2.6.0",
|
| 16 |
"react-scripts": "5.0.1",
|
| 17 |
-
"web-vitals": "^2.1.4"
|
|
|
|
|
|
|
| 18 |
},
|
| 19 |
"scripts": {
|
| 20 |
"start": "react-scripts start",
|
|
|
|
| 14 |
"react-dom": "^19.1.0",
|
| 15 |
"react-hot-toast": "^2.6.0",
|
| 16 |
"react-scripts": "5.0.1",
|
| 17 |
+
"web-vitals": "^2.1.4",
|
| 18 |
+
"xterm": "^5.3.0",
|
| 19 |
+
"xterm-addon-fit": "^0.8.0"
|
| 20 |
},
|
| 21 |
"scripts": {
|
| 22 |
"start": "react-scripts start",
|
src/App.js
CHANGED
|
@@ -16,7 +16,8 @@ import {
|
|
| 16 |
import { downloadProjectZip } from "./zipExport";
|
| 17 |
import { parseProblems } from "./problemParser";
|
| 18 |
import "./App.css";
|
| 19 |
-
|
|
|
|
| 20 |
// =================== SUPPORTED LANGUAGES ===================
|
| 21 |
const LANGUAGE_OPTIONS = [
|
| 22 |
{ id: "python", ext: ".py", icon: "🐍", monaco: "python" },
|
|
@@ -535,34 +536,18 @@ function codeNeedsInput(code, langId) {
|
|
| 535 |
{/* Bottom panels */}
|
| 536 |
<div className="ide-panels">
|
| 537 |
{/* Terminal output */}
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
placeholder="Type input and press Enter..."
|
| 551 |
-
value={terminalInput}
|
| 552 |
-
onChange={(e) => setTerminalInput(e.target.value)}
|
| 553 |
-
onKeyDown={onTerminalKeyDown}
|
| 554 |
-
disabled={!awaitingInput || isRunning}
|
| 555 |
-
/>
|
| 556 |
-
<button onClick={sendTerminalInput} disabled={!awaitingInput || isRunning} className="ide-button">Send</button>
|
| 557 |
-
</div>
|
| 558 |
-
) : (
|
| 559 |
-
// If not awaiting input, show legacy single-run input + hint to run for interactive programs
|
| 560 |
-
<div style={{ display: "flex", gap: 6, marginTop: 6 }}>
|
| 561 |
-
<input className="ide-input-box" placeholder="(Optional) Program input for single-run" value={stdin} onChange={(e) => setStdin(e.target.value)} />
|
| 562 |
-
<div style={{ alignSelf: "center", color: "#999", fontSize: 12 }}>Press Run → to execute</div>
|
| 563 |
-
</div>
|
| 564 |
-
)}
|
| 565 |
-
</div>
|
| 566 |
|
| 567 |
{/* Problems */}
|
| 568 |
{problems.length > 0 && (
|
|
|
|
| 16 |
import { downloadProjectZip } from "./zipExport";
|
| 17 |
import { parseProblems } from "./problemParser";
|
| 18 |
import "./App.css";
|
| 19 |
+
import "xterm/css/xterm.css";
|
| 20 |
+
import XTerm from "./Terminal";
|
| 21 |
// =================== SUPPORTED LANGUAGES ===================
|
| 22 |
const LANGUAGE_OPTIONS = [
|
| 23 |
{ id: "python", ext: ".py", icon: "🐍", monaco: "python" },
|
|
|
|
| 536 |
{/* Bottom panels */}
|
| 537 |
<div className="ide-panels">
|
| 538 |
{/* Terminal output */}
|
| 539 |
+
<XTerm
|
| 540 |
+
output={output}
|
| 541 |
+
onData={(userInputLine) => {
|
| 542 |
+
// Send each line to backend dynamically
|
| 543 |
+
setAccumStdin((prev) => prev + userInputLine + "\n");
|
| 544 |
+
|
| 545 |
+
// If waiting for next input, resume execution or re-run code
|
| 546 |
+
if (awaitingInput) {
|
| 547 |
+
runCodeWithUpdatedInput();
|
| 548 |
+
}
|
| 549 |
+
}}
|
| 550 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
|
| 552 |
{/* Problems */}
|
| 553 |
{problems.length > 0 && (
|
src/Terminal.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useEffect, useRef } from "react";
|
| 2 |
+
import { Terminal } from "xterm";
|
| 3 |
+
import { FitAddon } from "xterm-addon-fit";
|
| 4 |
+
import "xterm/css/xterm.css";
|
| 5 |
+
|
| 6 |
+
export default function XTerm({ onData, output }) {
|
| 7 |
+
const termRef = useRef(null);
|
| 8 |
+
const fitAddon = new FitAddon();
|
| 9 |
+
|
| 10 |
+
useEffect(() => {
|
| 11 |
+
const term = new Terminal({
|
| 12 |
+
cursorBlink: true,
|
| 13 |
+
fontSize: 14,
|
| 14 |
+
theme: {
|
| 15 |
+
background: "#1e1e1e",
|
| 16 |
+
foreground: "#ffffff",
|
| 17 |
+
},
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
termRef.current = term;
|
| 21 |
+
term.loadAddon(fitAddon);
|
| 22 |
+
|
| 23 |
+
term.open(document.getElementById("terminal-container"));
|
| 24 |
+
fitAddon.fit();
|
| 25 |
+
|
| 26 |
+
term.onData((data) => {
|
| 27 |
+
// Echo user input (like real terminals)
|
| 28 |
+
term.write(data);
|
| 29 |
+
|
| 30 |
+
// When ENTER pressed, send full line to parent
|
| 31 |
+
if (data === "\r") {
|
| 32 |
+
const line = term._core.buffer.xtermBuffer.active.getLine(
|
| 33 |
+
term.buffer.active.cursorY
|
| 34 |
+
).translateToString().trim();
|
| 35 |
+
onData(line);
|
| 36 |
+
}
|
| 37 |
+
});
|
| 38 |
+
|
| 39 |
+
return () => term.dispose();
|
| 40 |
+
}, []);
|
| 41 |
+
|
| 42 |
+
// Print backend output into terminal
|
| 43 |
+
useEffect(() => {
|
| 44 |
+
if (output) {
|
| 45 |
+
termRef.current?.writeln("\r\n" + output);
|
| 46 |
+
}
|
| 47 |
+
}, [output]);
|
| 48 |
+
|
| 49 |
+
return (
|
| 50 |
+
<div
|
| 51 |
+
id="terminal-container"
|
| 52 |
+
style={{
|
| 53 |
+
width: "100%",
|
| 54 |
+
height: "180px",
|
| 55 |
+
background: "#1e1e1e",
|
| 56 |
+
borderTop: "1px solid #333",
|
| 57 |
+
}}
|
| 58 |
+
></div>
|
| 59 |
+
);
|
| 60 |
+
}
|