CodeEditor.svelte 3.3 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
		if (codeEditor) {
			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
39
40

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

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

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

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

		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
113
114
115
		// Add a keyboard shortcut to format the code when Ctrl/Cmd + S is pressed
		// Override the default browser save functionality

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
116
		const handleSave = async (e) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
117
118
			if ((e.ctrlKey || e.metaKey) && e.key === 's') {
				e.preventDefault();
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
119
				dispatch('save');
Timothy J. Baek's avatar
Timothy J. Baek committed
120
121
122
123
124
			}
		};

		document.addEventListener('keydown', handleSave);

Timothy J. Baek's avatar
Timothy J. Baek committed
125
126
		return () => {
			observer.disconnect();
Timothy J. Baek's avatar
Timothy J. Baek committed
127
			document.removeEventListener('keydown', handleSave);
Timothy J. Baek's avatar
Timothy J. Baek committed
128
		};
Timothy J. Baek's avatar
Timothy J. Baek committed
129
130
131
	});
</script>

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