FrederickSundeep commited on
Commit
ed21b24
·
1 Parent(s): 05cc4a5

commit initial 09-12-2025 40

Browse files
Files changed (1) hide show
  1. src/App.js +152 -252
src/App.js CHANGED
@@ -1,4 +1,4 @@
1
- // App.js (complete updated file)
2
  import { useState, useEffect, useRef } from "react";
3
  import Editor from "@monaco-editor/react";
4
  import { askAgent } from "./agent/assistant";
@@ -18,7 +18,7 @@ import { downloadProjectZip } from "./zipExport";
18
  import { parseProblems } from "./problemParser";
19
  import "./App.css";
20
  import "xterm/css/xterm.css";
21
- import XTerm from "./Terminal"; // keep your existing XTerm wrapper
22
 
23
  // =================== SUPPORTED LANGUAGES ===================
24
  const LANGUAGE_OPTIONS = [
@@ -36,7 +36,7 @@ const LANGUAGE_OPTIONS = [
36
  const RUNNABLE_LANGS = ["python", "javascript", "java"];
37
 
38
  // =================== Heuristics ===================
39
- // output patterns that indicate the program is waiting for console input
40
  function outputLooksForInput(output) {
41
  if (!output) return false;
42
  const o = output.toString();
@@ -52,40 +52,35 @@ function outputLooksForInput(output) {
52
  /awaiting input/i,
53
  /provide input/i,
54
  /stdin/i,
 
55
  ];
56
  return patterns.some((p) => p.test(o));
57
  }
58
 
59
- // code-level heuristics to detect input() or equivalent calls
60
  function codeNeedsInput(code, langId) {
61
  if (!code) return false;
62
  try {
63
  const c = code.toString();
64
- // Python
65
  if (langId === "python") {
66
- // input(), sys.stdin.read(), sys.stdin.readline()
67
  if (/\binput\s*\(/i.test(c)) return true;
68
  if (/\bsys\.stdin\.(read|readline|readlines)\s*\(/i.test(c)) return true;
69
- if (/\braw_input\s*\(/i.test(c)) return true; // py2
70
  }
71
- // Java
72
  if (langId === "java") {
73
  if (/\bScanner\s*\(/i.test(c)) return true;
74
  if (/\bBufferedReader\b.*readLine/i.test(c)) return true;
75
  if (/\bSystem\.console\(\)/i.test(c)) return true;
76
  if (/\bnext(Int|Line|Double|)\b/i.test(c)) return true;
77
  }
78
- // JavaScript/Node
79
  if (langId === "javascript") {
80
  if (/process\.stdin|readline|readlineSync|prompt\(|require\(['"]readline['"]\)/i.test(c)) return true;
81
  }
82
- // C/C++/C: scanf / cin
83
  if (langId === "cpp" || langId === "c") {
84
  if (/\bscanf\s*\(/i.test(c)) return true;
85
  if (/\bstd::cin\b|cin\s*>>/i.test(c)) return true;
86
  if (/\bgets?\s*\(/i.test(c)) return true;
87
  }
88
- // Generic fallback
89
  if (/\binput\b|\bscanf\b|\bscanf_s\b|\bcin\b|\bScanner\b|readLine|readline/i.test(c)) return true;
90
  return false;
91
  } catch {
@@ -100,49 +95,45 @@ function focusXtermHelper() {
100
  if (ta) {
101
  try {
102
  ta.focus();
103
- // move caret to end
104
  const len = ta.value?.length ?? 0;
105
  ta.setSelectionRange(len, len);
106
  } catch {}
107
  } else {
108
- // Fallback: focus container to encourage user to click
109
  const cont = document.getElementById("terminal-container");
110
  if (cont) cont.focus();
111
  }
112
- }, 120); // small delay to ensure xterm is ready
113
  }
114
 
115
  // =================== APP ===================
116
  function App() {
 
117
  const [tree, setTree] = useState(loadTree());
118
  const [activePath, setActivePath] = useState("main.py");
119
 
120
- // Terminal state
121
- const [accumStdin, setAccumStdin] = useState("");
122
  const [awaitingInput, setAwaitingInput] = useState(false);
123
- const [output, setOutput] = useState("");
 
 
 
 
124
  const [prompt, setPrompt] = useState("");
125
  const [explanation, setExplanation] = useState("");
126
- const [terminalLines, setTerminalLines] = useState([]);
127
- // legacy small-input & helpers (kept for convenience)
128
- const [terminalInput, setTerminalInput] = useState("");
129
- const [stdin, setStdin] = useState("");
130
  const [problems, setProblems] = useState([]);
131
  const [theme, setTheme] = useState("vs-dark");
132
  const [searchOpen, setSearchOpen] = useState(false);
133
  const [searchQuery, setSearchQuery] = useState("");
134
  const [aiSuggestions, setAiSuggestions] = useState([]);
135
  const [contextMenu, setContextMenu] = useState(null);
136
- const editorRef = useRef(null);
137
- const fileInputRef = useRef(null);
138
-
139
- // loading flags
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);
@@ -153,7 +144,7 @@ const [interactivePromptShown, setInteractivePromptShown] = useState(false);
153
  LANGUAGE_OPTIONS.find((l) => currentNode?.name?.endsWith(l.ext)) ||
154
  LANGUAGE_OPTIONS[0];
155
 
156
- // ---------- File/Folder helpers (unchanged) ----------
157
  const collectFolderPaths = (node, acc = []) => {
158
  if (!node) return acc;
159
  if (node.type === "folder") acc.push(node.path || "");
@@ -164,7 +155,6 @@ const [interactivePromptShown, setInteractivePromptShown] = useState(false);
164
  const handleNewFile = () => {
165
  const filename = window.prompt("Filename (with extension):", "untitled.js");
166
  if (!filename) return;
167
-
168
  const selected = getNodeByPath(tree, activePath);
169
  let parentPath = "";
170
  if (selected?.type === "folder") parentPath = selected.path;
@@ -172,7 +162,6 @@ const [interactivePromptShown, setInteractivePromptShown] = useState(false);
172
  const parts = selected.path.split("/").slice(0, -1);
173
  parentPath = parts.join("/");
174
  }
175
-
176
  const folders = collectFolderPaths(tree);
177
  const suggestion = parentPath || folders[0] || "";
178
  const chosen = window.prompt(
@@ -180,10 +169,8 @@ const [interactivePromptShown, setInteractivePromptShown] = useState(false);
180
  suggestion
181
  );
182
  const targetParent = chosen == null ? parentPath : (chosen.trim() || "");
183
-
184
  const updated = addFile(tree, filename, targetParent);
185
  setTree(updated);
186
-
187
  const newPath = (targetParent ? targetParent + "/" : "") + filename;
188
  setActivePath(newPath);
189
  };
@@ -205,7 +192,6 @@ const [interactivePromptShown, setInteractivePromptShown] = useState(false);
205
  if (!newName || newName === node.name) return;
206
  const updated = renameNode(tree, activePath, newName);
207
  setTree(updated);
208
-
209
  const parts = activePath.split("/");
210
  parts.pop();
211
  const parent = parts.join("/");
@@ -217,7 +203,6 @@ const [interactivePromptShown, setInteractivePromptShown] = useState(false);
217
  if (!activePath) return;
218
  const node = getNodeByPath(tree, activePath);
219
  if (!node) return;
220
-
221
  if (node.type === "folder" && node.children?.length > 0) {
222
  const ok = window.confirm(`Folder "${node.name}" has ${node.children.length} items. Delete anyway?`);
223
  if (!ok) return;
@@ -225,7 +210,6 @@ const [interactivePromptShown, setInteractivePromptShown] = useState(false);
225
  const ok = window.confirm(`Delete "${node.name}"?`);
226
  if (!ok) return;
227
  }
228
-
229
  const updated = deleteNode(tree, activePath);
230
  setTree(updated);
231
  setActivePath("");
@@ -241,19 +225,15 @@ const [interactivePromptShown, setInteractivePromptShown] = useState(false);
241
  a.click();
242
  };
243
 
244
- // import file
245
  const handleImportFileClick = () => fileInputRef.current?.click();
246
-
247
  const handleFileInputChange = async (e) => {
248
  const f = e.target.files?.[0];
249
  if (!f) return;
250
  const text = await f.text();
251
-
252
  const selected = getNodeByPath(tree, activePath);
253
  let parentPath = "";
254
  if (selected?.type === "folder") parentPath = selected.path;
255
  else if (selected?.type === "file") parentPath = selected.path.split("/").slice(0, -1).join("");
256
-
257
  const updated = addFile(tree, f.name, parentPath);
258
  const newPath = (parentPath ? parentPath + "/" : "") + f.name;
259
  const finalTree = updateFileContent(updated, newPath, text);
@@ -263,181 +243,144 @@ const [interactivePromptShown, setInteractivePromptShown] = useState(false);
263
  };
264
 
265
  // ---------- Terminal helpers ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  const resetTerminal = (keepAccum = false) => {
 
267
  setOutput("");
268
  if (!keepAccum) {
269
  setAccumStdin("");
270
  }
271
  setAwaitingInput(false);
272
- setTerminalInput("");
273
  };
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 — keep accumStdin so input can be appended
311
- setAwaitingInput(true);
312
- focusXtermHelper();
313
- } else {
314
- // program finished — clear accumulated stdin so next Run asks fresh
315
  setAwaitingInput(false);
316
- setAccumStdin("");
317
  }
318
- } catch (err) {
319
- setOutput(String(err));
320
- // on error, allow user to continue the interactive session
321
- setAwaitingInput(true);
322
- } finally {
323
- setIsRunning(false);
324
- }
325
- };
326
-
327
- // append a line to the terminal
328
- const appendTerminal = (text) => {
329
- setTerminalLines((prev) => [...prev, text]);
330
- };
331
-
332
- // Initial Run: do NOT run if code needs interactive input and there's no accumulated stdin.
333
- // Replace your existing handleRun with this version.
334
- // This version clears accumulated stdin at the start so "Run" asks fresh input every time.
335
- // Replace your existing handleRun with this
336
- const handleRun = async () => {
337
- const node = getNodeByPath(tree, activePath);
338
- if (!node || node.type !== "file") {
339
- setOutput("Select a file to run.");
340
- return;
341
- }
342
-
343
- const selectedLang = LANGUAGE_OPTIONS.find((l) =>
344
- node.name.endsWith(l.ext)
345
- )?.id;
346
- if (!selectedLang || !RUNNABLE_LANGS.includes(selectedLang)) {
347
- setOutput(`⚠️ Run not supported for this file type.`);
348
- return;
349
- }
350
 
351
- // --- FORCE a fresh run: clear any previous accumulated stdin and terminal ---
352
- setAccumStdin("");
353
- resetTerminal(false); // clear terminalLines + terminalInput and also clears accumStdin because keepAccum=false
354
- setAwaitingInput(false);
355
- setInteractivePromptShown(false);
356
-
357
- // detect whether the code likely needs interactive input
358
- const needs = codeNeedsInput(node.content, selectedLang);
359
-
360
- // If code needs input, show a clear interactive message (do NOT run yet)
361
- if (needs) {
362
- appendTerminal("[Interactive program detected — type input directly into the terminal]");
363
- setAwaitingInput(true);
364
- setInteractivePromptShown(true);
365
- focusXtermHelper();
366
- return; // wait for user's first input (avoids EOFError)
367
- }
368
-
369
- // If not interactive, run immediately with empty stdin
370
- const stdinToSend = ""; // fresh run uses no previous input
371
-
372
- appendTerminal(`[Running (fresh) with stdin length=${stdinToSend.length}]`);
373
- setIsRunning(true);
374
- setProblems([]);
375
- try {
376
- const res = await runCode(node.content, selectedLang, stdinToSend);
377
- const out = res.output ?? "";
378
- setOutput(out);
379
- appendTerminal(out);
380
- setProblems(res.error ? parseProblems(res.output) : []);
381
-
382
- // If output suggests further input, enter interactive mode (rare since we checked 'needs', but handle anyway)
383
- if (outputLooksForInput(out)) {
384
- setAwaitingInput(true);
385
- focusXtermHelper();
386
- } else {
387
  setAwaitingInput(false);
388
- setInteractivePromptShown(false);
389
- setAccumStdin(""); // ensure accum cleared after finish
390
  }
391
- } catch (err) {
392
- const errStr = String(err);
393
- setOutput(errStr);
394
- appendTerminal(errStr);
395
- // if error looks like EOF or program expects input, enable interactive mode
396
- setAwaitingInput(true);
397
- setInteractivePromptShown(true);
398
- focusXtermHelper();
399
- } finally {
400
- setIsRunning(false);
401
- }
402
- };
403
-
404
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
 
 
 
 
 
 
 
 
406
 
407
- // Send from the small input box: delegate to runCodeWithUpdatedInput to keep behavior uniform
408
- const sendTerminalInput = async () => {
409
- const line = (terminalInput || "").replace(/\r$/, "");
410
- if (!line) {
411
- // If there is no typed small-input but interactivePromptShown is true, just focus terminal
412
- if (interactivePromptShown) {
413
- focusXtermHelper();
414
  }
415
- return;
416
- }
417
 
418
- // show echo in output
419
- setOutput((prev) => (prev ? prev + `\n> ${line}` : `> ${line}`));
420
- setTerminalInput("");
 
 
421
 
422
- // Use the unified runner that appends input and runs
423
- await runCodeWithUpdatedInput(line);
424
- };
425
 
 
 
 
 
 
 
 
426
 
427
- // handle press Enter for small input
428
- const onTerminalKeyDown = (e) => {
429
- if (e.key === "Enter") {
430
- e.preventDefault();
431
- if (!awaitingInput) return;
432
- sendTerminalInput();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
  }
434
  };
435
 
436
- // ---------- Agent functions (unchanged) ----------
437
  const handleAskFix = async () => {
438
  const node = getNodeByPath(tree, activePath);
439
  if (!node || node.type !== "file") {
440
- setOutput("Select a file to apply fix.");
441
  return;
442
  }
443
  setIsFixing(true);
@@ -448,8 +391,9 @@ const handleRun = async () => {
448
  );
449
  const updatedTree = updateFileContent(tree, node.path, reply);
450
  setTree(updatedTree);
 
451
  } catch (err) {
452
- setOutput(String(err));
453
  } finally {
454
  setIsFixing(false);
455
  }
@@ -494,7 +438,6 @@ const handleRun = async () => {
494
 
495
  // ---------- Search ----------
496
  const handleSearchToggle = () => setSearchOpen(!searchOpen);
497
-
498
  const handleSearchNow = () => {
499
  if (!searchQuery) return;
500
  const results = searchTree(tree, searchQuery);
@@ -535,9 +478,12 @@ const handleRun = async () => {
535
 
536
  const anyLoading = isRunning || isFixing || isExplaining;
537
 
538
- // JSX
539
  return (
540
- <div className={`ide-root ${theme === "vs-dark" ? "ide-dark" : "ide-light"}`}>
 
 
 
541
  <input ref={fileInputRef} id="file-import-input" type="file" style={{ display: "none" }} onChange={handleFileInputChange} />
542
 
543
  <div className="ide-menubar">
@@ -557,14 +503,15 @@ const handleRun = async () => {
557
  <button onClick={handleSearchToggle} disabled={anyLoading}>🔍 Search</button>
558
  <button onClick={handleRun} disabled={isRunning || anyLoading}>{isRunning ? "⏳ Running..." : "▶ Run"}</button>
559
  <button onClick={handleAskFix} disabled={isFixing || anyLoading}>{isFixing ? "⏳ Fixing..." : "🤖 Fix"}</button>
560
- <button onClick={handleExplainSelection} disabled={isExplaining || anyLoading}>{isExplaining ? "⏳ Explaining..." : "📖 Explain"}</button>
561
  <button onClick={() => setTheme((t) => (t === "vs-dark" ? "light" : "vs-dark"))} disabled={anyLoading}>
562
  {theme === "vs-dark" ? "☀️" : "🌙"}
563
  </button>
 
564
  </div>
565
  </div>
566
 
567
- {anyLoading && (
568
  <div className="ide-progress-wrap">
569
  <div className="ide-progress" />
570
  </div>
@@ -605,78 +552,31 @@ const handleRun = async () => {
605
  <div style={{ marginBottom: 8 }}>
606
  <div style={{ fontSize: 12, color: "#ccc", marginBottom: 6 }}>Terminal</div>
607
 
608
- <XTerm
609
- output={output}
610
- onData={(line) => {
611
- // line is a clean string (no CR)
612
- const trimmed = (line || "").replace(/\r$/, "");
613
- if (!trimmed && !awaitingInput) {
614
- // nothing typed
615
- return;
616
- }
617
- // Always delegate to the unified runner which appends to accumulated stdin
618
- runCodeWithUpdatedInput(trimmed);
619
- }}
620
- />
621
-
622
 
623
- {/* When interactive program detected and waiting for user input */}
624
  {awaitingInput && (
625
  <div style={{ marginTop: 8, padding: 8, background: "#252526", border: "1px solid #333", borderRadius: 6 }}>
626
  <div style={{ marginBottom: 6, color: "#ddd" }}>
627
  This program appears to be interactive and requires console input.
628
  </div>
629
  <div style={{ display: "flex", gap: 8 }}>
630
- <button
631
- onClick={() => {
632
- focusXtermHelper();
633
- // ensure terminal is ready to accept typed input
634
- }}
635
- className="ide-button"
636
- >
637
- ▶ Focus Terminal
638
- </button>
639
- <button
640
- onClick={() => {
641
- // start interactive session: clear output but keep accum
642
- resetTerminal(true);
643
- setOutput("[Interactive session started — type into the terminal]");
644
- setAwaitingInput(true);
645
- focusXtermHelper();
646
- }}
647
- className="ide-button"
648
- >
649
- ▶ Start interactive session
650
- </button>
651
- <div style={{ color: "#999", alignSelf: "center" }}>Type & press Enter in terminal or use Send below.</div>
652
  </div>
653
  </div>
654
  )}
655
-
656
- {/* If not awaiting input show legacy small input for single-run */}
657
- {!awaitingInput && (
658
- <div style={{ display: "flex", gap: 6, marginTop: 6 }}>
659
- <input className="ide-input-box" placeholder="(Optional) Program input for single-run" value={stdin} onChange={(e) => setStdin(e.target.value)} />
660
- <div style={{ alignSelf: "center", color: "#999", fontSize: 12 }}>Press Run → to execute</div>
661
- </div>
662
- )}
663
-
664
- {/* Always show small input/send option so users can type without xterm if they prefer */}
665
- <div style={{ display: "flex", gap: 6, marginTop: 8 }}>
666
- <input
667
- className="ide-input-box"
668
- placeholder={awaitingInput ? "Type input and press Send (or press Enter in terminal)" : "Optional input (press Send to append and run)"}
669
- value={terminalInput}
670
- onChange={(e) => setTerminalInput(e.target.value)}
671
- onKeyDown={(e) => {
672
- if (e.key === "Enter") {
673
- e.preventDefault();
674
- sendTerminalInput();
675
- }
676
- }}
677
- />
678
- <button onClick={sendTerminalInput} className="ide-button" disabled={isRunning}>{isRunning ? "⏳" : "Send"}</button>
679
- </div>
680
  </div>
681
 
682
  {problems.length > 0 && (
 
1
+ // src/App.js
2
  import { useState, useEffect, useRef } from "react";
3
  import Editor from "@monaco-editor/react";
4
  import { askAgent } from "./agent/assistant";
 
18
  import { parseProblems } from "./problemParser";
19
  import "./App.css";
20
  import "xterm/css/xterm.css";
21
+ import XTerm from "./Terminal"; // your existing wrapper
22
 
23
  // =================== SUPPORTED LANGUAGES ===================
24
  const LANGUAGE_OPTIONS = [
 
36
  const RUNNABLE_LANGS = ["python", "javascript", "java"];
37
 
38
  // =================== Heuristics ===================
39
+ // patterns that indicate program is waiting for input
40
  function outputLooksForInput(output) {
41
  if (!output) return false;
42
  const o = output.toString();
 
52
  /awaiting input/i,
53
  /provide input/i,
54
  /stdin/i,
55
+ /enter a value/i,
56
  ];
57
  return patterns.some((p) => p.test(o));
58
  }
59
 
60
+ // code-level heuristics to detect input calls
61
  function codeNeedsInput(code, langId) {
62
  if (!code) return false;
63
  try {
64
  const c = code.toString();
 
65
  if (langId === "python") {
 
66
  if (/\binput\s*\(/i.test(c)) return true;
67
  if (/\bsys\.stdin\.(read|readline|readlines)\s*\(/i.test(c)) return true;
68
+ if (/\braw_input\s*\(/i.test(c)) return true;
69
  }
 
70
  if (langId === "java") {
71
  if (/\bScanner\s*\(/i.test(c)) return true;
72
  if (/\bBufferedReader\b.*readLine/i.test(c)) return true;
73
  if (/\bSystem\.console\(\)/i.test(c)) return true;
74
  if (/\bnext(Int|Line|Double|)\b/i.test(c)) return true;
75
  }
 
76
  if (langId === "javascript") {
77
  if (/process\.stdin|readline|readlineSync|prompt\(|require\(['"]readline['"]\)/i.test(c)) return true;
78
  }
 
79
  if (langId === "cpp" || langId === "c") {
80
  if (/\bscanf\s*\(/i.test(c)) return true;
81
  if (/\bstd::cin\b|cin\s*>>/i.test(c)) return true;
82
  if (/\bgets?\s*\(/i.test(c)) return true;
83
  }
 
84
  if (/\binput\b|\bscanf\b|\bscanf_s\b|\bcin\b|\bScanner\b|readLine|readline/i.test(c)) return true;
85
  return false;
86
  } catch {
 
95
  if (ta) {
96
  try {
97
  ta.focus();
 
98
  const len = ta.value?.length ?? 0;
99
  ta.setSelectionRange(len, len);
100
  } catch {}
101
  } else {
 
102
  const cont = document.getElementById("terminal-container");
103
  if (cont) cont.focus();
104
  }
105
+ }, 120);
106
  }
107
 
108
  // =================== APP ===================
109
  function App() {
110
+ // ----- file tree + selection -----
111
  const [tree, setTree] = useState(loadTree());
112
  const [activePath, setActivePath] = useState("main.py");
113
 
114
+ // ----- terminal / interactive state -----
115
+ const [accumStdin, setAccumStdin] = useState(""); // accumulated input for interactive runs
116
  const [awaitingInput, setAwaitingInput] = useState(false);
117
+ const [terminalLines, setTerminalLines] = useState([]); // visible lines in terminal area
118
+ const [output, setOutput] = useState(""); // "write" prop for XTerm (Terminal component picks this up)
119
+ const [interactivePromptShown, setInteractivePromptShown] = useState(false);
120
+
121
+ // ----- AI + editor state -----
122
  const [prompt, setPrompt] = useState("");
123
  const [explanation, setExplanation] = useState("");
 
 
 
 
124
  const [problems, setProblems] = useState([]);
125
  const [theme, setTheme] = useState("vs-dark");
126
  const [searchOpen, setSearchOpen] = useState(false);
127
  const [searchQuery, setSearchQuery] = useState("");
128
  const [aiSuggestions, setAiSuggestions] = useState([]);
129
  const [contextMenu, setContextMenu] = useState(null);
 
 
 
 
130
  const [isRunning, setIsRunning] = useState(false);
131
  const [isFixing, setIsFixing] = useState(false);
132
  const [isExplaining, setIsExplaining] = useState(false);
 
 
133
 
134
+ // refs & helpers
135
+ const editorRef = useRef(null);
136
+ const fileInputRef = useRef(null);
137
 
138
  useEffect(() => {
139
  saveTree(tree);
 
144
  LANGUAGE_OPTIONS.find((l) => currentNode?.name?.endsWith(l.ext)) ||
145
  LANGUAGE_OPTIONS[0];
146
 
147
+ // ---------- File / Folder actions ----------
148
  const collectFolderPaths = (node, acc = []) => {
149
  if (!node) return acc;
150
  if (node.type === "folder") acc.push(node.path || "");
 
155
  const handleNewFile = () => {
156
  const filename = window.prompt("Filename (with extension):", "untitled.js");
157
  if (!filename) return;
 
158
  const selected = getNodeByPath(tree, activePath);
159
  let parentPath = "";
160
  if (selected?.type === "folder") parentPath = selected.path;
 
162
  const parts = selected.path.split("/").slice(0, -1);
163
  parentPath = parts.join("/");
164
  }
 
165
  const folders = collectFolderPaths(tree);
166
  const suggestion = parentPath || folders[0] || "";
167
  const chosen = window.prompt(
 
169
  suggestion
170
  );
171
  const targetParent = chosen == null ? parentPath : (chosen.trim() || "");
 
172
  const updated = addFile(tree, filename, targetParent);
173
  setTree(updated);
 
174
  const newPath = (targetParent ? targetParent + "/" : "") + filename;
175
  setActivePath(newPath);
176
  };
 
192
  if (!newName || newName === node.name) return;
193
  const updated = renameNode(tree, activePath, newName);
194
  setTree(updated);
 
195
  const parts = activePath.split("/");
196
  parts.pop();
197
  const parent = parts.join("/");
 
203
  if (!activePath) return;
204
  const node = getNodeByPath(tree, activePath);
205
  if (!node) return;
 
206
  if (node.type === "folder" && node.children?.length > 0) {
207
  const ok = window.confirm(`Folder "${node.name}" has ${node.children.length} items. Delete anyway?`);
208
  if (!ok) return;
 
210
  const ok = window.confirm(`Delete "${node.name}"?`);
211
  if (!ok) return;
212
  }
 
213
  const updated = deleteNode(tree, activePath);
214
  setTree(updated);
215
  setActivePath("");
 
225
  a.click();
226
  };
227
 
 
228
  const handleImportFileClick = () => fileInputRef.current?.click();
 
229
  const handleFileInputChange = async (e) => {
230
  const f = e.target.files?.[0];
231
  if (!f) return;
232
  const text = await f.text();
 
233
  const selected = getNodeByPath(tree, activePath);
234
  let parentPath = "";
235
  if (selected?.type === "folder") parentPath = selected.path;
236
  else if (selected?.type === "file") parentPath = selected.path.split("/").slice(0, -1).join("");
 
237
  const updated = addFile(tree, f.name, parentPath);
238
  const newPath = (parentPath ? parentPath + "/" : "") + f.name;
239
  const finalTree = updateFileContent(updated, newPath, text);
 
243
  };
244
 
245
  // ---------- Terminal helpers ----------
246
+ const appendTerminal = (text) => {
247
+ // push to visible lines and set `output` (which Terminal writes)
248
+ setTerminalLines((prev) => {
249
+ const next = [...prev, text];
250
+ // also keep the XTerm single-output prop to trigger Terminal.writeln
251
+ setOutput(text);
252
+ return next;
253
+ });
254
+ };
255
+
256
+ const clearTerminal = () => {
257
+ // ANSI sequence to clear screen + move cursor home (xterm will honor)
258
+ setTerminalLines([]);
259
+ setOutput("\x1b[2J\x1b[H");
260
+ setAccumStdin("");
261
+ setAwaitingInput(false);
262
+ setInteractivePromptShown(false);
263
+ };
264
+
265
  const resetTerminal = (keepAccum = false) => {
266
+ setTerminalLines([]);
267
  setOutput("");
268
  if (!keepAccum) {
269
  setAccumStdin("");
270
  }
271
  setAwaitingInput(false);
272
+ setInteractivePromptShown(false);
273
  };
274
 
275
+ // Unified runner used when terminal provides input (or small input)
276
  const runCodeWithUpdatedInput = async (inputLine) => {
277
+ if (typeof inputLine !== "string") inputLine = String(inputLine || "");
278
+ const trimmed = inputLine.replace(/\r$/, "");
279
+ // if user pressed Enter with empty line and no accum, ignore
280
+ if (trimmed.length === 0 && !accumStdin) {
281
+ return;
282
+ }
 
 
 
 
 
 
 
 
 
 
 
 
283
 
284
+ // append newline like console
285
+ const newAccum = (accumStdin || "") + trimmed + "\n";
286
+ setAccumStdin(newAccum);
287
+ setInteractivePromptShown(false);
 
 
288
 
289
+ const node = getNodeByPath(tree, activePath);
290
+ if (!node || node.type !== "file") {
291
+ appendTerminal("[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
+ appendTerminal(`[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
+ if (out) appendTerminal(out);
308
+ setProblems(res.error ? parseProblems(res.output) : []);
309
+ if (outputLooksForInput(out)) {
310
+ setAwaitingInput(true);
311
+ focusXtermHelper();
312
+ } else {
313
+ setAwaitingInput(false);
314
+ setAccumStdin(""); // finished -> clear accumulated input so next Run is fresh
315
+ }
316
+ } catch (err) {
317
+ appendTerminal(String(err));
318
+ setAwaitingInput(true);
319
+ } finally {
320
+ setIsRunning(false);
321
+ }
322
+ };
323
 
324
+ // ---------- Initial Run handler (fresh runs) ----------
325
+ const handleRun = async () => {
326
+ const node = getNodeByPath(tree, activePath);
327
+ if (!node || node.type !== "file") {
328
+ appendTerminal("Select a file to run.");
329
+ return;
330
+ }
331
 
332
+ const selectedLang = LANGUAGE_OPTIONS.find((l) => node.name.endsWith(l.ext))?.id;
333
+ if (!selectedLang || !RUNNABLE_LANGS.includes(selectedLang)) {
334
+ appendTerminal(`⚠️ Run not supported for this file type.`);
335
+ return;
 
 
 
336
  }
 
 
337
 
338
+ // Force fresh run: clear accumulated input and terminal
339
+ setAccumStdin("");
340
+ resetTerminal(false);
341
+ setAwaitingInput(false);
342
+ setInteractivePromptShown(false);
343
 
344
+ const needs = codeNeedsInput(node.content, selectedLang);
 
 
345
 
346
+ if (needs) {
347
+ appendTerminal("[Interactive program detected — type input directly into the terminal]");
348
+ setAwaitingInput(true);
349
+ setInteractivePromptShown(true);
350
+ focusXtermHelper();
351
+ return; // wait for user's input to avoid EOFError
352
+ }
353
 
354
+ // Non-interactive: run immediately with empty stdin
355
+ appendTerminal(`[Running (fresh)]`);
356
+ setIsRunning(true);
357
+ setProblems([]);
358
+ try {
359
+ const res = await runCode(node.content, selectedLang, "");
360
+ const out = res.output ?? "";
361
+ if (out) appendTerminal(out);
362
+ setProblems(res.error ? parseProblems(res.output) : []);
363
+ if (outputLooksForInput(out)) {
364
+ setAwaitingInput(true);
365
+ focusXtermHelper();
366
+ } else {
367
+ setAwaitingInput(false);
368
+ setAccumStdin("");
369
+ }
370
+ } catch (err) {
371
+ appendTerminal(String(err));
372
+ setAwaitingInput(true);
373
+ focusXtermHelper();
374
+ } finally {
375
+ setIsRunning(false);
376
  }
377
  };
378
 
379
+ // ---------- Agent functions ----------
380
  const handleAskFix = async () => {
381
  const node = getNodeByPath(tree, activePath);
382
  if (!node || node.type !== "file") {
383
+ appendTerminal("Select a file to apply fix.");
384
  return;
385
  }
386
  setIsFixing(true);
 
391
  );
392
  const updatedTree = updateFileContent(tree, node.path, reply);
393
  setTree(updatedTree);
394
+ appendTerminal("[AI] Applied fixes to file.");
395
  } catch (err) {
396
+ appendTerminal(String(err));
397
  } finally {
398
  setIsFixing(false);
399
  }
 
438
 
439
  // ---------- Search ----------
440
  const handleSearchToggle = () => setSearchOpen(!searchOpen);
 
441
  const handleSearchNow = () => {
442
  if (!searchQuery) return;
443
  const results = searchTree(tree, searchQuery);
 
478
 
479
  const anyLoading = isRunning || isFixing || isExplaining;
480
 
481
+ // ---------- JSX ----------
482
  return (
483
+ <div
484
+ className={`ide-root ${theme === "vs-dark" ? "ide-dark" : "ide-light"}`}
485
+ style={{ overflowX: "hidden" }} // prevent horizontal white gap / scroller issue
486
+ >
487
  <input ref={fileInputRef} id="file-import-input" type="file" style={{ display: "none" }} onChange={handleFileInputChange} />
488
 
489
  <div className="ide-menubar">
 
503
  <button onClick={handleSearchToggle} disabled={anyLoading}>🔍 Search</button>
504
  <button onClick={handleRun} disabled={isRunning || anyLoading}>{isRunning ? "⏳ Running..." : "▶ Run"}</button>
505
  <button onClick={handleAskFix} disabled={isFixing || anyLoading}>{isFixing ? "⏳ Fixing..." : "🤖 Fix"}</button>
506
+ <button onClick={handleExplainSelection} disabled={isExplaining || anyLoading}>{isExplaining ? "⏳ Explaining" : "📖 Explain"}</button>
507
  <button onClick={() => setTheme((t) => (t === "vs-dark" ? "light" : "vs-dark"))} disabled={anyLoading}>
508
  {theme === "vs-dark" ? "☀️" : "🌙"}
509
  </button>
510
+ <button onClick={clearTerminal} disabled={anyLoading} title="Clear terminal">🧹 Clear</button>
511
  </div>
512
  </div>
513
 
514
+ {(isRunning || isFixing || isExplaining) && (
515
  <div className="ide-progress-wrap">
516
  <div className="ide-progress" />
517
  </div>
 
552
  <div style={{ marginBottom: 8 }}>
553
  <div style={{ fontSize: 12, color: "#ccc", marginBottom: 6 }}>Terminal</div>
554
 
555
+ <XTerm
556
+ output={output}
557
+ onData={(line) => {
558
+ // line is the typed content (no CR), pass to unified runner
559
+ const trimmed = (line || "").replace(/\r$/, "");
560
+ if (!trimmed && !awaitingInput) {
561
+ // nothing typed and not expecting input
562
+ return;
563
+ }
564
+ runCodeWithUpdatedInput(trimmed);
565
+ }}
566
+ />
 
 
567
 
 
568
  {awaitingInput && (
569
  <div style={{ marginTop: 8, padding: 8, background: "#252526", border: "1px solid #333", borderRadius: 6 }}>
570
  <div style={{ marginBottom: 6, color: "#ddd" }}>
571
  This program appears to be interactive and requires console input.
572
  </div>
573
  <div style={{ display: "flex", gap: 8 }}>
574
+ <button onClick={() => { focusXtermHelper(); }} className="ide-button">▶ Focus Terminal</button>
575
+ <button onClick={() => { resetTerminal(true); appendTerminal("[Interactive session started — type into the terminal]"); setAwaitingInput(true); focusXtermHelper(); }} className="ide-button">▶ Start interactive session</button>
576
+ <div style={{ color: "#999", alignSelf: "center" }}>Type & press Enter in terminal.</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
  </div>
578
  </div>
579
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
  </div>
581
 
582
  {problems.length > 0 && (