https://github.com/akkartik/mu/blob/master/edit/004-programming-environment.mu
1
2
3
4
5
6 def! main [
7 local-scope
8 open-console
9 clear-screen null/screen
10 env:&:environment <- new-programming-environment null/filesystem, null/screen
11 render-all null/screen, env, render
12 event-loop null/screen, null/console, env, null/filesystem
13 ]
14
15 container environment [
16 recipes:&:editor
17 current-sandbox:&:editor
18 sandbox-in-focus?:bool
19 ]
20
21 def new-programming-environment resources:&:resources, screen:&:screen, test-sandbox-editor-contents:text -> result:&:environment [
22 local-scope
23 load-inputs
24 width:num <- screen-width screen
25 result <- new environment:type
26
27 initial-recipe-contents:text <- slurp resources, [lesson/recipes.mu]
28 divider:num, _ <- divide-with-remainder width, 2
29 recipes:&:editor <- new-editor initial-recipe-contents, 0/left, divider/right
30
31 sandbox-left:num <- add divider, 1
32 current-sandbox:&:editor <- new-editor test-sandbox-editor-contents, sandbox-left, width/right
33 *result <- put *result, recipes:offset, recipes
34 *result <- put *result, current-sandbox:offset, current-sandbox
35 *result <- put *result, sandbox-in-focus?:offset, false
36 <programming-environment-initialization>
37 ]
38
39 def event-loop screen:&:screen, console:&:console, env:&:environment, resources:&:resources -> screen:&:screen, console:&:console, env:&:environment, resources:&:resources [
40 local-scope
41 load-inputs
42 recipes:&:editor <- get *env, recipes:offset
43 current-sandbox:&:editor <- get *env, current-sandbox:offset
44 sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset
45
46
47
48 render-recipes-on-no-more-events?:bool <- copy false
49 render-sandboxes-on-no-more-events?:bool <- copy false
50 {
51
52 +next-event
53 e:event, found?:bool, quit?:bool, console <- read-event console
54 loop-unless found?
55 break-if quit?
56 trace 10, [app], [next-event]
57 <handle-event>
58
59 {
60 k:num, is-keycode?:bool <- maybe-convert e:event, keycode:variant
61 break-unless is-keycode?
62 <global-keypress>
63 }
64 {
65 c:char, is-unicode?:bool <- maybe-convert e:event, text:variant
66 break-unless is-unicode?
67 <global-type>
68 }
69
70 {
71 t:touch-event, is-touch?:bool <- maybe-convert e:event, touch:variant
72 break-unless is-touch?
73
74
75 touch-type:num <- get t, type:offset
76 is-left-click?:bool <- equal touch-type, 65513/mouse-left
77 loop-unless is-left-click?, +next-event
78 click-row:num <- get t, row:offset
79 click-column:num <- get t, column:offset
80
81 <global-touch>
82
83 _ <- move-cursor recipes, screen, t
84 sandbox-in-focus?:bool <- move-cursor current-sandbox, screen, t
85 *env <- put *env, sandbox-in-focus?:offset, sandbox-in-focus?
86 screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
87 loop +next-event
88 }
89
90
91 {
92 r:resize-event, is-resize?:bool <- maybe-convert e:event, resize:variant
93 break-unless is-resize?
94 env, screen <- resize screen, env
95 screen <- render-all screen, env, render-without-moving-cursor
96 loop +next-event
97 }
98
99 {
100 sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset
101 {
102 break-if sandbox-in-focus?
103 render?:bool <- handle-keyboard-event screen, recipes, e:event
104 render-recipes-on-no-more-events? <- or render?, render-recipes-on-no-more-events?
105 }
106 {
107 break-unless sandbox-in-focus?
108 render?:bool <- handle-keyboard-event screen, current-sandbox, e:event
109 render-sandboxes-on-no-more-events? <- or render?, render-sandboxes-on-no-more-events?
110 }
111 more-events?:bool <- has-more-events? console
112 {
113 break-if more-events?
114 {
115 break-unless render-recipes-on-no-more-events?
116 render-recipes-on-no-more-events? <- copy false
117 screen <- render-recipes screen, env, render
118 }
119 {
120 break-unless render-sandboxes-on-no-more-events?
121 render-sandboxes-on-no-more-events? <- copy false
122 screen <- render-sandbox-side screen, env, render
123 }
124 }
125 screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
126 }
127 loop
128 }
129 ]
130
131 def resize screen:&:screen, env:&:environment -> env:&:environment, screen:&:screen [
132 local-scope
133 load-inputs
134 clear-screen screen
135 width:num <- screen-width screen
136 divider:num, _ <- divide-with-remainder width, 2
137
138 recipes:&:editor <- get *env, recipes:offset
139 right:num <- subtract divider, 1
140 *recipes <- put *recipes, right:offset, right
141
142 *recipes <- put *recipes, cursor-row:offset, 1
143 *recipes <- put *recipes, cursor-column:offset, 0
144
145 current-sandbox:&:editor <- get *env, current-sandbox:offset
146 left:num <- add divider, 1
147 *current-sandbox <- put *current-sandbox, left:offset, left
148 right:num <- subtract width, 1
149 *current-sandbox <- put *current-sandbox, right:offset, right
150
151 *current-sandbox <- put *current-sandbox, cursor-row:offset, 1
152 *current-sandbox <- put *current-sandbox, cursor-column:offset, left
153 ]
154
155
156
157
158 def render-without-moving-cursor screen:&:screen, editor:&:editor -> last-row:num, last-column:num, screen:&:screen, editor:&:editor [
159 local-scope
160 load-inputs
161 return-unless editor, 1/top, 0/left
162 left:num <- get *editor, left:offset
163 screen-height:num <- screen-height screen
164 right:num <- get *editor, right:offset
165 curr:&:duplex-list:char <- get *editor, top-of-screen:offset
166 prev:&:duplex-list:char <- copy curr
167 curr <- next curr
168 color:num <- copy 7/white
169 row:num <- copy 1/top
170 column:num <- copy left
171
172 old-before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
173
174
175 *editor <- put *editor, cursor-row:offset, row
176 *editor <- put *editor, cursor-column:offset, column
177 top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
178 *editor <- put *editor, before-cursor:offset, top-of-screen
179 screen <- move-cursor screen, row, column
180 {
181 +next-character
182 break-unless curr
183 off-screen?:bool <- greater-or-equal row, screen-height
184 break-if off-screen?
185
186
187
188 {
189 at-cursor?:bool <- equal old-before-cursor, prev
190 break-unless at-cursor?
191 *editor <- put *editor, cursor-row:offset, row
192 *editor <- put *editor, cursor-column:offset, column
193 *editor <- put *editor, before-cursor:offset, old-before-cursor
194 }
195 c:char <- get *curr, value:offset
196 <character-c-received>
197 {
198
199 newline?:bool <- equal c, 10/newline
200 break-unless newline?
201
202 clear-line-until screen, right
203
204 row <- add row, 1
205 column <- copy left
206 screen <- move-cursor screen, row, column
207 curr <- next curr
208 prev <- next prev
209 loop +next-character
210 }
211 {
212
213
214 at-right?:bool <- equal column, right
215 break-unless at-right?
216
217 wrap-icon:char <- copy 8617/loop-back-to-left
218 print screen, wrap-icon, 245/grey
219 column <- copy left
220 row <- add row, 1
221 screen <- move-cursor screen, row, column
222
223 loop +next-character
224 }
225 print screen, c, color
226 curr <- next curr
227 prev <- next prev
228 column <- add column, 1
229 loop
230 }
231
232 *editor <- put *editor, bottom-of-screen:offset, curr
233 *editor <- put *editor, bottom:offset, row
234 return row, column
235 ]
236
237 scenario point-at-multiple-editors [
238 local-scope
239 trace-until 100/app
240 assume-screen 30/width, 5/height
241
242 assume-resources [
243 [lesson/recipes.mu] <- [
244 |abc|
245 ]
246 ]
247 env:&:environment <- new-programming-environment resources, screen, [def]
248
249 assume-console [
250 left-click 1, 1
251 left-click 1, 17
252 ]
253
254 run [
255 event-loop screen, console, env, resources
256 recipes:&:editor <- get *env, recipes:offset
257 5:num/raw <- get *recipes, cursor-column:offset
258 sandbox:&:editor <- get *env, current-sandbox:offset
259 7:num/raw <- get *sandbox, cursor-column:offset
260 ]
261 memory-should-contain [
262 5 <- 1
263 7 <- 17
264 ]
265 ]
266
267 scenario edit-multiple-editors [
268 local-scope
269 trace-until 100/app
270 assume-screen 30/width, 5/height
271
272 assume-resources [
273 [lesson/recipes.mu] <- [
274 |abc|
275 ]
276 ]
277 env:&:environment <- new-programming-environment resources, screen, [def]
278 render-all screen, env, render
279
280 assume-console [
281 left-click 1, 1
282 type [0]
283 left-click 1, 17
284 type [1]
285 ]
286 run [
287 event-loop screen, console, env, resources
288 recipes:&:editor <- get *env, recipes:offset
289 5:num/raw <- get *recipes, cursor-column:offset
290 sandbox:&:editor <- get *env, current-sandbox:offset
291 7:num/raw <- get *sandbox, cursor-column:offset
292 ]
293 screen-should-contain [
294 . run (F4) . # this line has a different background, but we don't test that yet
295 .a0bc ╎d1ef .
296 . ╎──────────────.
297 .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎ .
298 . ╎ .
299 ]
300 memory-should-contain [
301 5 <- 2
302 7 <- 18
303 ]
304
305 run [
306 cursor:char <- copy 9251/␣
307 print screen, cursor
308 ]
309 screen-should-contain [
310 . run (F4) .
311 .a0bc ╎d1␣f .
312 . ╎──────────────.
313 .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎ .
314 . ╎ .
315 ]
316 ]
317
318 scenario editor-in-focus-keeps-cursor [
319 local-scope
320 trace-until 100/app
321 assume-screen 30/width, 5/height
322 assume-resources [
323 [lesson/recipes.mu] <- [
324 |abc|
325 ]
326 ]
327 env:&:environment <- new-programming-environment resources, screen, [def]
328 render-all screen, env, render
329
330 assume-console []
331 run [
332 event-loop screen, console, env, resources
333 cursor:char <- copy 9251/␣
334 print screen, cursor
335 ]
336
337 screen-should-contain [
338 . run (F4) .
339 .␣bc ╎def .
340 . ╎──────────────.
341 .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎ .
342 . ╎ .
343 ]
344
345 assume-console [
346 type [z]
347 ]
348 run [
349 event-loop screen, console, env, resources
350 cursor:char <- copy 9251/␣
351 print screen, cursor
352 ]
353
354 screen-should-contain [
355 . run (F4) .
356 .z␣bc ╎def .
357 . ╎──────────────.
358 .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎ .
359 . ╎ .
360 ]
361 ]
362
363 scenario backspace-in-sandbox-editor-joins-lines [
364 local-scope
365 trace-until 100/app
366 assume-screen 30/width, 5/height
367 assume-resources [
368 ]
369
370 test-sandbox-editor-contents:text <- new [abc
371 def]
372 env:&:environment <- new-programming-environment resources, screen, test-sandbox-editor-contents
373 render-all screen, env, render
374 screen-should-contain [
375 . run (F4) .
376 . ╎abc .
377 .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎def .
378 . ╎──────────────.
379 . ╎ .
380 ]
381
382 assume-console [
383 left-click 2, 16
384 press backspace
385 ]
386 run [
387 event-loop screen, console, env, resources
388 cursor:char <- copy 9251/␣
389 print screen, cursor
390 ]
391
392 screen-should-contain [
393 . run (F4) .
394 . ╎abc␣ef .
395 .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎──────────────.
396 . ╎ .
397 ]
398 ]
399
400 type render-recipe = (recipe (address screen) (address editor) -> number number (address screen) (address editor))
401
402 def render-all screen:&:screen, env:&:environment, render-editor:render-recipe -> screen:&:screen, env:&:environment [
403 local-scope
404 load-inputs
405 trace 10, [app], [render all]
406
407 trace 11, [app], [render top menu]
408 width:num <- screen-width screen
409 draw-horizontal screen, 0, 0/left, width, 32/space, 0/black, 238/grey
410 button-start:num <- subtract width, 20
411 button-on-screen?:bool <- greater-or-equal button-start, 0
412 assert button-on-screen?, [screen too narrow for menu]
413 screen <- move-cursor screen, 0/row, button-start
414 print screen, [ run (F4) ], 255/white, 161/reddish
415
416 trace 11, [app], [render divider]
417 divider:num, _ <- divide-with-remainder width, 2
418 height:num <- screen-height screen
419 draw-vertical screen, divider, 1/top, height, 9482/vertical-dotted
420
421 screen <- render-recipes screen, env, render-editor
422 screen <- render-sandbox-side screen, env, render-editor
423 <end-render-components>
424
425 recipes:&:editor <- get *env, recipes:offset
426 current-sandbox:&:editor <- get *env, current-sandbox:offset
427 sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset
428 screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
429 ]
430
431 def render-recipes screen:&:screen, env:&:environment, render-editor:render-recipe -> screen:&:screen, env:&:environment [
432 local-scope
433 load-inputs
434 trace 11, [app], [render recipes]
435 old-top-idx:num <- save-top-idx screen
436 recipes:&:editor <- get *env, recipes:offset
437
438 left:num <- get *recipes, left:offset
439 right:num <- get *recipes, right:offset
440 row:num, column:num, screen <- call render-editor, screen, recipes
441 <end-render-recipe-components>
442
443 draw-horizontal screen, row, left, right, 9480/horizontal-dotted
444 row <- add row, 1
445 clear-screen-from screen, row, left, left, right
446
447 assert-no-scroll screen, old-top-idx
448 ]
449
450
451 def render-sandbox-side screen:&:screen, env:&:environment, render-editor:render-recipe -> screen:&:screen, env:&:environment [
452 local-scope
453 load-inputs
454 trace 11, [app], [render sandboxes]
455 old-top-idx:num <- save-top-idx screen
456 current-sandbox:&:editor <- get *env, current-sandbox:offset
457 left:num <- get *current-sandbox, left:offset
458 right:num <- get *current-sandbox, right:offset
459 row:num, column:num, screen, current-sandbox <- call render-editor, screen, current-sandbox
460
461 draw-horizontal screen, row, left, right
462 row <- add row, 1
463 clear-screen-from screen, row, left, left, right
464
465 assert-no-scroll screen, old-top-idx
466 ]
467
468 def update-cursor screen:&:screen, recipes:&:editor, current-sandbox:&:editor, sandbox-in-focus?:bool, env:&:environment -> screen:&:screen [
469 local-scope
470 load-inputs
471 <update-cursor-special-cases>
472 {
473 break-if sandbox-in-focus?
474 cursor-row:num <- get *recipes, cursor-row:offset
475 cursor-column:num <- get *recipes, cursor-column:offset
476 }
477 {
478 break-unless sandbox-in-focus?
479 cursor-row:num <- get *current-sandbox, cursor-row:offset
480 cursor-column:num <- get *current-sandbox, cursor-column:offset
481 }
482 screen <- move-cursor screen, cursor-row, cursor-column
483 ]
484
485
486
487
488 after <global-type> [
489 {
490 switch-side?:bool <- equal c, 14/ctrl-n
491 break-unless switch-side?
492 sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset
493 sandbox-in-focus? <- not sandbox-in-focus?
494 *env <- put *env, sandbox-in-focus?:offset, sandbox-in-focus?
495 screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
496 loop +next-event
497 }
498 ]
499
500
501
502 def draw-vertical screen:&:screen, col:num, y:num, bottom:num -> screen:&:screen [
503 local-scope
504 load-inputs
505 style:char, style-found?:bool <- next-input
506 {
507 break-if style-found?
508 style <- copy 9474/vertical
509 }
510 color:num, color-found?:bool <- next-input
511 {
512
513 break-if color-found?
514 color <- copy 245/grey
515 }
516 {
517 continue?:bool <- lesser-than y, bottom
518 break-unless continue?
519 screen <- move-cursor screen, y, col
520 print screen, style, color
521 y <- add y, 1
522 loop
523 }
524 ]
525
526 scenario backspace-over-text [
527 local-scope
528 trace-until 100/app
529 assume-screen 50/width, 15/height
530
531 assume-resources [
532 ]
533
534 env:&:environment <- new-programming-environment resources, screen, []
535
536 assume-console [
537 type [a]
538 press backspace
539 ]
540 run [
541 event-loop screen, console, env, resources
542 10:num/raw <- get *screen, cursor-row:offset
543 11:num/raw <- get *screen, cursor-column:offset
544 ]
545 memory-should-contain [
546 10 <- 1
547 11 <- 0
548 ]
549 ]