diff --git a/bun.lockb b/bun.lockb index 049946d..e4870c8 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index da5efb7..ad209f1 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,12 @@ "vite": "^6.2.0" }, "dependencies": { + "@codemirror/state": "^6.5.2", + "@codemirror/view": "^6.36.4", + "@shopify/lang-jsonc": "^1.0.1", "@tailwindcss/vite": "^4.0.9", "bagon-hooks": "^0.0.4", + "codemirror": "^6.0.1", "hono": "^4.7.2", "jsonc-parser": "^3.3.1", "solid-js": "^1.9.5", diff --git a/pages/index/+Page.tsx b/pages/index/+Page.tsx index 0d9b225..3f98a9a 100644 --- a/pages/index/+Page.tsx +++ b/pages/index/+Page.tsx @@ -1,14 +1,23 @@ import { convertVSCodeToZedSnippets } from "@/utils/convert-vscode-to-zed-snippets"; import { debounce } from "@/utils/debounce"; -import { createEffect, createSignal, For, Show } from "solid-js"; +import { createEffect, createSignal, For, Match, onCleanup, onMount, Show, Switch } from "solid-js"; import { useMetadata } from "vike-metadata-solid"; import { IconGitHub, IconVSCode, IconZed } from "@/assets"; import { useClipboard, useLocalStorage } from "bagon-hooks"; +// CodeMirror imports +import { EditorState } from "@codemirror/state"; +import { EditorView } from "@codemirror/view"; +import { basicSetup } from "codemirror"; + +// Create a variable to store jsonc +let jsonc: any; + export default function Page() { useMetadata({}); + const [mounted, setMounted] = createSignal(false); const [vsCodeSnippet, setVSCodeSnippet] = useLocalStorage({ key: "vscode-snippet-input", defaultValue: "", @@ -30,6 +39,14 @@ export default function Page() { timeout: 1000, }); + // References for CodeMirror elements + let vsCodeEditorContainer: HTMLDivElement | undefined; + let zedEditorContainer: HTMLDivElement | undefined; + + // References for editor views + let vsCodeEditorView: EditorView | undefined; + let zedEditorView: EditorView | undefined; + const debouncedConvert = debounce((snippetText: string) => { try { const converted = convertVSCodeToZedSnippets(snippetText); @@ -40,17 +57,164 @@ export default function Page() { })) ); setHasError(false); + + // Update zed editor content if initialized + if (zedEditorView && convertedSnippets().length > 0) { + updateZedEditor(); + } } catch (error) { console.error("Failed to convert snippet:", error); setHasError(true); } }, 500); - createEffect(() => { - debouncedConvert(vsCodeSnippet()); + // Theme extension for CodeMirror + const createThemeExtension = (isDark: boolean) => { + return EditorView.theme({ + "&": { + height: "100%", + fontSize: "12px", + }, + ".cm-content": { + fontFamily: "monospace", + caretColor: isDark ? "#fff" : "#000", + }, + ".cm-gutters": { + backgroundColor: isDark ? "#1f2937" : "#f3f4f6", + color: isDark ? "#9ca3af" : "#6b7280", + border: "none", + }, + ".cm-scroller": { + overflow: "auto", + height: "100%", + }, + ".cm-line": { + padding: "0 4px", + }, + "&.cm-focused .cm-cursor": { + borderLeftColor: isDark ? "#fff" : "#000", + }, + "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": { + backgroundColor: isDark ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.1)", + }, + ".cm-activeLine": { + backgroundColor: isDark ? "rgba(255, 255, 255, 0.05)" : "rgba(0, 0, 0, 0.05)", + }, + }); + }; + + // Initialize VSCode editor + const initVSCodeEditor = () => { + if (!vsCodeEditorContainer) return; + + vsCodeEditorView = new EditorView({ + state: EditorState.create({ + doc: vsCodeSnippet(), + extensions: [ + basicSetup, + jsonc(), + createThemeExtension(isDarkMode()), + EditorView.updateListener.of((update) => { + if (update.docChanged) { + const newValue = update.state.doc.toString(); + setVSCodeSnippet(newValue); + debouncedConvert(newValue); + } + }), + ], + }), + parent: vsCodeEditorContainer, + }); + }; + + // Initialize Zed editor + const initZedEditor = () => { + if (!zedEditorContainer || convertedSnippets().length === 0) return; + + zedEditorView = new EditorView({ + state: EditorState.create({ + doc: convertedSnippets()[activeTab()].content, + extensions: [ + basicSetup, + jsonc(), + createThemeExtension(isDarkMode()), + EditorState.readOnly.of(true), + ], + }), + parent: zedEditorContainer, + }); + }; + + // Update Zed editor content when tab changes or content changes + const updateZedEditor = () => { + if (!zedEditorView || convertedSnippets().length === 0) return; + + const newState = EditorState.create({ + doc: convertedSnippets()[activeTab()].content, + extensions: [ + basicSetup, + jsonc(), + createThemeExtension(isDarkMode()), + EditorState.readOnly.of(true), + ], + }); + + zedEditorView.setState(newState); + }; + + onMount(async () => { + const _import = await import("@shopify/lang-jsonc"); + jsonc = _import.jsonc; + + if (typeof window !== "undefined") { + initVSCodeEditor(); + if (convertedSnippets().length > 0) { + initZedEditor(); + } + debouncedConvert(vsCodeSnippet()); + } + setMounted(true); + }); + + onCleanup(() => { + vsCodeEditorView?.destroy(); + zedEditorView?.destroy(); }); createEffect(() => { + // Update theme when dark mode changes + if (vsCodeEditorView) { + const newState = EditorState.create({ + doc: vsCodeEditorView.state.doc, + extensions: [ + basicSetup, + jsonc(), + createThemeExtension(isDarkMode()), + EditorView.updateListener.of((update) => { + if (update.docChanged) { + const newValue = update.state.doc.toString(); + setVSCodeSnippet(newValue); + debouncedConvert(newValue); + } + }), + ], + }); + vsCodeEditorView.setState(newState); + } + + if (zedEditorView && convertedSnippets().length > 0) { + const newState = EditorState.create({ + doc: convertedSnippets()[activeTab()].content, + extensions: [ + basicSetup, + jsonc(), + createThemeExtension(isDarkMode()), + EditorState.readOnly.of(true), + ], + }); + zedEditorView.setState(newState); + } + if (isDarkMode()) { document.documentElement.classList.add("dark"); } else { @@ -58,6 +222,21 @@ export default function Page() { } }); + // Handle tab changes + createEffect(() => { + const tab = activeTab(); + if (convertedSnippets().length > 0 && zedEditorView) { + updateZedEditor(); + } + }); + + createEffect(() => { + // If convertedSnippets changes and we have no Zed editor yet, initialize it + if (convertedSnippets().length > 0 && !zedEditorView && zedEditorContainer) { + initZedEditor(); + } + }); + const toggleDarkMode = () => { setIsDarkMode(!isDarkMode()); }; @@ -86,18 +265,11 @@ export default function Page() {