FrederickSundeep commited on
Commit
921b162
Β·
1 Parent(s): eb69de5

commit initial 09-12-2025 018

Browse files
Files changed (1) hide show
  1. src/App.js +145 -33
src/App.js CHANGED
@@ -32,14 +32,38 @@ const LANGUAGE_OPTIONS = [
32
 
33
  const RUNNABLE_LANGS = ["python", "javascript", "java"];
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  // =================== APP ===================
36
  function App() {
37
  const [tree, setTree] = useState(loadTree());
38
  const [activePath, setActivePath] = useState("main.py"); // selected file or folder path
39
- const [output, setOutput] = useState("");
 
 
 
 
 
40
  const [prompt, setPrompt] = useState("");
41
  const [explanation, setExplanation] = useState("");
42
- const [stdin, setStdin] = useState("");
 
43
  const [problems, setProblems] = useState([]);
44
  const [theme, setTheme] = useState("vs-dark");
45
  const [searchOpen, setSearchOpen] = useState(false);
@@ -65,7 +89,7 @@ function App() {
65
  LANGUAGE_OPTIONS.find((l) => currentNode?.name?.endsWith(l.ext)) ||
66
  LANGUAGE_OPTIONS[0];
67
 
68
- // ---------- Tree utilities ----------
69
  const collectFolderPaths = (node, acc = []) => {
70
  if (!node) return acc;
71
  if (node.type === "folder") acc.push(node.path || "");
@@ -176,7 +200,23 @@ function App() {
176
  e.target.value = "";
177
  };
178
 
179
- // ---------- Run & Agent (with progress flags) ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  const handleRun = async () => {
181
  const node = getNodeByPath(tree, activePath);
182
  if (!node || node.type !== "file") {
@@ -189,20 +229,83 @@ function App() {
189
  return;
190
  }
191
 
 
 
192
  setIsRunning(true);
193
- setOutput(""); // clear previous
194
  setProblems([]);
 
195
  try {
196
- const res = await runCode(node.content, selectedLang, stdin);
197
- setOutput(res.output || "");
 
 
 
198
  setProblems(res.error ? parseProblems(res.output) : []);
 
 
 
 
 
 
 
199
  } catch (err) {
200
- setOutput(String(err));
 
 
 
201
  } finally {
202
  setIsRunning(false);
203
  }
204
  };
205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  const handleAskFix = async () => {
207
  const node = getNodeByPath(tree, activePath);
208
  if (!node || node.type !== "file") {
@@ -327,19 +430,9 @@ function App() {
327
 
328
  <div className="ide-menubar-right">
329
  <button onClick={handleSearchToggle} disabled={anyLoading}>πŸ” Search</button>
330
-
331
- <button onClick={handleRun} disabled={isRunning || anyLoading}>
332
- {isRunning ? "⏳ Running..." : "β–Ά Run"}
333
- </button>
334
-
335
- <button onClick={handleAskFix} disabled={isFixing || anyLoading}>
336
- {isFixing ? "⏳ Fixing..." : "πŸ€– Fix"}
337
- </button>
338
-
339
- <button onClick={handleExplainSelection} disabled={isExplaining || anyLoading}>
340
- {isExplaining ? "⏳ Explaining..." : "πŸ“– Explain"}
341
- </button>
342
-
343
  <button onClick={() => setTheme((t) => (t === "vs-dark" ? "light" : "vs-dark"))} disabled={anyLoading}>
344
  {theme === "vs-dark" ? "β˜€οΈ" : "πŸŒ™"}
345
  </button>
@@ -375,11 +468,7 @@ function App() {
375
  onChange={updateActiveFileContent}
376
  onMount={(editor) => (editorRef.current = editor)}
377
  onBlur={() => fetchAiSuggestions(currentNode?.content)}
378
- options={{
379
- minimap: { enabled: true },
380
- fontSize: 14,
381
- scrollBeyondLastLine: false,
382
- }}
383
  />
384
  </div>
385
 
@@ -394,18 +483,41 @@ function App() {
394
 
395
  {/* Bottom panels */}
396
  <div className="ide-panels">
397
- <pre className="ide-output">{output}</pre>
 
 
 
 
 
 
398
 
399
- <input className="ide-input-box" placeholder="Program input..." value={stdin} onChange={(e) => setStdin(e.target.value)} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
 
 
401
  {problems.length > 0 && (
402
  <div className="ide-problems-panel">
403
  <div>🚨 Problems ({problems.length})</div>
404
- {problems.map((p, i) => (
405
- <div key={i}>
406
- {p.path}:{p.line} β€” {p.message}
407
- </div>
408
- ))}
409
  </div>
410
  )}
411
  </div>
 
32
 
33
  const RUNNABLE_LANGS = ["python", "javascript", "java"];
34
 
35
+ // Utility: detect whether output likely requests input
36
+ function outputLooksForInput(output) {
37
+ if (!output) return false;
38
+ const o = output.toString();
39
+ // common prompt words / patterns and trailing prompt char
40
+ const patterns = [
41
+ /enter.*:/i,
42
+ /input.*:/i,
43
+ /please enter/i,
44
+ /scanner/i, // java Scanner exceptions or prompts
45
+ /press enter/i,
46
+ /: $/, // ends with colon + space (e.g. "Enter number: ")
47
+ /:\n$/, // ends with colon + newline
48
+ /> $/, // trailing >
49
+ ];
50
+ return patterns.some((p) => p.test(o));
51
+ }
52
+
53
  // =================== APP ===================
54
  function App() {
55
  const [tree, setTree] = useState(loadTree());
56
  const [activePath, setActivePath] = useState("main.py"); // selected file or folder path
57
+ // Terminal-ish state
58
+ const [terminalLines, setTerminalLines] = useState([]); // array of strings shown in terminal
59
+ const [terminalInput, setTerminalInput] = useState(""); // current typed input in terminal prompt
60
+ const [accumStdin, setAccumStdin] = useState(""); // accumulated stdin passed to backend
61
+ const [awaitingInput, setAwaitingInput] = useState(false); // true if program waiting for input
62
+ const [output, setOutput] = useState(""); // last raw output (kept for compatibility)
63
  const [prompt, setPrompt] = useState("");
64
  const [explanation, setExplanation] = useState("");
65
+ // other states
66
+ const [stdin, setStdin] = useState(""); // legacy single-run input (kept for compatibility)
67
  const [problems, setProblems] = useState([]);
68
  const [theme, setTheme] = useState("vs-dark");
69
  const [searchOpen, setSearchOpen] = useState(false);
 
89
  LANGUAGE_OPTIONS.find((l) => currentNode?.name?.endsWith(l.ext)) ||
90
  LANGUAGE_OPTIONS[0];
91
 
92
+ // ---------- TREE helpers ----------
93
  const collectFolderPaths = (node, acc = []) => {
94
  if (!node) return acc;
95
  if (node.type === "folder") acc.push(node.path || "");
 
200
  e.target.value = "";
201
  };
202
 
203
+ // ---------- Terminal/Run logic (interactive) ----------
204
+ // We will accumulate stdin in `accumStdin`. On initial run we clear it.
205
+ // When backend returns output containing cues that input is requested, set awaitingInput=true.
206
+ // When user types into terminal prompt and presses Enter, append that input to accumStdin + "\n",
207
+ // then re-run the program with updated accumStdin. Repeat until no input cues.
208
+
209
+ const resetTerminal = () => {
210
+ setTerminalLines([]);
211
+ setTerminalInput("");
212
+ setAccumStdin("");
213
+ setAwaitingInput(false);
214
+ };
215
+
216
+ const appendTerminal = (text) => {
217
+ setTerminalLines((prev) => [...prev, text]);
218
+ };
219
+
220
  const handleRun = async () => {
221
  const node = getNodeByPath(tree, activePath);
222
  if (!node || node.type !== "file") {
 
229
  return;
230
  }
231
 
232
+ // clear terminal state and start a fresh run
233
+ resetTerminal();
234
  setIsRunning(true);
 
235
  setProblems([]);
236
+ setOutput("");
237
  try {
238
+ // call backend with current accumStdin (empty on first run)
239
+ const res = await runCode(node.content, selectedLang, accumStdin || stdin || "");
240
+ const out = res.output ?? "";
241
+ setOutput(out);
242
+ appendTerminal(out);
243
  setProblems(res.error ? parseProblems(res.output) : []);
244
+
245
+ if (outputLooksForInput(out)) {
246
+ // program likely wants input: show prompt
247
+ setAwaitingInput(true);
248
+ } else {
249
+ setAwaitingInput(false);
250
+ }
251
  } catch (err) {
252
+ const e = String(err);
253
+ setOutput(e);
254
+ appendTerminal(e);
255
+ setAwaitingInput(false);
256
  } finally {
257
  setIsRunning(false);
258
  }
259
  };
260
 
261
+ // Called when user types input into terminal and presses Enter
262
+ const sendTerminalInput = async () => {
263
+ if (!awaitingInput) return;
264
+ const userText = terminalInput;
265
+ // echo user input in terminal
266
+ appendTerminal(`> ${userText}`);
267
+ const newAccum = (accumStdin || "") + userText + "\n";
268
+ setAccumStdin(newAccum);
269
+ setTerminalInput("");
270
+ setIsRunning(true);
271
+ try {
272
+ // re-run program with updated stdin
273
+ const node = getNodeByPath(tree, activePath);
274
+ if (!node || node.type !== "file") {
275
+ appendTerminal("No file selected.");
276
+ setAwaitingInput(false);
277
+ return;
278
+ }
279
+ const selectedLang = LANGUAGE_OPTIONS.find((l) => node.name.endsWith(l.ext))?.id;
280
+ const res = await runCode(node.content, selectedLang, newAccum);
281
+ const out = res.output ?? "";
282
+ setOutput(out);
283
+ appendTerminal(out);
284
+ setProblems(res.error ? parseProblems(res.output) : []);
285
+ // detect if program still waiting
286
+ if (outputLooksForInput(out)) {
287
+ setAwaitingInput(true);
288
+ } else {
289
+ setAwaitingInput(false);
290
+ }
291
+ } catch (err) {
292
+ appendTerminal(String(err));
293
+ setAwaitingInput(false);
294
+ } finally {
295
+ setIsRunning(false);
296
+ }
297
+ };
298
+
299
+ // Allow pressing Enter to send input
300
+ const onTerminalKeyDown = (e) => {
301
+ if (e.key === "Enter") {
302
+ e.preventDefault();
303
+ if (!awaitingInput) return;
304
+ sendTerminalInput();
305
+ }
306
+ };
307
+
308
+ // ---------- Agent functions ----------
309
  const handleAskFix = async () => {
310
  const node = getNodeByPath(tree, activePath);
311
  if (!node || node.type !== "file") {
 
430
 
431
  <div className="ide-menubar-right">
432
  <button onClick={handleSearchToggle} disabled={anyLoading}>πŸ” Search</button>
433
+ <button onClick={handleRun} disabled={isRunning || anyLoading}>{isRunning ? "⏳ Running..." : "β–Ά Run"}</button>
434
+ <button onClick={handleAskFix} disabled={isFixing || anyLoading}>{isFixing ? "⏳ Fixing..." : "πŸ€– Fix"}</button>
435
+ <button onClick={handleExplainSelection} disabled={isExplaining || anyLoading}>{isExplaining ? "⏳ Explaining..." : "πŸ“– Explain"}</button>
 
 
 
 
 
 
 
 
 
 
436
  <button onClick={() => setTheme((t) => (t === "vs-dark" ? "light" : "vs-dark"))} disabled={anyLoading}>
437
  {theme === "vs-dark" ? "β˜€οΈ" : "πŸŒ™"}
438
  </button>
 
468
  onChange={updateActiveFileContent}
469
  onMount={(editor) => (editorRef.current = editor)}
470
  onBlur={() => fetchAiSuggestions(currentNode?.content)}
471
+ options={{ minimap: { enabled: true }, fontSize: 14, scrollBeyondLastLine: false }}
 
 
 
 
472
  />
473
  </div>
474
 
 
483
 
484
  {/* Bottom panels */}
485
  <div className="ide-panels">
486
+ {/* Terminal output */}
487
+ <div className="ide-output-terminal" style={{ marginBottom: 8 }}>
488
+ <div style={{ fontSize: 12, color: "#ccc", marginBottom: 6 }}>Terminal</div>
489
+ <div className="terminal-content" style={{ background: "#000", color: "#0f0", padding: 8, borderRadius: 6, maxHeight: 220, overflowY: "auto", whiteSpace: "pre-wrap", fontFamily: "Consolas, monospace" }}>
490
+ {terminalLines.length === 0 ? <div style={{ color: "#999" }}>[Program output will appear here]</div> : terminalLines.map((ln, i) => <div key={i}>{ln}</div>)}
491
+ {awaitingInput && <div style={{ color: "#fff" }}>&nbsp;</div>}
492
+ </div>
493
 
494
+ {/* Terminal input (shown only when program asks input) */}
495
+ {awaitingInput ? (
496
+ <div style={{ display: "flex", gap: 6, marginTop: 6 }}>
497
+ <input
498
+ className="ide-input-box"
499
+ placeholder="Type input and press Enter..."
500
+ value={terminalInput}
501
+ onChange={(e) => setTerminalInput(e.target.value)}
502
+ onKeyDown={onTerminalKeyDown}
503
+ disabled={!awaitingInput || isRunning}
504
+ />
505
+ <button onClick={sendTerminalInput} disabled={!awaitingInput || isRunning} className="ide-button">Send</button>
506
+ </div>
507
+ ) : (
508
+ // If not awaiting input, show legacy single-run input + hint to run for interactive programs
509
+ <div style={{ display: "flex", gap: 6, marginTop: 6 }}>
510
+ <input className="ide-input-box" placeholder="(Optional) Program input for single-run" value={stdin} onChange={(e) => setStdin(e.target.value)} />
511
+ <div style={{ alignSelf: "center", color: "#999", fontSize: 12 }}>Press Run β†’ to execute</div>
512
+ </div>
513
+ )}
514
+ </div>
515
 
516
+ {/* Problems */}
517
  {problems.length > 0 && (
518
  <div className="ide-problems-panel">
519
  <div>🚨 Problems ({problems.length})</div>
520
+ {problems.map((p, i) => <div key={i}>{p.path}:{p.line} β€” {p.message}</div>)}
 
 
 
 
521
  </div>
522
  )}
523
  </div>