CodeEditor.svelte 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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 { python } from '@codemirror/lang-python';
  9. import { oneDark } from '@codemirror/theme-one-dark';
  10. import { onMount, createEventDispatcher } from 'svelte';
  11. import { formatPythonCode } from '$lib/apis/utils';
  12. import { toast } from 'svelte-sonner';
  13. const dispatch = createEventDispatcher();
  14. export let boilerplate = '';
  15. export let value = '';
  16. let codeEditor;
  17. let isDarkMode = false;
  18. let editorTheme = new Compartment();
  19. const formatPythonCodeHandler = async () => {
  20. if (codeEditor) {
  21. console.log('formatPythonCodeHandler');
  22. const res = await formatPythonCode(value).catch((error) => {
  23. toast.error(error);
  24. return null;
  25. });
  26. if (res && res.code) {
  27. const formattedCode = res.code;
  28. codeEditor.dispatch({
  29. changes: [{ from: 0, to: codeEditor.state.doc.length, insert: formattedCode }]
  30. });
  31. return true;
  32. }
  33. return false;
  34. }
  35. };
  36. let extensions = [
  37. basicSetup,
  38. keymap.of([{ key: 'Tab', run: acceptCompletion }, indentWithTab]),
  39. python(),
  40. indentUnit.of(' '),
  41. placeholder('Enter your code here...'),
  42. EditorView.updateListener.of((e) => {
  43. if (e.docChanged) {
  44. value = e.state.doc.toString();
  45. }
  46. }),
  47. editorTheme.of([])
  48. ];
  49. onMount(() => {
  50. // Check if html class has dark mode
  51. isDarkMode = document.documentElement.classList.contains('dark');
  52. // python code editor, highlight python code
  53. codeEditor = new EditorView({
  54. state: EditorState.create({
  55. doc: boilerplate,
  56. extensions: extensions
  57. }),
  58. parent: document.getElementById('code-textarea')
  59. });
  60. if (isDarkMode) {
  61. codeEditor.dispatch({
  62. effects: editorTheme.reconfigure(oneDark)
  63. });
  64. }
  65. // listen to html class changes this should fire only when dark mode is toggled
  66. const observer = new MutationObserver((mutations) => {
  67. mutations.forEach((mutation) => {
  68. if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
  69. const _isDarkMode = document.documentElement.classList.contains('dark');
  70. if (_isDarkMode !== isDarkMode) {
  71. isDarkMode = _isDarkMode;
  72. if (_isDarkMode) {
  73. codeEditor.dispatch({
  74. effects: editorTheme.reconfigure(oneDark)
  75. });
  76. } else {
  77. codeEditor.dispatch({
  78. effects: editorTheme.reconfigure()
  79. });
  80. }
  81. }
  82. }
  83. });
  84. });
  85. observer.observe(document.documentElement, {
  86. attributes: true,
  87. attributeFilter: ['class']
  88. });
  89. // Add a keyboard shortcut to format the code when Ctrl/Cmd + S is pressed
  90. // Override the default browser save functionality
  91. const handleSave = (e) => {
  92. if ((e.ctrlKey || e.metaKey) && e.key === 's') {
  93. e.preventDefault();
  94. formatPythonCodeHandler();
  95. dispatch('save');
  96. }
  97. };
  98. document.addEventListener('keydown', handleSave);
  99. return () => {
  100. observer.disconnect();
  101. document.removeEventListener('keydown', handleSave);
  102. };
  103. });
  104. </script>
  105. <div id="code-textarea" class="h-full w-full" />