diff --git a/components/Editor.js b/components/Editor.js index 17c8508..c0e55e5 100644 --- a/components/Editor.js +++ b/components/Editor.js @@ -1,32 +1,78 @@ -import React from "react"; +import React, { useRef, useEffect } from "react"; import AceEditor from "react-ace"; -import "ace-builds/src-noconflict/mode-java"; +// Import Ace modes and themes import "ace-builds/src-noconflict/mode-fortran"; -import "ace-builds/src-noconflict/theme-github"; import "ace-builds/src-noconflict/theme-monokai"; import "ace-builds/src-noconflict/ext-language_tools"; -function Editor({sourceCode, setSourceCode}) { +const Editor = ({ sourceCode, setSourceCode, editorRef }) => { + const aceEditorRef = useRef(null); + + useEffect(() => { + if (editorRef) { + editorRef.current = { + jumpToRange(rangeData) { + if (aceEditorRef.current) { + const editor = aceEditorRef.current.editor; + const ace = window.ace; + + if (ace) { + // Required for the selection to not be a solid block + editor.setSelectionStyle("text"); + + const Range = ace.require("ace/range").Range; + + // Convert 1-based metadata to 0-based Ace indexing + const startRow = rangeData.startLine - 1; + const startCol = rangeData.startCol - 1; + const endRow = rangeData.endLine - 1; + const endCol = rangeData.endCol - 1; + + // Clear existing selection to avoid visual ghosting + editor.selection.clearSelection(); + + // Apply the precise syntactic range + const newRange = new Range(startRow, startCol, endRow, endCol); + editor.selection.setRange(newRange); + + // Navigation and Focus + editor.scrollToLine(rangeData.startLine, true, true); + editor.focus(); + editor.renderer.scrollSelectionIntoView(newRange); + } + } + } + }; + } + }, [editorRef]); + return ( setSourceCode(code)} - name="UNIQUE_ID_OF_DIV" + onChange={(code) => setSourceCode(code)} + name="LFORTRAN_EDITOR" editorProps={{ $blockScrolling: true }} value={sourceCode} width="100%" height="100%" showPrintMargin={false} + setOptions={{ + enableBasicAutocompletion: true, + enableLiveAutocompletion: true, + enableSnippets: true, + showLineNumbers: true, + tabSize: 4, + }} style={{ fontFamily: '"Fira code", "Fira Mono", monospace', fontSize: 14, - // minHeight: "100%", - // border: "0.1px solid black" }} /> - ) -} + ); +}; + -export default Editor; +export default Editor; \ No newline at end of file diff --git a/components/LoadLFortran.js b/components/LoadLFortran.js index b82e2c7..999d8e6 100644 --- a/components/LoadLFortran.js +++ b/components/LoadLFortran.js @@ -85,44 +85,67 @@ function define_imports(outputBuffer, exit_code, stdout_print) { return imports; } -async function setup_lfortran_funcs(lfortran_funcs, myPrint) { +async function setup_lfortran_funcs(lfortran_funcs, CustomPrint) { const compiler_funcs = await getLfortranExportedFuncs(); lfortran_funcs.emit_ast_from_source = function (source_code) { - try { return compiler_funcs.emit_ast_from_source(source_code); } - catch (e) { console.log(e); myPrint(e + "\nERROR: AST could not be generated from the code"); return 0; } + try { + return compiler_funcs.emit_ast_from_source(source_code); + } catch (e) { + console.log(e); + CustomPrint(e + "\nERROR: AST could not be generated from the code"); + return 0; + } } lfortran_funcs.emit_asr_from_source = function (source_code) { - try { return compiler_funcs.emit_asr_from_source(source_code); } - catch (e) { console.log(e); myPrint(e + "\nERROR: ASR could not be generated from the code"); return 0; } + try { + return compiler_funcs.emit_asr_from_source(source_code); + } catch (e) { + console.log(e); + CustomPrint(e + "\nERROR: ASR could not be generated from the code"); + return 0; + } } lfortran_funcs.emit_wat_from_source = function (source_code) { - try { return compiler_funcs.emit_wat_from_source(source_code); } - catch (e) { console.log(e); myPrint(e + "\nERROR: WAT could not be generated from the code"); return 0; } + try { + return compiler_funcs.emit_wat_from_source(source_code); + } catch (e) { + console.log(e); + CustomPrint(e + "\nERROR: WAT could not be generated from the code"); + return 0; + } } lfortran_funcs.emit_cpp_from_source = function (source_code) { - try { return compiler_funcs.emit_cpp_from_source(source_code); } - catch (e) { console.log(e); myPrint(e + "\nERROR: CPP could not be generated from the code"); return 0; } + try { + return compiler_funcs.emit_cpp_from_source(source_code); + } catch (e) { + console.log(e); + CustomPrint(e + "\nERROR: CPP could not be generated from the code"); + return 0; + } } lfortran_funcs.emit_py_from_source = function (source_code) { - try { return compiler_funcs.emit_py_from_source(source_code); } - catch (e) { console.log(e); myPrint(e + "\nERROR: LLVM could not be generated from the code"); return 0; } + try { + return compiler_funcs.emit_py_from_source(source_code); + } catch (e) { + console.log(e); + CustomPrint(e + "\nERROR: LLVM could not be generated from the code"); + return 0; + } } lfortran_funcs.compile_code = function (source_code) { try { return compiler_funcs.emit_wasm_from_source(source_code); - } - catch (e) { - console.log(e); - myPrint(e + "\nERROR: The code could not be compiled. Either there is a compile-time error or there is an issue at our end.") + } catch (e) { + console.log(e); + CustomPrint(e + "\nERROR: The code could not be compiled. Either there is a compile-time error or there is an issue at our end."); return 0; } - }; lfortran_funcs.execute_code = async function (bytes, stdout_print) { @@ -150,7 +173,6 @@ async function setup_lfortran_funcs(lfortran_funcs, myPrint) { if (exit_code.val == 0) { return; } - console.log(err_msg); stdout_print(`\n${err_msg}\nERROR: The code could not be executed. Either there is a runtime error or there is an issue at our end.`); } }; @@ -161,7 +183,7 @@ function LoadLFortran({ setModuleReady, lfortran_funcs, openNotification, - myPrint, + CustomPrint, }) { const { basePath } = useRouter(); @@ -174,10 +196,9 @@ function LoadLFortran({ }, [basePath]); const setupLFortran = useCallback(async () => { - await setup_lfortran_funcs(lfortran_funcs, myPrint); + await setup_lfortran_funcs(lfortran_funcs, CustomPrint); setModuleReady(true); openNotification("LFortran Module Initialized!", "bottomRight"); - console.log("LFortran Module Initialized!"); }, [moduleReady]); // update the callback if the state changes return ( diff --git a/components/ResultBox.js b/components/ResultBox.js index e40a24b..cf72fae 100644 --- a/components/ResultBox.js +++ b/components/ResultBox.js @@ -4,21 +4,42 @@ import { Button } from "antd"; import { CopyOutlined } from "@ant-design/icons"; import { Segmented } from "antd"; -function ResultBox({ activeTab, output, handleUserTabChange, myHeight, openNotification }) { +// onNodeClick to props +function ResultBox({ activeTab, output, handleUserTabChange, myHeight, openNotification, onNodeClick }) { const isMobile = useIsMobile(); const [showCopy, setShowCopy] = useState(false); + + const handleOutputClick = (e) => { + // .closest to handle nested spans + const target = e.target.closest("[data-start-line]"); + + if (target) { + const rangeData = { + startLine: parseInt(target.getAttribute("data-start-line")), + startCol: parseInt(target.getAttribute("data-start-col")), + endLine: parseInt(target.getAttribute("data-end-line")), + endCol: parseInt(target.getAttribute("data-end-col")) + }; + + if (onNodeClick) { + onNodeClick(rangeData); + } + } + }; + function copyTextToClipboard(e) { - e.stopPropagation(); // Prevents the click from reaching the container below + e.stopPropagation(); const parser = new DOMParser(); const doc = parser.parseFromString(output, 'text/html'); navigator.clipboard.writeText(doc.documentElement.textContent); openNotification(`${activeTab} output copied`, "bottomRight"); - setShowCopy(false); // Hide the icon after the user copies the text + setShowCopy(false); } + return (
isMobile && setShowCopy(!showCopy)} // Only toggle if on mobile + onClick={() => isMobile && setShowCopy(!showCopy)} > @@ -44,6 +65,8 @@ function ResultBox({ activeTab, output, handleUserTabChange, myHeight, openNotif
                 
