CodeEditor.svelte 3.42 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
fix  
Timothy J. Baek committed
13
	import { onMount, createEventDispatcher, getContext } from 'svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
14
15
16
17
	import { formatPythonCode } from '$lib/apis/utils';
	import { toast } from 'svelte-sonner';

	const dispatch = createEventDispatcher();
Timothy J. Baek's avatar
fix  
Timothy J. Baek committed
18
	const $i18n = getContext('i18n');
Timothy J. Baek's avatar
Timothy J. Baek committed
19

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

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

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

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

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

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
Timothy J. Baek committed
64
65
66
67
		console.log(value);
		if (value === '') {
			value = boilerplate;
		}
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
68

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

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

		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
refac  
Timothy J. Baek committed
114
		const keydownHandler = async (e) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
115
116
			if ((e.ctrlKey || e.metaKey) && e.key === 's') {
				e.preventDefault();
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
117
				dispatch('save');
Timothy J. Baek's avatar
Timothy J. Baek committed
118
			}
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
119
120
121
122
123
124

			// Format code when Ctrl + Shift + F is pressed
			if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'f') {
				e.preventDefault();
				await formatPythonCodeHandler();
			}
Timothy J. Baek's avatar
Timothy J. Baek committed
125
126
		};

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
127
		document.addEventListener('keydown', keydownHandler);
Timothy J. Baek's avatar
Timothy J. Baek committed
128

Timothy J. Baek's avatar
Timothy J. Baek committed
129
130
		return () => {
			observer.disconnect();
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
131
			document.removeEventListener('keydown', keydownHandler);
Timothy J. Baek's avatar
Timothy J. Baek committed
132
		};
Timothy J. Baek's avatar
Timothy J. Baek committed
133
134
135
	});
</script>

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