CodeEditor.svelte 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <script lang="ts">
  2. import { basicSetup, EditorView } from 'codemirror';
  3. import { keymap, placeholder } from '@codemirror/view';
  4. import { Compartment, EditorState } from '@codemirror/state';
  5. import { acceptCompletion } from '@codemirror/autocomplete';
  6. import { indentWithTab } from '@codemirror/commands';
  7. import { indentUnit } from '@codemirror/language';
  8. import { languages } from '@codemirror/language-data';
  9. // import { python } from '@codemirror/lang-python';
  10. // import { javascript } from '@codemirror/lang-javascript';
  11. import { oneDark } from '@codemirror/theme-one-dark';
  12. import { onMount, createEventDispatcher, getContext } from 'svelte';
  13. import { formatPythonCode } from '$lib/apis/utils';
  14. import { toast } from 'svelte-sonner';
  15. const dispatch = createEventDispatcher();
  16. const i18n = getContext('i18n');
  17. export let boilerplate = '';
  18. export let value = '';
  19. let _value = '';
  20. $: if (value) {
  21. updateValue();
  22. }
  23. const updateValue = () => {
  24. _value = value;
  25. if (codeEditor) {
  26. codeEditor.dispatch({
  27. changes: [{ from: 0, to: codeEditor.state.doc.length, insert: _value }]
  28. });
  29. }
  30. };
  31. export let id = '';
  32. export let lang = '';
  33. let codeEditor;
  34. let isDarkMode = false;
  35. let editorTheme = new Compartment();
  36. let editorLanguage = new Compartment();
  37. const getLang = async () => {
  38. console.log(languages);
  39. const language = languages.find((l) => l.alias.includes(lang));
  40. return await language?.load();
  41. };
  42. export const formatPythonCodeHandler = async () => {
  43. if (codeEditor) {
  44. const res = await formatPythonCode(_value).catch((error) => {
  45. toast.error(error);
  46. return null;
  47. });
  48. if (res && res.code) {
  49. const formattedCode = res.code;
  50. codeEditor.dispatch({
  51. changes: [{ from: 0, to: codeEditor.state.doc.length, insert: formattedCode }]
  52. });
  53. toast.success($i18n.t('Code formatted successfully'));
  54. return true;
  55. }
  56. return false;
  57. }
  58. return false;
  59. };
  60. let extensions = [
  61. basicSetup,
  62. keymap.of([{ key: 'Tab', run: acceptCompletion }, indentWithTab]),
  63. indentUnit.of(' '),
  64. placeholder('Enter your code here...'),
  65. EditorView.updateListener.of((e) => {
  66. if (e.docChanged) {
  67. _value = e.state.doc.toString();
  68. dispatch('change', { value: _value });
  69. }
  70. }),
  71. editorTheme.of([]),
  72. editorLanguage.of([])
  73. ];
  74. $: if (lang) {
  75. setLanguage();
  76. }
  77. const setLanguage = async () => {
  78. const language = await getLang();
  79. if (language) {
  80. codeEditor.dispatch({
  81. effects: editorLanguage.reconfigure(language)
  82. });
  83. }
  84. };
  85. onMount(() => {
  86. console.log(value);
  87. if (value === '') {
  88. value = boilerplate;
  89. }
  90. _value = value;
  91. // Check if html class has dark mode
  92. isDarkMode = document.documentElement.classList.contains('dark');
  93. // python code editor, highlight python code
  94. codeEditor = new EditorView({
  95. state: EditorState.create({
  96. doc: _value,
  97. extensions: extensions
  98. }),
  99. parent: document.getElementById(`code-textarea-${id}`)
  100. });
  101. if (isDarkMode) {
  102. codeEditor.dispatch({
  103. effects: editorTheme.reconfigure(oneDark)
  104. });
  105. }
  106. // listen to html class changes this should fire only when dark mode is toggled
  107. const observer = new MutationObserver((mutations) => {
  108. mutations.forEach((mutation) => {
  109. if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
  110. const _isDarkMode = document.documentElement.classList.contains('dark');
  111. if (_isDarkMode !== isDarkMode) {
  112. isDarkMode = _isDarkMode;
  113. if (_isDarkMode) {
  114. codeEditor.dispatch({
  115. effects: editorTheme.reconfigure(oneDark)
  116. });
  117. } else {
  118. codeEditor.dispatch({
  119. effects: editorTheme.reconfigure()
  120. });
  121. }
  122. }
  123. }
  124. });
  125. });
  126. observer.observe(document.documentElement, {
  127. attributes: true,
  128. attributeFilter: ['class']
  129. });
  130. const keydownHandler = async (e) => {
  131. if ((e.ctrlKey || e.metaKey) && e.key === 's') {
  132. e.preventDefault();
  133. dispatch('save');
  134. }
  135. // Format code when Ctrl + Shift + F is pressed
  136. if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'f') {
  137. e.preventDefault();
  138. await formatPythonCodeHandler();
  139. }
  140. };
  141. document.addEventListener('keydown', keydownHandler);
  142. return () => {
  143. observer.disconnect();
  144. document.removeEventListener('keydown', keydownHandler);
  145. };
  146. });
  147. </script>
  148. <div id="code-textarea-{id}" class="h-full w-full" />