🐍🐍🐍
1<script lang="ts">
2 // @ts-nocheck
3 // typescript hates what we're doing here
4
5 import { onMount } from 'svelte';
6 import Transmitter from '../../wiring/transmitter.svelte';
7
8 Prism.languages['fictionscript'] = {
9 'fic-comment': /^\s*#.*/,
10 'message': {
11 pattern: /^\s*(.|\n)*/,
12 inside: {
13 'method-no-at': {
14 pattern: /<[^@]*>.*/,
15 inside: {
16 'fic-variable': {
17 pattern: /(<)[^\+-\?>]*/,
18 lookbehind: true,
19 },
20 //'operator': /(\+\+|--|\+|-|\?|\?\?)\s*>/,
21 'fic-brackets': /(<|@|(|\+\+|--|\+|-|\?|\?\?)\s*>)/,
22 }
23 },
24 'method-at': {
25 pattern: /<.*@.*>.*/,
26 inside: {
27 'fic-variable': {
28 pattern: /(@)[^\+-\?>@]*/,
29 lookbehind: true,
30 },
31 'fic-method': {
32 pattern: /(<)[^@>]*(?<![@>])/,
33 lookbehind: true,
34 },
35 //'operator': /(\+\+|--|\+|-|\?|\?\?)\s*>/,
36 'fic-brackets': /(<|@|(|\+\+|--|\+|-|\?|\?\?)\s*>)/,
37 }
38 },
39 'set-literal': {
40 pattern: /(\.|arg|var|insert).*?:=.*/,
41 inside: {
42 'fic-string': {
43 pattern: /(\:=).*/,
44 lookbehind: true,
45 },
46 'fic-variable': {
47 pattern: /(\.)[^.:]*/,
48 lookbehind: true,
49 greedy: true,
50 },
51 'fic-accessors': /(\.|args?|var|insert|:=)/,
52 }
53 },
54 'set': {
55 pattern: /(\.|arg|var|insert).*?=.*/,
56 inside: {
57 'expression': {
58 pattern: /(\=).*/,
59 lookbehind: true,
60 inside: null
61 },
62 'fic-variable': {
63 pattern: /(\.)[^.=]*/,
64 lookbehind: true,
65 greedy: true,
66 },
67 'fic-accessors': /(\.|arg|var|insert|=)/,
68 }
69 },
70 'get': {
71 pattern: /(\.|args?|retrieve).*/,
72 inside: {
73 'fic-variable': {
74 pattern: /(\.)(\s|[^.(\?\s*$)])*/,
75 lookbehind: true,
76 greedy: true,
77 },
78 'fic-accessors': /(\.|args?|retrieve|=|\?)/,
79 }
80 }
81 }
82 }
83 //'number': /(\.|arg|var|insert|retrieve)(?:\s*(.*?)\s*(\.))*?\s*(.*?)\s*(:=).*\n.*/,
84 };
85
86 Prism.languages['fictionscript']['message'].inside['set'].inside['expression'].inside = Prism.languages['fictionscript']['message'].inside;
87
88 /**
89 * @param {string} text
90 */
91 function update(text) {
92 // Handle final newlines (see article)
93 if(text[text.length-1] == "\n") {
94 text += " ";
95 }
96 // Update code
97 code_display_content_element.innerHTML = text.replace(new RegExp("&", "g"), "&").replace(new RegExp("<", "g"), "<"); /* Global RegExp */
98 // Syntax Highlight
99 Prism.highlightElement(code_display_content_element);
100 }
101
102 /**
103 * @param {HTMLTextAreaElement} element
104 */
105 function sync_scroll(element) {
106 /* Scroll result to scroll coords of event - sync with textarea */
107 // Get and set x and y
108 code_display_element.scrollTop = element.scrollTop;
109 code_display_element.scrollLeft = element.scrollLeft;
110 }
111
112 /**
113 * @param {{ value: string; selectionStart: number; selectionEnd: number; }} element
114 * @param {{ key: string; preventDefault: () => void; }} event
115 */
116 function on_key(element, event) {
117 let code = element.value;
118 if(event.key == "Tab") {
119 /* Tab key pressed */
120 event.preventDefault(); // stop normal
121 let before_tab = code.slice(0, element.selectionStart); // text before tab
122 let after_tab = code.slice(element.selectionEnd, element.value.length); // text after tab
123 let cursor_pos = element.selectionStart + 1; // where cursor moves after tab - moving forward by 1 char to after tab
124 element.value = before_tab + "\t" + after_tab; // add tab char
125 // move cursor
126 element.selectionStart = cursor_pos;
127 element.selectionEnd = cursor_pos;
128 update(element.value); // Update text to include indent
129 }
130
131 if(!event.shiftKey && (event.key === "Enter" || event.keyCode === 13)) {
132 onSubmit(code);
133 //transmitter.send({ schema: "script", code: code, language: "fictionscript" });
134 transmitter.send({ schema: "command", command: code });
135 element.value = "";
136 update("");
137 event.preventDefault();
138 }
139 }
140
141 /**
142 * @type {HTMLTextAreaElement}
143 */
144 let code_input_element;
145
146 /**
147 * @type {HTMLPreElement}
148 */
149 let code_display_element;
150
151 /**
152 * @type {HTMLCodeElement}
153 */
154 let code_display_content_element;
155
156 let transmitter;
157
158 export let width = "100%";
159 export let height = "100%";
160 export let padding = "10";
161
162 export let font_size = "1em";
163
164 export let code = "";
165
166 export let onSubmit = (value) => {};
167
168 let code_style = `width: calc(100% - ${padding * 2}px); height: calc(100% - ${padding * 2}px); top: ${padding}px; left: ${padding}px; font-size: ${font_size} !important;`;
169
170 onMount(async () => {
171 update(code);
172 });
173</script>
174
175<div class=wrapper
176 style="width: {width}; height: {height}; font-size: {font_size} !important;">
177 <div class=backdrop></div>
178 <textarea
179 placeholder="..."
180 class=code-input
181 style={code_style}
182 spellcheck=false
183 bind:this={code_input_element}
184 bind:value={code}
185 on:input={() => {update(code_input_element.value); sync_scroll(code_input_element);}}
186 on:scroll={() => {sync_scroll(code_input_element);}}
187 on:keydown={(event) => {on_key(code_input_element, event);}}
188 />
189
190 <pre class="code-display" style={code_style} aria-hidden=true bind:this={code_display_element}>
191 <code class="language-fictionscript code-display-content"
192 bind:this={code_display_content_element} />
193 </pre>
194
195 <Transmitter schemas={["command"]} bind:this={transmitter} style="right: -1.5em;" />
196</div>
197
198<svelte:head>
199 <link rel="stylesheet" href="css/prism.css" />
200 <link rel="stylesheet" href="css/syntax.css" />
201</svelte:head>
202
203<style>
204 /* adapted from https://css-tricks.com/creating-an-editable-textarea-that-supports-syntax-highlighted-code/ */
205
206 .wrapper {
207 cursor: default;
208 position: relative;
209 display: flex;
210 }
211
212 .backdrop {
213 position: absolute;
214 top: 0;
215 left: 0;
216 margin: 0;
217 padding: 0;
218 border: 0;
219 width: 100%;
220 height: 100%;
221 background-color: var(--code-editor-background);
222 }
223
224 .code-input, .code-display {
225 /* Both elements need the same text and space styling so they are directly on top of each other */
226 margin: 0;
227 padding: 0;
228 border: 0;
229 border-radius: 0;
230 }
231
232 pre {
233 box-shadow: none;
234 background-color: none !important;
235 }
236
237 code {
238 background-color: none !important;
239 }
240
241 .code-input:focus {
242 border: 0;
243 outline: 0;
244 }
245
246 ::-webkit-scrollbar {
247 width: 10px;
248 position: absolute;
249 right: 10px;
250 }
251
252 ::-webkit-scrollbar-track {
253 background: #344;
254 cursor: pointer !important;
255 }
256
257 ::-webkit-scrollbar-thumb {
258 background: #788;
259 cursor: pointer !important;
260 }
261
262 ::-webkit-scrollbar-track:hover, ::-webkit-scrollbar-thumb:hover {
263 cursor: pointer !important;
264 user-select: none;
265 }
266
267 .code-input, .code-display, .code-display * {
268 /* Also add text styles to highlighing tokens */
269 font-family: var(--code-font);
270 line-height: 1.5em !important;
271 tab-size: 4;
272 }
273
274 .code-input, .code-display {
275 /* In the same place */
276 position: absolute;
277 }
278
279 /* Move the textarea in front of the result */
280
281 .code-input {
282 z-index: 2;
283 }
284 .code-display {
285 z-index: 1;
286 }
287 .backdrop {
288 z-index: 0;
289 }
290
291
292 /* Make textarea almost completely transparent */
293
294 .code-input {
295 color: transparent;
296 background: transparent;
297 caret-color: white;
298
299 /* Can be scrolled */
300 overflow: auto;
301 white-space: pre-wrap;
302 word-wrap: break-word;
303
304 /* Fix cursor of scrollbar */
305 cursor: auto;
306 }
307
308 .code-display {
309 overflow: auto;
310 white-space: normal;
311 word-wrap: break-word;
312 user-select: none;
313 }
314
315 /* No resize on textarea */
316 .code-input {
317 resize: none;
318 }
319
320 /* Paragraphs; First Image */
321 * {
322 font-family: var(--code-font);
323 }
324
325 /* Syntax Highlighting from prism.js starts below, partly modified: */
326
327 /* PrismJS 1.23.0
328 https://prismjs.com/download.html#themes=prism-funky&languages=markup */
329 /**
330 * prism.js Funky theme
331 * Based on “Polyfilling the gaps” talk slides http://lea.verou.me/polyfilling-the-gaps/
332 * @author Lea Verou
333 */
334
335 code[class*="language-"] {
336 font-family: var(--code-font);
337 text-align: left;
338 white-space: pre-wrap;
339 word-spacing: normal;
340 word-break: normal;
341 word-wrap: break-word;
342 line-height: 1.5;
343
344 border: 0;
345
346 -moz-tab-size: 4;
347 -o-tab-size: 4;
348 tab-size: 4;
349
350 -webkit-hyphens: none;
351 -moz-hyphens: none;
352 -ms-hyphens: none;
353 hyphens: none;
354 }
355
356 /* Inline code */
357 :not(pre) > code[class*="language-"] {
358 padding: .2em;
359 border-radius: .3em;
360 white-space: normal;
361 }
362
363 pre[class*="code-display"] {
364 background-color: transparent;
365 }
366</style>