FrederickSundeep commited on
Commit
47e3df3
·
1 Parent(s): 996fe90

commit initial 09-12-2025 31

Browse files
Files changed (1) hide show
  1. src/App.js +124 -67
src/App.js CHANGED
@@ -1,3 +1,4 @@
 
1
  import { useState, useEffect, useRef } from "react";
2
  import Editor from "@monaco-editor/react";
3
  import { askAgent } from "./agent/assistant";
@@ -17,7 +18,7 @@ import { downloadProjectZip } from "./zipExport";
17
  import { parseProblems } from "./problemParser";
18
  import "./App.css";
19
  import "xterm/css/xterm.css";
20
- import XTerm from "./Terminal"; // your xterm wrapper component
21
 
22
  // =================== SUPPORTED LANGUAGES ===================
23
  const LANGUAGE_OPTIONS = [
@@ -34,7 +35,8 @@ const LANGUAGE_OPTIONS = [
34
 
35
  const RUNNABLE_LANGS = ["python", "javascript", "java"];
36
 
37
- // Utility: detect whether output likely requests input
 
38
  function outputLooksForInput(output) {
39
  if (!output) return false;
40
  const o = output.toString();
@@ -47,30 +49,69 @@ function outputLooksForInput(output) {
47
  /: $/,
48
  /:\n$/,
49
  /> $/,
 
 
 
50
  ];
51
  return patterns.some((p) => p.test(o));
52
  }
53
 
54
- // Heuristic: detect input calls inside source
55
  function codeNeedsInput(code, langId) {
56
  if (!code) return false;
57
  try {
58
  const c = code.toString();
 
59
  if (langId === "python") {
60
- return /\binput\s*\(/i.test(c);
 
 
 
61
  }
 
62
  if (langId === "java") {
63
- return /\bScanner\s*\(|\bnext(Int|Line|Double)\b/i.test(c) || /\bSystem\.console\(\)/i.test(c);
 
 
 
64
  }
 
65
  if (langId === "javascript") {
66
- return /process\.stdin|readline|prompt\(|window\.prompt/i.test(c);
67
  }
68
- return /\binput\b|\bScanner\b|\bnextInt\b|prompt\(/i.test(c);
 
 
 
 
 
 
 
 
69
  } catch {
70
  return false;
71
  }
72
  }
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  // =================== APP ===================
75
  function App() {
76
  const [tree, setTree] = useState(loadTree());
@@ -83,7 +124,7 @@ function App() {
83
  const [prompt, setPrompt] = useState("");
84
  const [explanation, setExplanation] = useState("");
85
 
86
- // legacy and other UI
87
  const [terminalInput, setTerminalInput] = useState("");
88
  const [stdin, setStdin] = useState("");
89
  const [problems, setProblems] = useState([]);
@@ -109,7 +150,7 @@ function App() {
109
  LANGUAGE_OPTIONS.find((l) => currentNode?.name?.endsWith(l.ext)) ||
110
  LANGUAGE_OPTIONS[0];
111
 
112
- // ---------- TREE helpers ----------
113
  const collectFolderPaths = (node, acc = []) => {
114
  if (!node) return acc;
115
  if (node.type === "folder") acc.push(node.path || "");
@@ -117,12 +158,10 @@ function App() {
117
  return acc;
118
  };
119
 
120
- // ---------- File / Folder actions ----------
121
  const handleNewFile = () => {
122
  const filename = window.prompt("Filename (with extension):", "untitled.js");
123
  if (!filename) return;
124
 
125
- // Determine default parent
126
  const selected = getNodeByPath(tree, activePath);
127
  let parentPath = "";
128
  if (selected?.type === "folder") parentPath = selected.path;
@@ -230,8 +269,9 @@ function App() {
230
  setTerminalInput("");
231
  };
232
 
233
- // On-line input from XTerm -> append and re-run
234
  const runCodeWithUpdatedInput = async (inputLine) => {
 
235
  const newAccum = (accumStdin || "") + inputLine + "\n";
236
  setAccumStdin(newAccum);
237
 
@@ -257,6 +297,8 @@ function App() {
257
  setProblems(res.error ? parseProblems(res.output) : []);
258
  if (outputLooksForInput(out)) {
259
  setAwaitingInput(true);
 
 
260
  } else {
261
  setAwaitingInput(false);
262
  }
@@ -268,7 +310,7 @@ function App() {
268
  }
269
  };
270
 
271
- // Initial run DO NOT call run if code needs interactive input and we have no accumStdin.
272
  const handleRun = async () => {
273
  const node = getNodeByPath(tree, activePath);
274
  if (!node || node.type !== "file") {
@@ -281,22 +323,21 @@ function App() {
281
  return;
282
  }
283
 
284
- // If code likely needs input and we have no accumulated stdin, don't run — wait for user input in XTerm.
285
  const needs = codeNeedsInput(node.content, selectedLang);
286
  const haveAccum = !!(accumStdin && accumStdin.length > 0);
287
  const haveLegacyStdin = !!(stdin && stdin.length > 0);
288
 
289
  if (needs && !haveAccum && !haveLegacyStdin) {
290
- // Show helpful message in terminal and enable awaitingInput so user can type into XTerm.
291
- setOutput("[Interactive program detected — type input directly into the terminal]");
292
  setAwaitingInput(true);
293
- return; // <-- important: do NOT run now
 
294
  }
295
 
296
- // Otherwise run immediately with whichever stdin we have (accumStdin or legacy stdin)
297
  const stdinToSend = accumStdin || stdin || "";
298
-
299
- resetTerminal(true); // keep accumStdin
300
  setOutput(`[Running with stdin length=${stdinToSend ? stdinToSend.length : 0}]\n`);
301
  setIsRunning(true);
302
  setProblems([]);
@@ -307,53 +348,31 @@ function App() {
307
  setProblems(res.error ? parseProblems(res.output) : []);
308
  if (outputLooksForInput(out)) {
309
  setAwaitingInput(true);
 
310
  } else {
311
  setAwaitingInput(false);
312
  }
313
  } catch (err) {
314
- const e = String(err);
315
- setOutput(e);
316
- // Allow user to type into terminal after EOF errors
317
  setAwaitingInput(true);
 
318
  } finally {
319
  setIsRunning(false);
320
  }
321
  };
322
 
323
- // Legacy send from input box
324
  const sendTerminalInput = async () => {
325
- const node = getNodeByPath(tree, activePath);
326
- if (!node || node.type !== "file") {
327
- setOutput((prev) => prev + "\nNo file selected.");
328
- setAwaitingInput(false);
329
- return;
330
- }
331
-
332
- const userText = terminalInput;
333
- setOutput((prev) => prev + `\n> ${userText}`);
334
- const newAccum = (accumStdin || "") + userText + "\n";
335
- setAccumStdin(newAccum);
336
  setTerminalInput("");
337
- setIsRunning(true);
338
- try {
339
- const selectedLang = LANGUAGE_OPTIONS.find((l) => node.name.endsWith(l.ext))?.id;
340
- const res = await runCode(node.content, selectedLang, newAccum);
341
- const out = res.output ?? "";
342
- setOutput(out);
343
- setProblems(res.error ? parseProblems(res.output) : []);
344
- if (outputLooksForInput(out)) {
345
- setAwaitingInput(true);
346
- } else {
347
- setAwaitingInput(false);
348
- }
349
- } catch (err) {
350
- setOutput((prev) => prev + "\n" + String(err));
351
- setAwaitingInput(false);
352
- } finally {
353
- setIsRunning(false);
354
- }
355
  };
356
 
 
357
  const onTerminalKeyDown = (e) => {
358
  if (e.key === "Enter") {
359
  e.preventDefault();
@@ -362,7 +381,7 @@ function App() {
362
  }
363
  };
364
 
365
- // ---------- Agent functions ----------
366
  const handleAskFix = async () => {
367
  const node = getNodeByPath(tree, activePath);
368
  if (!node || node.type !== "file") {
@@ -537,10 +556,45 @@ function App() {
537
  <XTerm
538
  output={output}
539
  onData={(line) => {
 
540
  runCodeWithUpdatedInput(line);
541
  }}
542
  />
543
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  {!awaitingInput && (
545
  <div style={{ display: "flex", gap: 6, marginTop: 6 }}>
546
  <input className="ide-input-box" placeholder="(Optional) Program input for single-run" value={stdin} onChange={(e) => setStdin(e.target.value)} />
@@ -548,19 +602,22 @@ function App() {
548
  </div>
549
  )}
550
 
551
- {awaitingInput && (
552
- <div style={{ display: "flex", gap: 6, marginTop: 6 }}>
553
- <input
554
- className="ide-input-box"
555
- placeholder="Type input and press Send..."
556
- value={terminalInput}
557
- onChange={(e) => setTerminalInput(e.target.value)}
558
- onKeyDown={onTerminalKeyDown}
559
- disabled={!awaitingInput || isRunning}
560
- />
561
- <button onClick={sendTerminalInput} disabled={!awaitingInput || isRunning} className="ide-button">Send</button>
562
- </div>
563
- )}
 
 
 
564
  </div>
565
 
566
  {problems.length > 0 && (
 
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
  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 = [
 
35
 
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();
 
49
  /: $/,
50
  /:\n$/,
51
  /> $/,
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 {
92
  return false;
93
  }
94
  }
95
 
96
+ // Helper: focus xterm's hidden textarea (works with xterm.js default markup)
97
+ function focusXtermHelper() {
98
+ setTimeout(() => {
99
+ const ta = document.querySelector("#terminal-container .xterm-helper-textarea");
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());
 
124
  const [prompt, setPrompt] = useState("");
125
  const [explanation, setExplanation] = useState("");
126
 
127
+ // legacy small-input & helpers (kept for convenience)
128
  const [terminalInput, setTerminalInput] = useState("");
129
  const [stdin, setStdin] = useState("");
130
  const [problems, setProblems] = useState([]);
 
150
  LANGUAGE_OPTIONS.find((l) => currentNode?.name?.endsWith(l.ext)) ||
151
  LANGUAGE_OPTIONS[0];
152
 
153
+ // ---------- File/Folder helpers (unchanged) ----------
154
  const collectFolderPaths = (node, acc = []) => {
155
  if (!node) return acc;
156
  if (node.type === "folder") acc.push(node.path || "");
 
158
  return acc;
159
  };
160
 
 
161
  const handleNewFile = () => {
162
  const filename = window.prompt("Filename (with extension):", "untitled.js");
163
  if (!filename) return;
164
 
 
165
  const selected = getNodeByPath(tree, activePath);
166
  let parentPath = "";
167
  if (selected?.type === "folder") parentPath = selected.path;
 
269
  setTerminalInput("");
270
  };
271
 
272
+ // Core run with updated input (used by XTerm onData and Send button)
273
  const runCodeWithUpdatedInput = async (inputLine) => {
274
+ // append newline like a real console
275
  const newAccum = (accumStdin || "") + inputLine + "\n";
276
  setAccumStdin(newAccum);
277
 
 
297
  setProblems(res.error ? parseProblems(res.output) : []);
298
  if (outputLooksForInput(out)) {
299
  setAwaitingInput(true);
300
+ // keep focus
301
+ focusXtermHelper();
302
  } else {
303
  setAwaitingInput(false);
304
  }
 
310
  }
311
  };
312
 
313
+ // Initial Run: do NOT run if code needs interactive input and there's no accumulated stdin.
314
  const handleRun = async () => {
315
  const node = getNodeByPath(tree, activePath);
316
  if (!node || node.type !== "file") {
 
323
  return;
324
  }
325
 
 
326
  const needs = codeNeedsInput(node.content, selectedLang);
327
  const haveAccum = !!(accumStdin && accumStdin.length > 0);
328
  const haveLegacyStdin = !!(stdin && stdin.length > 0);
329
 
330
  if (needs && !haveAccum && !haveLegacyStdin) {
331
+ // Show instruction and enable typing in XTerm; do not perform a run with empty stdin (avoids EOFError)
332
+ setOutput("[Interactive program detected — click Start interactive session or type in terminal]");
333
  setAwaitingInput(true);
334
+ focusXtermHelper();
335
+ return;
336
  }
337
 
338
+ // Otherwise perform run with existing accumulated or legacy input
339
  const stdinToSend = accumStdin || stdin || "";
340
+ resetTerminal(true);
 
341
  setOutput(`[Running with stdin length=${stdinToSend ? stdinToSend.length : 0}]\n`);
342
  setIsRunning(true);
343
  setProblems([]);
 
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) => {
377
  if (e.key === "Enter") {
378
  e.preventDefault();
 
381
  }
382
  };
383
 
384
+ // ---------- Agent functions (unchanged) ----------
385
  const handleAskFix = async () => {
386
  const node = getNodeByPath(tree, activePath);
387
  if (!node || node.type !== "file") {
 
556
  <XTerm
557
  output={output}
558
  onData={(line) => {
559
+ // Called when XTerm wrapper detects a full line (ENTER)
560
  runCodeWithUpdatedInput(line);
561
  }}
562
  />
563
 
564
+ {/* When interactive program detected and waiting for user input */}
565
+ {awaitingInput && (
566
+ <div style={{ marginTop: 8, padding: 8, background: "#252526", border: "1px solid #333", borderRadius: 6 }}>
567
+ <div style={{ marginBottom: 6, color: "#ddd" }}>
568
+ This program appears to be interactive and requires console input.
569
+ </div>
570
+ <div style={{ display: "flex", gap: 8 }}>
571
+ <button
572
+ onClick={() => {
573
+ focusXtermHelper();
574
+ // ensure terminal is ready to accept typed input
575
+ }}
576
+ className="ide-button"
577
+ >
578
+ ▶ Focus Terminal
579
+ </button>
580
+ <button
581
+ onClick={() => {
582
+ // start interactive session: clear output but keep accum
583
+ resetTerminal(true);
584
+ setOutput("[Interactive session started — type into the terminal]");
585
+ setAwaitingInput(true);
586
+ focusXtermHelper();
587
+ }}
588
+ className="ide-button"
589
+ >
590
+ ▶ Start interactive session
591
+ </button>
592
+ <div style={{ color: "#999", alignSelf: "center" }}>Type & press Enter in terminal or use Send below.</div>
593
+ </div>
594
+ </div>
595
+ )}
596
+
597
+ {/* If not awaiting input show legacy small input for single-run */}
598
  {!awaitingInput && (
599
  <div style={{ display: "flex", gap: 6, marginTop: 6 }}>
600
  <input className="ide-input-box" placeholder="(Optional) Program input for single-run" value={stdin} onChange={(e) => setStdin(e.target.value)} />
 
602
  </div>
603
  )}
604
 
605
+ {/* Always show small input/send option so users can type without xterm if they prefer */}
606
+ <div style={{ display: "flex", gap: 6, marginTop: 8 }}>
607
+ <input
608
+ className="ide-input-box"
609
+ placeholder={awaitingInput ? "Type input and press Send (or press Enter in terminal)" : "Optional input (press Send to append and run)"}
610
+ value={terminalInput}
611
+ onChange={(e) => setTerminalInput(e.target.value)}
612
+ onKeyDown={(e) => {
613
+ if (e.key === "Enter") {
614
+ e.preventDefault();
615
+ sendTerminalInput();
616
+ }
617
+ }}
618
+ />
619
+ <button onClick={sendTerminalInput} className="ide-button" disabled={isRunning}>{isRunning ? "⏳" : "Send"}</button>
620
+ </div>
621
  </div>
622
 
623
  {problems.length > 0 && (