From 5817b2ea48816bb14f238d19fa52bcdb4e6c7c78 Mon Sep 17 00:00:00 2001 From: Akhileshrangani4 Date: Sat, 28 Sep 2024 19:25:03 -0400 Subject: [PATCH] fix: filecontent update while switching tabs, empty file crash --- backend/server/src/index.ts | 5 +- frontend/components/editor/index.tsx | 245 ++++++++++++++++----------- 2 files changed, 152 insertions(+), 98 deletions(-) diff --git a/backend/server/src/index.ts b/backend/server/src/index.ts index 6106faa..23d59cb 100644 --- a/backend/server/src/index.ts +++ b/backend/server/src/index.ts @@ -159,8 +159,9 @@ io.on("connection", async (socket) => { await lockManager.acquireLock(data.sandboxId, async () => { try { - if (!containers[data.sandboxId]) { - containers[data.sandboxId] = await Sandbox.create({ timeoutMs: 1200000 }); + // Start a new container if the container doesn't exist or it timed out. + if (!containers[data.sandboxId] || !(await containers[data.sandboxId].isRunning())) { + containers[data.sandboxId] = await Sandbox.create({ timeoutMs: 1200_000 }); console.log("Created container ", data.sandboxId); } } catch (e: any) { diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx index f5750ee..a85c33e 100644 --- a/frontend/components/editor/index.tsx +++ b/frontend/components/editor/index.tsx @@ -70,6 +70,8 @@ export default function CodeEditor({ const [activeFileId, setActiveFileId] = useState("") const [activeFileContent, setActiveFileContent] = useState("") const [deletingFolderId, setDeletingFolderId] = useState("") + // Added this state to track the most recent content for each file + const [fileContents, setFileContents] = useState>({}); // Editor state const [editorLanguage, setEditorLanguage] = useState("plaintext") @@ -257,6 +259,7 @@ export default function CodeEditor({ } }) }, [editorRef]) + // Generate widget effect useEffect(() => { if (generate.show) { @@ -335,6 +338,7 @@ export default function CodeEditor({ }) } }, [generate.show]) + // Suggestion widget effect useEffect(() => { if (!suggestionRef.current || !editorRef) return @@ -378,11 +382,20 @@ export default function CodeEditor({ } const model = editorRef?.getModel() - const line = model?.getLineContent(cursorLine) - - if (line === undefined || line.trim() !== "") { - decorations.instance?.clear() - return + // added this because it was giving client side exception - Illegal value for lineNumber when opening an empty file + if (model) { + const totalLines = model.getLineCount(); + // Check if the cursorLine is a valid number, If cursorLine is out of bounds, we fall back to 1 (the first line) as a default safe value. + const lineNumber = cursorLine > 0 && cursorLine <= totalLines ? cursorLine : 1; // fallback to a valid line number + // If for some reason the content doesn't exist, we use an empty string as a fallback. + const line = model.getLineContent(lineNumber) ?? ""; + // Check if the line is not empty or only whitespace (i.e., `.trim()` removes spaces). + // If the line has content, we clear any decorations using the instance of the `decorations` object. + // Decorations refer to editor highlights, underlines, or markers, so this clears those if conditions are met. + if (line.trim() !== "") { + decorations.instance?.clear(); + return + } } if (decorations.instance) { @@ -401,25 +414,33 @@ export default function CodeEditor({ }, [decorations.options]) // Save file keybinding logic effect + // Function to save the file content after a debounce period const debouncedSaveData = useCallback( - debounce((value: string | undefined, activeFileId: string | undefined) => { - setTabs((prev) => - prev.map((tab) => - tab.id === activeFileId ? { ...tab, saved: true } : tab - ) - ) - console.log(`Saving file...${activeFileId}`) - console.log(`Saving file...${value}`) - socket?.emit("saveFile", activeFileId, value) - }, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000), - [socket] - ) + debounce((activeFileId: string | undefined) => { + if (activeFileId) { + // Get the current content of the file + const content = fileContents[activeFileId]; + // Mark the file as saved in the tabs + setTabs((prev) => + prev.map((tab) => + tab.id === activeFileId ? { ...tab, saved: true } : tab + ) + ); + console.log(`Saving file...${activeFileId}`); + console.log(`Saving file...${content}`); + socket?.emit("saveFile", activeFileId, content); + } + }, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000), + [socket, fileContents] + ); + + // Keydown event listener to trigger file save on Ctrl+S or Cmd+S useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "s" && (e.metaKey || e.ctrlKey)) { e.preventDefault() - debouncedSaveData(editorRef?.getValue(), activeFileId) + debouncedSaveData(activeFileId); } } document.addEventListener("keydown", down) @@ -516,7 +537,7 @@ export default function CodeEditor({ // Socket event listener effect useEffect(() => { - const onConnect = () => {} + const onConnect = () => { } const onDisconnect = () => { setTerminals([]) @@ -586,31 +607,46 @@ export default function CodeEditor({ } // 300ms debounce delay, adjust as needed const selectFile = (tab: TTab) => { - if (tab.id === activeFileId) return - - setGenerate((prev) => ({ ...prev, show: false })) - - const exists = tabs.find((t) => t.id === tab.id) + if (tab.id === activeFileId) return; + + setGenerate((prev) => ({ ...prev, show: false })); + + // Check if the tab already exists in the list of open tabs + const exists = tabs.find((t) => t.id === tab.id); setTabs((prev) => { if (exists) { - setActiveFileId(exists.id) - return prev + // If the tab exists, make it the active tab + setActiveFileId(exists.id); + return prev; } - return [...prev, tab] - }) - - if (fileCache.current.has(tab.id)) { - setActiveFileContent(fileCache.current.get(tab.id)) + // If the tab doesn't exist, add it to the list of tabs and make it active + return [...prev, tab]; + }); + + // If the file's content is already cached, set it as the active content + if (fileContents[tab.id]) { + setActiveFileContent(fileContents[tab.id]); } else { - debouncedGetFile(tab.id, (response: SetStateAction) => { - fileCache.current.set(tab.id, response) - setActiveFileContent(response) - }) + // Otherwise, fetch the content of the file and cache it + debouncedGetFile(tab.id, (response: string) => { + setFileContents(prev => ({ ...prev, [tab.id]: response })); + setActiveFileContent(response); + }); } + + // Set the editor language based on the file type + setEditorLanguage(processFileType(tab.name)); + // Set the active file ID to the new tab + setActiveFileId(tab.id); + }; - setEditorLanguage(processFileType(tab.name)) - setActiveFileId(tab.id) - } + // Added this effect to update fileContents when the editor content changes + useEffect(() => { + if (activeFileId) { + // Cache the current active file content using the file ID as the key + setFileContents(prev => ({ ...prev, [activeFileId]: activeFileContent })); + } + }, [activeFileContent, activeFileId]); // Close tab and remove from tabs const closeTab = (id: string) => { @@ -626,8 +662,8 @@ export default function CodeEditor({ ? numTabs === 1 ? null : index < numTabs - 1 - ? tabs[index + 1].id - : tabs[index - 1].id + ? tabs[index + 1].id + : tabs[index - 1].id : activeFileId setTabs((prev) => prev.filter((t) => t.id !== id)) @@ -720,12 +756,25 @@ export default function CodeEditor({ {}} + setOpen={() => { }} /> ) + useEffect(() => { + console.log('Editor mounted'); + return () => console.log('Editor unmounted'); + }, []); + + useEffect(() => { + console.log('activeFileContent changed:'); + }, [activeFileContent]); + + useEffect(() => { + console.log('activeFileId changed:'); + }, [activeFileId]); + return ( <> {/* Copilot DOM elements */} @@ -759,8 +808,8 @@ export default function CodeEditor({ code: (isSelected && editorRef?.getSelection() ? editorRef - ?.getModel() - ?.getValueInRange(editorRef?.getSelection()!) + ?.getModel() + ?.getValueInRange(editorRef?.getSelection()!) : editorRef?.getValue()) ?? "", line: generate.line, }} @@ -784,11 +833,11 @@ export default function CodeEditor({ const afterLineNumber = isAbove ? line - 1 : line id = changeAccessor.addZone({ afterLineNumber, - heightInLines: isAbove?11: 12, + heightInLines: isAbove ? 11 : 12, domNode: generateRef.current, }) - const contentWidget= generate.widget - if (contentWidget){ + const contentWidget = generate.widget + if (contentWidget) { editorRef?.layoutContentWidget(contentWidget) } } else { @@ -885,58 +934,62 @@ export default function CodeEditor({ ) : // Note clerk.loaded is required here due to a bug: https://github.com/clerk/javascript/issues/1643 - clerk.loaded ? ( - <> - {provider && userInfo ? ( - - ) : null} - { - if (value === activeFileContent) { - setTabs((prev) => - prev.map((tab) => - tab.id === activeFileId - ? { ...tab, saved: true } - : tab + clerk.loaded ? ( + <> + {provider && userInfo ? ( + + ) : null} + { + // If the new content is different from the cached content, update it + if (value !== fileContents[activeFileId]) { + setActiveFileContent(value ?? ""); // Update the active file content + // Mark the file as unsaved by setting 'saved' to false + setTabs((prev) => + prev.map((tab) => + tab.id === activeFileId + ? { ...tab, saved: false } + : tab + ) ) - ) - } else { - setTabs((prev) => - prev.map((tab) => - tab.id === activeFileId - ? { ...tab, saved: false } - : tab + } else { + // If the content matches the cached content, mark the file as saved + setTabs((prev) => + prev.map((tab) => + tab.id === activeFileId + ? { ...tab, saved: true } + : tab + ) ) - ) - } - }} - options={{ - tabSize: 2, - minimap: { - enabled: false, - }, - padding: { - bottom: 4, - top: 4, - }, - scrollBeyondLastLine: false, - fixedOverflowWidgets: true, - fontFamily: "var(--font-geist-mono)", - }} - theme="vs-dark" - value={activeFileContent} - /> - - ) : ( -
- - Waiting for Clerk to load... -
- )} + } + }} + options={{ + tabSize: 2, + minimap: { + enabled: false, + }, + padding: { + bottom: 4, + top: 4, + }, + scrollBeyondLastLine: false, + fixedOverflowWidgets: true, + fontFamily: "var(--font-geist-mono)", + }} + theme="vs-dark" + value={activeFileContent} + /> + + ) : ( +
+ + Waiting for Clerk to load... +
+ )}