forked from stackblitz/bolt.new
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit replaces CodeMirror with Monaco editor to provide a more …
…VSCode-like experience. Key changes: Add Monaco editor with exact VSCode syntax highlighting colors Configure client-side only Monaco setup to avoid SSR issues Create dedicated theme configuration matching VSCode's token colors Update EditorPanel to use Monaco instead of CodeMirror Add proper theme switching between light/dark modes Ensure correct syntax highlighting for all file types The new editor provides a familiar VSCode experience with identical syntax highlighting colors, making code more readable and consistent with VSCode's default theme.
- Loading branch information
Showing
8 changed files
with
715 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import { useEffect, useRef, useState } from 'react'; | ||
import type { Theme } from '~/types/theme'; | ||
import type { EditorDocument } from './codemirror/CodeMirrorEditor'; | ||
import type { editor } from 'monaco-editor'; | ||
import { setupMonaco } from '~/lib/monaco-setup'; | ||
|
||
interface MonacoEditorProps { | ||
theme: Theme; | ||
editable?: boolean; | ||
settings?: { | ||
fontSize?: string; | ||
tabSize?: number; | ||
}; | ||
doc?: EditorDocument; | ||
autoFocusOnDocumentChange?: boolean; | ||
onScroll?: (scrollTop: number) => void; | ||
onChange?: (value: string) => void; | ||
onSave?: () => void; | ||
} | ||
|
||
export function MonacoEditor({ | ||
theme, | ||
editable = true, | ||
settings, | ||
doc, | ||
autoFocusOnDocumentChange, | ||
onScroll, | ||
onChange, | ||
onSave, | ||
}: MonacoEditorProps) { | ||
const containerRef = useRef<HTMLDivElement>(null); | ||
const editorRef = useRef<editor.IStandaloneCodeEditor>(); | ||
const [monaco, setMonaco] = useState<typeof import('monaco-editor')>(); | ||
|
||
useEffect(() => { | ||
// Only run in browser | ||
if (typeof window === 'undefined') return; | ||
|
||
// Load Monaco | ||
import('monaco-editor').then(async (module) => { | ||
await setupMonaco(); | ||
setMonaco(module); | ||
}); | ||
}, []); | ||
|
||
useEffect(() => { | ||
if (!monaco || !containerRef.current) return; | ||
|
||
// Initialize editor | ||
const editor = monaco.editor.create(containerRef.current, { | ||
value: doc?.value, | ||
language: getLanguage(doc?.filePath), | ||
theme: theme === 'dark' ? 'vs-dark' : 'vs', | ||
fontSize: parseInt(settings?.fontSize || '14'), | ||
tabSize: settings?.tabSize || 2, | ||
minimap: { | ||
enabled: false | ||
}, | ||
scrollBeyondLastLine: false, | ||
renderWhitespace: 'boundary', | ||
readOnly: !editable, | ||
lineNumbers: 'on', | ||
wordWrap: 'on', | ||
folding: true, | ||
bracketPairColorization: { | ||
enabled: true | ||
}, | ||
automaticLayout: true, | ||
scrollbar: { | ||
verticalScrollbarSize: 14, | ||
horizontalScrollbarSize: 14 | ||
} | ||
}); | ||
|
||
editorRef.current = editor; | ||
|
||
// Set up VSCode keybindings | ||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { | ||
onSave?.(); | ||
}); | ||
|
||
// Set up scroll listener | ||
editor.onDidScrollChange((e) => { | ||
onScroll?.(e.scrollTop); | ||
}); | ||
|
||
// Set up change listener | ||
editor.onDidChangeModelContent(() => { | ||
onChange?.(editor.getValue()); | ||
}); | ||
|
||
return () => { | ||
editor.dispose(); | ||
}; | ||
}, [monaco, containerRef.current]); | ||
|
||
useEffect(() => { | ||
if (!editorRef.current || !doc) return; | ||
|
||
// Update content if needed | ||
if (doc.value !== editorRef.current.getValue()) { | ||
editorRef.current.setValue(doc.value); | ||
} | ||
|
||
// Update language if needed | ||
const model = editorRef.current.getModel(); | ||
if (model) { | ||
const currentLanguage = model.getLanguageId(); | ||
const newLanguage = getLanguage(doc.filePath); | ||
if (currentLanguage !== newLanguage) { | ||
monaco?.editor.setModelLanguage(model, newLanguage); | ||
} | ||
} | ||
}, [doc?.value, doc?.filePath]); | ||
|
||
useEffect(() => { | ||
if (!editorRef.current || !monaco) return; | ||
monaco.editor.setTheme(theme === 'dark' ? 'vs-dark' : 'vs'); | ||
}, [theme]); | ||
|
||
useEffect(() => { | ||
if (editorRef.current && autoFocusOnDocumentChange) { | ||
editorRef.current.focus(); | ||
} | ||
}, [doc, autoFocusOnDocumentChange]); | ||
|
||
// Determine language from file extension | ||
const getLanguage = (filePath?: string) => { | ||
if (!filePath) return 'plaintext'; | ||
const ext = filePath.split('.').pop()?.toLowerCase(); | ||
switch (ext) { | ||
case 'js': | ||
return 'javascript'; | ||
case 'jsx': | ||
return 'javascript'; | ||
case 'ts': | ||
return 'typescript'; | ||
case 'tsx': | ||
return 'typescript'; | ||
case 'json': | ||
return 'json'; | ||
case 'md': | ||
return 'markdown'; | ||
case 'css': | ||
return 'css'; | ||
case 'scss': | ||
return 'scss'; | ||
case 'html': | ||
return 'html'; | ||
case 'py': | ||
return 'python'; | ||
default: | ||
return 'plaintext'; | ||
} | ||
}; | ||
|
||
return <div ref={containerRef} className="h-full" />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.