CodeEditor.svelte 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  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. export const formatPythonCodeHandler = async () => {
  20. if (codeEditor) {
  21. const res = await formatPythonCode(value).catch((error) => {
  22. toast.error(error);
  23. return null;
  24. });
  25. if (res && res.code) {
  26. const formattedCode = res.code;
  27. codeEditor.dispatch({
  28. changes: [{ from: 0, to: codeEditor.state.doc.length, insert: formattedCode }]
  29. });
  30. toast.success('Code formatted successfully');
  31. return true;
  32. }
  33. return false;
  34. }
  35. return false;
  36. };
  37. let extensions = [
  38. basicSetup,
  39. keymap.of([{ key: 'Tab', run: acceptCompletion }, indentWithTab]),
  40. python(),
  41. indentUnit.of(' '),
  42. placeholder('Enter your code here...'),
  43. EditorView.updateListener.of((e) => {
  44. if (e.docChanged) {
  45. value = e.state.doc.toString();
  46. }
  47. }),
  48. editorTheme.of([])
  49. ];
  50. onMount(() => {
  51. console.log(value);
  52. if (value === '') {
  53. value = boilerplate;
  54. }
  55. // Check if html class has dark mode
  56. isDarkMode = document.documentElement.classList.contains('dark');
  57. // python code editor, highlight python code
  58. codeEditor = new EditorView({
  59. state: EditorState.create({
  60. doc: value,
  61. extensions: extensions
  62. }),
  63. parent: document.getElementById('code-textarea')
  64. });
  65. if (isDarkMode) {
  66. codeEditor.dispatch({
  67. effects: editorTheme.reconfigure(oneDark)
  68. });
  69. }
  70. // listen to html class changes this should fire only when dark mode is toggled
  71. const observer = new MutationObserver((mutations) => {
  72. mutations.forEach((mutation) => {
  73. if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
  74. const _isDarkMode = document.documentElement.classList.contains('dark');
  75. if (_isDarkMode !== isDarkMode) {
  76. isDarkMode = _isDarkMode;
  77. if (_isDarkMode) {
  78. codeEditor.dispatch({
  79. effects: editorTheme.reconfigure(oneDark)
  80. });
  81. } else {
  82. codeEditor.dispatch({
  83. effects: editorTheme.reconfigure()
  84. });
  85. }
  86. }
  87. }
  88. });
  89. });
  90. observer.observe(document.documentElement, {
  91. attributes: true,
  92. attributeFilter: ['class']
  93. });
  94. const keydownHandler = async (e) => {
  95. if ((e.ctrlKey || e.metaKey) && e.key === 's') {
  96. e.preventDefault();
  97. dispatch('save');
  98. }
  99. // Format code when Ctrl + Shift + F is pressed
  100. if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'f') {
  101. e.preventDefault();
  102. await formatPythonCodeHandler();
  103. }
  104. };
  105. document.addEventListener('keydown', keydownHandler);
  106. return () => {
  107. observer.disconnect();
  108. document.removeEventListener('keydown', keydownHandler);
  109. };
  110. });
  111. </script>
  112. <div id="code-textarea" class="h-full w-full" />