Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 51 additions & 29 deletions modules/home/code-line.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,62 @@

"use client";
import React from 'react';

export const CodeLine = ({ line }: { line: string }) => {
// Basic replacements to simulate syntax highlighting
// Note: This is a very simplistic implementation and should be replaced with a proper library like prismjs or shiki in production

const highlight = (text: string) => {
// We use a series of replacements. Order matters to avoid replacing inside already replaced spans.
// A better approach for robust highlighting is tokenization.

const highlighted = text;

// Comments (simple // for now)
if (highlighted.includes('//')) {
const parts = highlighted.split('//');
return <><span dangerouslySetInnerHTML={{ __html: highlightCode(parts[0]) }} /><span className="text-slate-500 italic">{'//' + parts[1]}</span></>;
}
const escapeHtml = (text: string) => {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
};

return <span dangerouslySetInnerHTML={{ __html: highlightCode(highlighted) }} />;
const highlightCode = (code: string) => {
// Use placeholders for span tags to preserve them during escaping
const placeholderPrefix = "__SPAN_";
const placeholderSuffix = "__";
let placeholderIndex = 0;
const placeholders: string[] = [];

const replaceWithPlaceholder = (match: string) => {
const placeholder = `${placeholderPrefix}${placeholderIndex++}${placeholderSuffix}`;
placeholders.push(match);
return placeholder;
};

const highlightCode = (code: string) => {
return code
.replace(/import|from|export|default|return|const|new/g, '<span class="text-red-500 dark:text-red-400 font-semibold">$&</span>')
.replace(/'[^']*'/g, '<span class="text-amber-600 dark:text-amber-400">$&</span>')
.replace(/"[^"]*"/g, '<span class="text-amber-600 dark:text-amber-400">$&</span>')
.replace(/Editron|console|editor/g, '<span class="text-rose-600 dark:text-rose-400">$&</span>');
}
// Apply syntax highlighting on raw code first
let highlighted = code
.replace(/\b(?:import|from|export|default|return|const|new)\b/g, (match) => replaceWithPlaceholder(`<span class="text-red-500 dark:text-red-400 font-semibold">${escapeHtml(match)}</span>`))
.replace(/'[^']*'/g, (match) => replaceWithPlaceholder(`<span class="text-amber-600 dark:text-amber-400">${escapeHtml(match)}</span>`))
.replace(/\"[^\"]*\"/g, (match) => replaceWithPlaceholder(`<span class="text-amber-600 dark:text-amber-400">${escapeHtml(match)}</span>`))
.replace(/\b(?:Editron|console|editor)\b/g, (match) => replaceWithPlaceholder(`<span class="text-rose-600 dark:text-rose-400">${escapeHtml(match)}</span>`));

const [highlighted, setHighlighted] = React.useState<React.ReactNode>(line);
// Escape HTML entities in the non-span content
highlighted = escapeHtml(highlighted);

React.useEffect(() => {
setHighlighted(highlight(line));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [line]);
// Restore the span tags from placeholders
placeholders.forEach((span, index) => {
highlighted = highlighted.replace(`${placeholderPrefix}${index}${placeholderSuffix}`, span);
});

return highlighted;
};

const highlight = (text: string) => {
if (text.includes('//')) {
const commentStart = text.indexOf('//');
const codePart = text.slice(0, commentStart);
const commentPart = text.slice(commentStart);
return (
<>
<span dangerouslySetInnerHTML={{ __html: highlightCode(codePart) }} />
<span className="text-slate-500 italic">{commentPart}</span>
</>
);
}
return <span dangerouslySetInnerHTML={{ __html: highlightCode(text) }} />;
};

export const CodeLine = ({ line }: { line: string }) => {
const highlighted = React.useMemo(() => highlight(line), [line]);
return highlighted;
};
9 changes: 4 additions & 5 deletions modules/playground/components/playground-editor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { EDITOR_CONFIG } from "@/lib/constants/config";
import { useRef, useEffect, useState } from "react";
import { useRef, useEffect, useState, useCallback } from "react";
import Editor, { type Monaco } from "@monaco-editor/react";
import type { editor as MonacoEditor } from "monaco-editor";
import {
Expand Down Expand Up @@ -253,7 +253,7 @@ const PlaygroundEditor = ({
);
};

const updateEditorLanguage = () => {
const updateEditorLanguage = useCallback(() => {
if (!activeFile || !monacoRef.current || !editorRef.current) return;
const model = editorRef.current.getModel();
if (!model) return;
Expand All @@ -264,12 +264,11 @@ const PlaygroundEditor = ({
} catch (error) {
console.warn("Failed to set editor language:", error);
}
};
}, [activeFile]);

useEffect(() => {
updateEditorLanguage();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeFile]);
}, [updateEditorLanguage]);

// Bind Yjs to Monaco
useEffect(() => {
Expand Down
2 changes: 0 additions & 2 deletions modules/webcontainers/components/terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,6 @@ const
writePrompt();

return terminal;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [theme, handleTerminalInput, writePrompt]);

const connectToWebContainer = useCallback(async () => {
Expand Down Expand Up @@ -420,7 +419,6 @@ const
currentProcess.current.kill();
}
if (shellProcess.current) {
// eslint-disable-next-line react-hooks/exhaustive-deps
shellProcess.current.kill();
}
if (term.current) {
Expand Down