CodeEditor.svelte 3.23 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
2
3
<script lang="ts">
	import { basicSetup, EditorView } from 'codemirror';
	import { keymap, placeholder } from '@codemirror/view';
Timothy J. Baek's avatar
Timothy J. Baek committed
4
	import { Compartment, EditorState } from '@codemirror/state';
Timothy J. Baek's avatar
Timothy J. Baek committed
5
6
7
8

	import { acceptCompletion } from '@codemirror/autocomplete';
	import { indentWithTab } from '@codemirror/commands';

Timothy J. Baek's avatar
Timothy J. Baek committed
9
	import { indentUnit } from '@codemirror/language';
Timothy J. Baek's avatar
Timothy J. Baek committed
10
11
12
	import { python } from '@codemirror/lang-python';
	import { oneDark } from '@codemirror/theme-one-dark';

Timothy J. Baek's avatar
Timothy J. Baek committed
13
14
15
16
17
	import { onMount, createEventDispatcher } from 'svelte';
	import { formatPythonCode } from '$lib/apis/utils';
	import { toast } from 'svelte-sonner';

	const dispatch = createEventDispatcher();
Timothy J. Baek's avatar
Timothy J. Baek committed
18

Timothy J. Baek's avatar
Timothy J. Baek committed
19
	export let boilerplate = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
20
21
	export let value = '';

Timothy J. Baek's avatar
Timothy J. Baek committed
22
23
24
25
26
	let codeEditor;

	let isDarkMode = false;
	let editorTheme = new Compartment();

Timothy J. Baek's avatar
Timothy J. Baek committed
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
	const formatPythonCodeHandler = async () => {
		if (codeEditor) {
			console.log('formatPythonCodeHandler');
			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 }]
				});
				return true;
			}

			return false;
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
47
48
49
50
	let extensions = [
		basicSetup,
		keymap.of([{ key: 'Tab', run: acceptCompletion }, indentWithTab]),
		python(),
Timothy J. Baek's avatar
Timothy J. Baek committed
51
		indentUnit.of('    '),
Timothy J. Baek's avatar
Timothy J. Baek committed
52
53
54
55
56
57
58
59
60
		placeholder('Enter your code here...'),
		EditorView.updateListener.of((e) => {
			if (e.docChanged) {
				value = e.state.doc.toString();
			}
		}),
		editorTheme.of([])
	];

Timothy J. Baek's avatar
Timothy J. Baek committed
61
	onMount(() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
62
63
64
		// Check if html class has dark mode
		isDarkMode = document.documentElement.classList.contains('dark');

Timothy J. Baek's avatar
Timothy J. Baek committed
65
		// python code editor, highlight python code
Timothy J. Baek's avatar
Timothy J. Baek committed
66
		codeEditor = new EditorView({
Timothy J. Baek's avatar
Timothy J. Baek committed
67
			state: EditorState.create({
Timothy J. Baek's avatar
Timothy J. Baek committed
68
				doc: boilerplate,
Timothy J. Baek's avatar
Timothy J. Baek committed
69
				extensions: extensions
Timothy J. Baek's avatar
Timothy J. Baek committed
70
71
72
			}),
			parent: document.getElementById('code-textarea')
		});
Timothy J. Baek's avatar
Timothy J. Baek committed
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

		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']
		});

Timothy J. Baek's avatar
Timothy J. Baek committed
107
108
109
110
111
112
113
114
115
116
117
118
119
		// Add a keyboard shortcut to format the code when Ctrl/Cmd + S is pressed
		// Override the default browser save functionality

		const handleSave = (e) => {
			if ((e.ctrlKey || e.metaKey) && e.key === 's') {
				e.preventDefault();
				formatPythonCodeHandler();
				dispatch('save');
			}
		};

		document.addEventListener('keydown', handleSave);

Timothy J. Baek's avatar
Timothy J. Baek committed
120
121
		return () => {
			observer.disconnect();
Timothy J. Baek's avatar
Timothy J. Baek committed
122
			document.removeEventListener('keydown', handleSave);
Timothy J. Baek's avatar
Timothy J. Baek committed
123
		};
Timothy J. Baek's avatar
Timothy J. Baek committed
124
125
126
	});
</script>

Timothy J. Baek's avatar
Timothy J. Baek committed
127
<div id="code-textarea" class="h-full w-full" />