@@ -52,4 +75,4 @@ function ResultBox({ activeTab, output, handleUserTabChange, myHeight, openNotif
); } -export default ResultBox; +export default ResultBox; \ No newline at end of file diff --git a/components/TextBox.js b/components/TextBox.js index 6e37cdf..c77f6bd 100644 --- a/components/TextBox.js +++ b/components/TextBox.js @@ -1,19 +1,19 @@ import { Button, Tabs, Dropdown, Menu, Space } from "antd"; const { TabPane } = Tabs; -import { DownOutlined, PlayCircleOutlined,FullscreenOutlined,FullscreenExitOutlined} from "@ant-design/icons"; +import { DownOutlined, PlayCircleOutlined, FullscreenOutlined, FullscreenExitOutlined } from "@ant-design/icons"; import { useIsMobile } from "./useIsMobile"; import React, { useState, useEffect } from 'react'; import preinstalled_programs from "../utils/preinstalled_programs"; import dynamic from 'next/dynamic' -const Editor = dynamic(import('./Editor'), { - ssr: false -}) -function TextBox({ disabled, sourceCode, setSourceCode, exampleName, setExampleName, activeTab, handleUserTabChange, myHeight }) { +const Editor = dynamic(() => import('./Editor'), { + ssr: false, +}); + +function TextBox({ disabled, sourceCode, setSourceCode, exampleName, setExampleName, activeTab, handleUserTabChange, myHeight, editorRef }) { const isMobile = useIsMobile(); const [isFullScreen, setIsFullScreen] = useState(false); - // Listen for fullscreen changes (Esc key, browser buttons, etc.) useEffect(() => { const handler = () => setIsFullScreen(!!document.fullscreenElement); document.addEventListener("fullscreenchange", handler); @@ -49,7 +49,6 @@ function TextBox({ disabled, sourceCode, setSourceCode, exampleName, setExampleN }); } - const examples_menu = (); const extraOperations = { right: ( @@ -57,7 +56,6 @@ function TextBox({ disabled, sourceCode, setSourceCode, exampleName, setExampleN onClick={handleFullScreen} icon={isFullScreen ? : } > - {/* Now both text options only show on desktop, keeping mobile clean */} {!isMobile && (isFullScreen ? " Exit Fullscreen" : " Fullscreen")}
), @@ -106,4 +105,4 @@ function TextBox({ disabled, sourceCode, setSourceCode, exampleName, setExampleN ); } -export default TextBox; +export default TextBox; \ No newline at end of file diff --git a/pages/index.js b/pages/index.js index 2781033..eceb852 100644 --- a/pages/index.js +++ b/pages/index.js @@ -4,7 +4,7 @@ import LoadLFortran from "../components/LoadLFortran"; import preinstalled_programs from "../utils/preinstalled_programs"; import { useIsMobile } from "../components/useIsMobile"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { Col, Row, Spin } from "antd"; import { notification } from "antd"; import { LoadingOutlined } from "@ant-design/icons"; @@ -46,6 +46,10 @@ export default function Home() { const [activeTab, setActiveTab] = useState("STDOUT"); const [output, setOutput] = useState(""); const [dataFetch, setDataFetch] = useState(false); + + // Initialize the Ref for the Editor + const editorRef = useRef(null); + const isMobile = useIsMobile(); const myHeight = ((!isMobile) ? "calc(100vh - 170px)" : "calc(50vh - 85px)"); @@ -60,6 +64,14 @@ export default function Home() { handleUserTabChange("STDOUT"); } }, [moduleReady, dataFetch]); + // Jump Handler to be passed to ResultBox + const jumpToEditorLine = (rangeData) => { + + // Use jumpToRange (updated from jumpToLine to match your new Editor.js API) + if (editorRef.current && typeof editorRef.current.jumpToRange === 'function') { + editorRef.current.jumpToRange(rangeData); + } + }; async function fetchData() { const url = window.location.search; @@ -82,7 +94,6 @@ export default function Home() { ); }) .catch((error) => { - console.error("Error fetching data:", error); openNotification("error fetching .", "bottomRight"); }); } else { @@ -109,7 +120,6 @@ export default function Home() { if (wasm_bytes_response) { const [exit_code, ...compile_result] = wasm_bytes_response.split(","); if (exit_code !== "0") { - // print compile-time error found by lfortran to output setOutput(ansi_up.ansi_to_html(compile_result) + `\nCompilation Time: ${duration_compile} ms`); } else { @@ -124,12 +134,50 @@ export default function Home() { } else if (key == "AST") { const res = lfortran_funcs.emit_ast_from_source(sourceCode); if (res) { - setOutput(ansi_up.ansi_to_html(res)); - } + const htmlOutput = ansi_up.ansi_to_html(res); + + // Regex /g ensures all instances become interactive + const finalOutput = htmlOutput + .replace(/Declaration/g, + `Declaration` + ) + .replace(/Subroutine/g, + `Subroutine` + ); + + setOutput(finalOutput); + } } else if (key == "ASR") { const res = lfortran_funcs.emit_asr_from_source(sourceCode); if (res) { - setOutput(ansi_up.ansi_to_html(res)); + const htmlOutput = ansi_up.ansi_to_html(res); + + // Using global regex (/g) ensures all instances are captured + const finalOutput = htmlOutput + .replace(/Declaration/g, + `Declaration` + ) + .replace(/Subroutine/g, + `Subroutine` + ); + + setOutput(finalOutput); } } else if (key == "WAT") { const res = lfortran_funcs.emit_wat_from_source(sourceCode); @@ -144,7 +192,6 @@ export default function Home() { } else if (key == "PY") { setOutput("Support for PY is not yet enabled"); } else { - console.log("Unknown key:", key); setOutput("Unknown key: " + key); } setActiveTab(key); @@ -157,7 +204,7 @@ export default function Home() { setModuleReady={setModuleReady} lfortran_funcs={lfortran_funcs} openNotification={openNotification} - myPrint={setOutput} + CustomPrint={setOutput} > @@ -171,6 +218,8 @@ export default function Home() { activeTab={activeTab} handleUserTabChange={handleUserTabChange} myHeight={myHeight} + // Pass the editorRef to TextBox + editorRef={editorRef} > @@ -181,6 +230,8 @@ export default function Home() { handleUserTabChange={handleUserTabChange} myHeight={myHeight} openNotification={openNotification} + //Pass the jump handler to ResultBox + onNodeClick={jumpToEditorLine} > ) : (
@@ -198,4 +249,4 @@ export default function Home() { ); -} +} \ No newline at end of file