CodeEditor.svelte 3.31 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
refac  
Timothy J. Baek committed
27
	export const formatPythonCodeHandler = async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
28
29
30
31
32
33
34
35
36
37
38
39
		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 }]
				});
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
40
41

				toast.success('Code formatted successfully');
Timothy J. Baek's avatar
Timothy J. Baek committed
42
43
44
45
46
47
48
				return true;
			}

			return false;
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
49
50
51
52
	let extensions = [
		basicSetup,
		keymap.of([{ key: 'Tab', run: acceptCompletion }, indentWithTab]),
		python(),
Timothy J. Baek's avatar
Timothy J. Baek committed
53
		indentUnit.of('    '),
Timothy J. Baek's avatar
Timothy J. Baek committed
54
55
56
57
58
59
60
61
62
		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
63
	onMount(() => {
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
64
65
		value = boilerplate;

Timothy J. Baek's avatar
Timothy J. Baek committed
66
67
68
		// Check if html class has dark mode
		isDarkMode = document.documentElement.classList.contains('dark');

Timothy J. Baek's avatar
Timothy J. Baek committed
69
		// python code editor, highlight python code
Timothy J. Baek's avatar
Timothy J. Baek committed
70
		codeEditor = new EditorView({
Timothy J. Baek's avatar
Timothy J. Baek committed
71
			state: EditorState.create({
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
72
				doc: value,
Timothy J. Baek's avatar
Timothy J. Baek committed
73
				extensions: extensions
Timothy J. Baek's avatar
Timothy J. Baek committed
74
75
76
			}),
			parent: document.getElementById('code-textarea')
		});
Timothy J. Baek's avatar
Timothy J. Baek committed
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
107
108
109
110

		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
111
112
113
114
115
116
117
118
119
120
121
122
123
		// 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
124
125
		return () => {
			observer.disconnect();
Timothy J. Baek's avatar
Timothy J. Baek committed
126
			document.removeEventListener('keydown', handleSave);
Timothy J. Baek's avatar
Timothy J. Baek committed
127
		};
Timothy J. Baek's avatar
Timothy J. Baek committed
128
129
130
	});
</script>

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