<script lang="ts"> import { basicSetup, EditorView } from 'codemirror'; import { keymap, placeholder } from '@codemirror/view'; import { Compartment, EditorState } from '@codemirror/state'; import { acceptCompletion } from '@codemirror/autocomplete'; import { indentWithTab } from '@codemirror/commands'; import { indentUnit } from '@codemirror/language'; import { languages } from '@codemirror/language-data'; import { oneDark } from '@codemirror/theme-one-dark'; import { onMount, createEventDispatcher, getContext, tick } from 'svelte'; import { formatPythonCode } from '$lib/apis/utils'; import { toast } from 'svelte-sonner'; const dispatch = createEventDispatcher(); const i18n = getContext('i18n'); export let boilerplate = ''; export let value = ''; let _value = ''; $: if (value) { updateValue(); } const updateValue = () => { if (_value !== value) { _value = value; if (codeEditor) { codeEditor.dispatch({ changes: [{ from: 0, to: codeEditor.state.doc.length, insert: _value }] }); } } }; export let id = ''; export let lang = ''; let codeEditor; let isDarkMode = false; let editorTheme = new Compartment(); let editorLanguage = new Compartment(); const getLang = async () => { const language = languages.find((l) => l.alias.includes(lang)); return await language?.load(); }; export const formatPythonCodeHandler = async () => { if (codeEditor) { const res = await formatPythonCode(_value).catch((error) => { toast.error(error); return null; }); if (res && res.code) { const formattedCode = res.code; codeEditor.dispatch({ changes: [{ from: 0, to: codeEditor.state.doc.length, insert: formattedCode }] }); _value = formattedCode; dispatch('change', { value: _value }); await tick(); toast.success($i18n.t('Code formatted successfully')); return true; } return false; } return false; }; let extensions = [ basicSetup, keymap.of([{ key: 'Tab', run: acceptCompletion }, indentWithTab]), indentUnit.of(' '), placeholder('Enter your code here...'), EditorView.updateListener.of((e) => { if (e.docChanged) { _value = e.state.doc.toString(); dispatch('change', { value: _value }); } }), editorTheme.of([]), editorLanguage.of([]) ]; $: if (lang) { setLanguage(); } const setLanguage = async () => { const language = await getLang(); if (language && codeEditor) { codeEditor.dispatch({ effects: editorLanguage.reconfigure(language) }); } }; onMount(() => { console.log(value); if (value === '') { value = boilerplate; } _value = value; // Check if html class has dark mode isDarkMode = document.documentElement.classList.contains('dark'); // python code editor, highlight python code codeEditor = new EditorView({ state: EditorState.create({ doc: _value, extensions: extensions }), parent: document.getElementById(`code-textarea-${id}`) }); if (isDarkMode) { codeEditor.dispatch({ effects: editorTheme.reconfigure(oneDark) }); } // listen to html class changes this should fire only when dark mode is toggled const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'attributes' && mutation.attributeName === 'class') { const _isDarkMode = document.documentElement.classList.contains('dark'); if (_isDarkMode !== isDarkMode) { isDarkMode = _isDarkMode; if (_isDarkMode) { codeEditor.dispatch({ effects: editorTheme.reconfigure(oneDark) }); } else { codeEditor.dispatch({ effects: editorTheme.reconfigure() }); } } } }); }); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); const keydownHandler = async (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); dispatch('save'); } // Format code when Ctrl + Shift + F is pressed if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'f') { e.preventDefault(); await formatPythonCodeHandler(); } }; document.addEventListener('keydown', keydownHandler); return () => { observer.disconnect(); document.removeEventListener('keydown', keydownHandler); }; }); </script> <div id="code-textarea-{id}" class="h-full w-full" />