|
1 | 1 | <script>
|
2 | 2 | import { browser, dev } from '$app/environment';
|
3 |
| - import { onMount } from 'svelte'; |
| 3 | + import { onMount, tick } from 'svelte'; |
4 | 4 | import { basicSetup } from 'codemirror';
|
5 | 5 | import { EditorView, keymap } from '@codemirror/view';
|
6 | 6 | import { EditorState } from '@codemirror/state';
|
|
12 | 12 | import { tags } from '@lezer/highlight';
|
13 | 13 | import { HighlightStyle } from '@codemirror/language';
|
14 | 14 | import { syntaxHighlighting } from '@codemirror/language';
|
15 |
| - import { afterNavigate } from '$app/navigation'; |
| 15 | + import { afterNavigate, beforeNavigate } from '$app/navigation'; |
16 | 16 | import { files, selected_file, selected_name, update_file } from './state.js';
|
17 | 17 | import './codemirror.css';
|
18 | 18 |
|
|
30 | 30 | let container;
|
31 | 31 |
|
32 | 32 | let preserve_editor_focus = false;
|
| 33 | + let skip_reset = true; |
33 | 34 |
|
34 | 35 | /** @type {any} */
|
35 | 36 | let remove_focus_timeout;
|
|
40 | 41 | /** @type {import('@codemirror/view').EditorView} */
|
41 | 42 | let editor_view;
|
42 | 43 |
|
43 |
| - $: if (editor_view && $selected_name) { |
44 |
| - select_state($selected_name); |
| 44 | + const extensions = [ |
| 45 | + basicSetup, |
| 46 | + EditorState.tabSize.of(2), |
| 47 | + keymap.of([indentWithTab]), |
| 48 | + indentUnit.of('\t'), |
| 49 | + theme |
| 50 | + ]; |
| 51 | +
|
| 52 | + $: if (editor_view) select_state($selected_name); |
| 53 | +
|
| 54 | + $: reset($files); |
| 55 | +
|
| 56 | + /** @param {import('$lib/types').Stub[]} $files */ |
| 57 | + function reset($files) { |
| 58 | + if (skip_reset) return; |
| 59 | +
|
| 60 | + for (const file of $files) { |
| 61 | + if (file.type !== 'file') continue; |
| 62 | +
|
| 63 | + let state = editor_states.get(file.name); |
| 64 | +
|
| 65 | + if (state) { |
| 66 | + const existing = state.doc.toString(); |
| 67 | +
|
| 68 | + if (file.contents !== existing) { |
| 69 | + const transaction = state.update({ |
| 70 | + changes: { |
| 71 | + from: 0, |
| 72 | + to: existing.length, |
| 73 | + insert: file.contents |
| 74 | + } |
| 75 | + }); |
| 76 | +
|
| 77 | + editor_states.set(file.name, transaction.state); |
| 78 | + state = transaction.state; |
| 79 | +
|
| 80 | + if ($selected_name === file.name) { |
| 81 | + editor_view.setState(state); |
| 82 | + } |
| 83 | + } |
| 84 | + } else { |
| 85 | + let lang; |
| 86 | +
|
| 87 | + if (file.name.endsWith('.js') || file.name.endsWith('.json')) { |
| 88 | + lang = javascript(); |
| 89 | + } else if (file.name.endsWith('.html')) { |
| 90 | + lang = html(); |
| 91 | + } else if (file.name.endsWith('.svelte')) { |
| 92 | + lang = svelte(); |
| 93 | + } |
| 94 | +
|
| 95 | + state = EditorState.create({ |
| 96 | + doc: file.contents, |
| 97 | + extensions: lang ? [...extensions, lang] : extensions |
| 98 | + }); |
| 99 | +
|
| 100 | + editor_states.set(file.name, state); |
| 101 | + } |
| 102 | + } |
45 | 103 | }
|
46 | 104 |
|
47 |
| - /** @param {string} $selected_name */ |
| 105 | + /** @param {string | null} $selected_name */ |
48 | 106 | function select_state($selected_name) {
|
49 |
| - const file = $files.find((file) => file.name === $selected_name); |
50 |
| - if (file?.type !== 'file') return; |
51 |
| -
|
52 |
| - let state = editor_states.get(file.name); |
53 |
| - if (!state) { |
54 |
| - const extensions = [ |
55 |
| - basicSetup, |
56 |
| - EditorState.tabSize.of(2), |
57 |
| - keymap.of([indentWithTab]), |
58 |
| - indentUnit.of('\t'), |
59 |
| - theme |
60 |
| - ]; |
61 |
| -
|
62 |
| - if (file.name.endsWith('.js') || file.name.endsWith('.json')) { |
63 |
| - extensions.push(javascript()); |
64 |
| - } else if (file.name.endsWith('.html')) { |
65 |
| - extensions.push(html()); |
66 |
| - } else if (file.name.endsWith('.svelte')) { |
67 |
| - extensions.push(svelte()); |
68 |
| - } |
| 107 | + if (skip_reset) return; |
69 | 108 |
|
70 |
| - state = EditorState.create({ |
71 |
| - doc: file.contents, |
72 |
| - extensions |
| 109 | + const state = |
| 110 | + ($selected_name && editor_states.get($selected_name)) || |
| 111 | + EditorState.create({ |
| 112 | + doc: '', |
| 113 | + extensions: [EditorState.readOnly.of(true)] |
73 | 114 | });
|
74 | 115 |
|
75 |
| - editor_states.set(file.name, state); |
76 |
| - } |
77 |
| -
|
78 |
| - if (editor_view) { |
79 |
| - editor_view.setState(state); |
80 |
| - } |
| 116 | + editor_view.setState(state); |
81 | 117 | }
|
82 | 118 |
|
83 | 119 | onMount(() => {
|
|
101 | 137 |
|
102 | 138 | editor_view = new EditorView({
|
103 | 139 | parent: container,
|
104 |
| - dispatch(transaction) { |
| 140 | + async dispatch(transaction) { |
105 | 141 | editor_view.update([transaction]);
|
106 | 142 |
|
107 | 143 | if (transaction.docChanged && $selected_file) {
|
| 144 | + skip_reset = true; |
| 145 | +
|
108 | 146 | // TODO do we even need to update `$files`? maintaining separate editor states is probably sufficient
|
109 | 147 | update_file({
|
110 | 148 | ...$selected_file,
|
|
113 | 151 |
|
114 | 152 | // keep `editor_states` updated so that undo/redo history is preserved for files independently
|
115 | 153 | editor_states.set($selected_file.name, editor_view.state);
|
| 154 | +
|
| 155 | + await tick(); |
| 156 | + skip_reset = false; |
116 | 157 | }
|
117 | 158 | }
|
118 | 159 | });
|
|
126 | 167 | };
|
127 | 168 | });
|
128 | 169 |
|
| 170 | + beforeNavigate(() => { |
| 171 | + skip_reset = true; |
| 172 | + }); |
| 173 | +
|
129 | 174 | afterNavigate(() => {
|
| 175 | + skip_reset = false; |
| 176 | +
|
130 | 177 | editor_states.clear();
|
| 178 | + reset($files); |
| 179 | +
|
131 | 180 | select_state($selected_name);
|
132 | 181 | });
|
133 | 182 | </script>
|
|
214 | 263 | .fake-content {
|
215 | 264 | padding: 0 1rem;
|
216 | 265 | }
|
| 266 | +
|
| 267 | + @media (prefers-color-scheme: dark) { |
| 268 | + .fake * { |
| 269 | + color: #666; |
| 270 | + } |
| 271 | + } |
217 | 272 | </style>
|
0 commit comments