| <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, LanguageDescription } 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 = ''; |
| |
| export let onSave = () => {}; |
| export let onChange = () => {}; |
| |
| let _value = ''; |
| |
| $: if (value) { |
| updateValue(); |
| } |
| |
| const updateValue = () => { |
| if (_value !== value) { |
| const changes = findChanges(_value, value); |
| _value = value; |
| |
| if (codeEditor && changes.length > 0) { |
| codeEditor.dispatch({ changes }); |
| } |
| } |
| }; |
| |
| |
| |
| |
| function findChanges(oldStr, newStr) { |
| let changes = []; |
| let oldIndex = 0, |
| newIndex = 0; |
| |
| while (oldIndex < oldStr.length || newIndex < newStr.length) { |
| if (oldStr[oldIndex] !== newStr[newIndex]) { |
| let start = oldIndex; |
| |
| // Identify the changed portion |
| while (oldIndex < oldStr.length && oldStr[oldIndex] !== newStr[newIndex]) { |
| oldIndex++; |
| } |
| while (newIndex < newStr.length && newStr[newIndex] !== oldStr[start]) { |
| newIndex++; |
| } |
| |
| changes.push({ |
| from: start, |
| to: oldIndex, // Replace the differing part |
| insert: newStr.substring(start, newIndex) |
| }); |
| } else { |
| oldIndex++; |
| newIndex++; |
| } |
| } |
| |
| return changes; |
| } |
| |
| export let id = ''; |
| export let lang = ''; |
| |
| let codeEditor; |
| |
| export const focus = () => { |
| codeEditor.focus(); |
| }; |
| |
| let isDarkMode = false; |
| let editorTheme = new Compartment(); |
| let editorLanguage = new Compartment(); |
| |
| languages.push( |
| LanguageDescription.of({ |
| name: 'HCL', |
| extensions: ['hcl', 'tf'], |
| load() { |
| return import('codemirror-lang-hcl').then((m) => m.hcl()); |
| } |
| }) |
| ); |
| languages.push( |
| LanguageDescription.of({ |
| name: 'Elixir', |
| extensions: ['ex', 'exs'], |
| load() { |
| return import('codemirror-lang-elixir').then((m) => m.elixir()); |
| } |
| }) |
| ); |
| |
| 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(localStorage.token, _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; |
| onChange(_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(); |
| onChange(_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; |
| |
| |
| isDarkMode = document.documentElement.classList.contains('dark'); |
| |
| |
| codeEditor = new EditorView({ |
| state: EditorState.create({ |
| doc: _value, |
| extensions: extensions |
| }), |
| parent: document.getElementById(`code-textarea-${id}`) |
| }); |
| |
| if (isDarkMode) { |
| codeEditor.dispatch({ |
| effects: editorTheme.reconfigure(oneDark) |
| }); |
| } |
| |
| |
| 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(); |
| |
| onSave(); |
| } |
| |
| |
| 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 text-sm" /> |
|
|