From 6e1eeeebfb453fa7c871869c19375ce60fbd7413 Mon Sep 17 00:00:00 2001 From: Kartik Agaram Date: Sat, 27 Jul 2019 16:01:55 -0700 Subject: 5485 - promote SubX to top-level --- html/edit/001-editor.mu.html | 529 --- html/edit/002-typing.mu.html | 1209 ------- html/edit/003-shortcuts.mu.html | 4526 ------------------------- html/edit/004-programming-environment.mu.html | 614 ---- html/edit/005-sandbox.mu.html | 1258 ------- html/edit/006-sandbox-copy.mu.html | 459 --- html/edit/007-sandbox-delete.mu.html | 406 --- html/edit/008-sandbox-edit.mu.html | 389 --- html/edit/009-sandbox-test.mu.html | 296 -- html/edit/010-sandbox-trace.mu.html | 318 -- html/edit/011-errors.mu.html | 951 ------ html/edit/012-editor-undo.mu.html | 2176 ------------ 12 files changed, 13131 deletions(-) delete mode 100644 html/edit/001-editor.mu.html delete mode 100644 html/edit/002-typing.mu.html delete mode 100644 html/edit/003-shortcuts.mu.html delete mode 100644 html/edit/004-programming-environment.mu.html delete mode 100644 html/edit/005-sandbox.mu.html delete mode 100644 html/edit/006-sandbox-copy.mu.html delete mode 100644 html/edit/007-sandbox-delete.mu.html delete mode 100644 html/edit/008-sandbox-edit.mu.html delete mode 100644 html/edit/009-sandbox-test.mu.html delete mode 100644 html/edit/010-sandbox-trace.mu.html delete mode 100644 html/edit/011-errors.mu.html delete mode 100644 html/edit/012-editor-undo.mu.html (limited to 'html/edit') diff --git a/html/edit/001-editor.mu.html b/html/edit/001-editor.mu.html deleted file mode 100644 index bafdade1..00000000 --- a/html/edit/001-editor.mu.html +++ /dev/null @@ -1,529 +0,0 @@ - - - - -Mu - edit/001-editor.mu - - - - - - - - - - -https://github.com/akkartik/mu/blob/master/edit/001-editor.mu -
-  1 ## the basic editor data structure, and how it displays text to the screen
-  2 
-  3 # temporary main for this layer: just render the given text at the given
-  4 # screen dimensions, then stop
-  5 def main text:text [
-  6   local-scope
-  7   load-inputs
-  8   open-console
-  9   clear-screen null/screen  # non-scrolling app
- 10   e:&:editor <- new-editor text, 0/left, 5/right
- 11   render null/screen, e
- 12   wait-for-event null/console
- 13   close-console
- 14 ]
- 15 
- 16 scenario editor-renders-text-to-screen [
- 17   local-scope
- 18   assume-screen 10/width, 5/height
- 19   e:&:editor <- new-editor [abc], 0/left, 10/right
- 20   run [
- 21     render screen, e
- 22   ]
- 23   screen-should-contain [
- 24     # top line of screen reserved for menu
- 25     .          .
- 26     .abc       .
- 27     .          .
- 28   ]
- 29 ]
- 30 
- 31 container editor [
- 32   # editable text: doubly linked list of characters (head contains a special sentinel)
- 33   data:&:duplex-list:char
- 34   top-of-screen:&:duplex-list:char
- 35   bottom-of-screen:&:duplex-list:char
- 36   # location before cursor inside data
- 37   before-cursor:&:duplex-list:char
- 38 
- 39   # raw bounds of display area on screen
- 40   # always displays from row 1 (leaving row 0 for a menu) and at most until bottom of screen
- 41   left:num
- 42   right:num
- 43   bottom:num
- 44   # raw screen coordinates of cursor
- 45   cursor-row:num
- 46   cursor-column:num
- 47 ]
- 48 
- 49 # creates a new editor widget
- 50 #   right is exclusive
- 51 def new-editor s:text, left:num, right:num -> result:&:editor [
- 52   local-scope
- 53   load-inputs
- 54   # no clipping of bounds
- 55   right <- subtract right, 1
- 56   result <- new editor:type
- 57   # initialize screen-related fields
- 58   *result <- put *result, left:offset, left
- 59   *result <- put *result, right:offset, right
- 60   # initialize cursor coordinates
- 61   *result <- put *result, cursor-row:offset, 1/top
- 62   *result <- put *result, cursor-column:offset, left
- 63   # initialize empty contents
- 64   init:&:duplex-list:char <- push 167/§, null
- 65   *result <- put *result, data:offset, init
- 66   *result <- put *result, top-of-screen:offset, init
- 67   *result <- put *result, before-cursor:offset, init
- 68   result <- insert-text result, s
- 69   <editor-initialization>
- 70 ]
- 71 
- 72 def insert-text editor:&:editor, text:text -> editor:&:editor [
- 73   local-scope
- 74   load-inputs
- 75   curr:&:duplex-list:char <- get *editor, data:offset
- 76   insert curr, text
- 77 ]
- 78 
- 79 scenario editor-initializes-without-data [
- 80   local-scope
- 81   assume-screen 5/width, 3/height
- 82   run [
- 83     e:&:editor <- new-editor null/data, 2/left, 5/right
- 84     1:editor/raw <- copy *e
- 85   ]
- 86   memory-should-contain [
- 87     # 1,2 (data) <- just the § sentinel
- 88     # 3,4 (top of screen) <- the § sentinel
- 89     # 5 (bottom of screen) <- null since text fits on screen
- 90     5 <- 0
- 91     6 <- 0
- 92     # 7,8 (before cursor) <- the § sentinel
- 93     9 <- 2  # left
- 94     10 <- 4  # right  (inclusive)
- 95     11 <- 0  # bottom (not set until render)
- 96     12 <- 1  # cursor row
- 97     13 <- 2  # cursor column
- 98   ]
- 99   screen-should-contain [
-100     .     .
-101     .     .
-102     .     .
-103   ]
-104 ]
-105 
-106 # Assumes cursor should be at coordinates (cursor-row, cursor-column) and
-107 # updates before-cursor to match. Might also move coordinates if they're
-108 # outside text.
-109 def render screen:&:screen, editor:&:editor -> last-row:num, last-column:num, screen:&:screen, editor:&:editor [
-110   local-scope
-111   load-inputs
-112   return-unless editor, 1/top, 0/left
-113   left:num <- get *editor, left:offset
-114   screen-height:num <- screen-height screen
-115   right:num <- get *editor, right:offset
-116   # traversing editor
-117   curr:&:duplex-list:char <- get *editor, top-of-screen:offset
-118   prev:&:duplex-list:char <- copy curr  # just in case curr becomes null and we can't compute prev
-119   curr <- next curr
-120   # traversing screen
-121   color:num <- copy 7/white
-122   row:num <- copy 1/top
-123   column:num <- copy left
-124   cursor-row:num <- get *editor, cursor-row:offset
-125   cursor-column:num <- get *editor, cursor-column:offset
-126   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-127   screen <- move-cursor screen, row, column
-128   {
-129     +next-character
-130     break-unless curr
-131     off-screen?:bool <- greater-or-equal row, screen-height
-132     break-if off-screen?
-133     # update editor.before-cursor
-134     # Doing so at the start of each iteration ensures it stays one step behind
-135     # the current character.
-136     {
-137       at-cursor-row?:bool <- equal row, cursor-row
-138       break-unless at-cursor-row?
-139       at-cursor?:bool <- equal column, cursor-column
-140       break-unless at-cursor?
-141       before-cursor <- copy prev
-142     }
-143     c:char <- get *curr, value:offset
-144     <character-c-received>
-145     {
-146       # newline? move to left rather than 0
-147       newline?:bool <- equal c, 10/newline
-148       break-unless newline?
-149       # adjust cursor if necessary
-150       {
-151         at-cursor-row?:bool <- equal row, cursor-row
-152         break-unless at-cursor-row?
-153         left-of-cursor?:bool <- lesser-than column, cursor-column
-154         break-unless left-of-cursor?
-155         cursor-column <- copy column
-156         before-cursor <- prev curr
-157       }
-158       # clear rest of line in this window
-159       clear-line-until screen, right
-160       # skip to next line
-161       row <- add row, 1
-162       column <- copy left
-163       screen <- move-cursor screen, row, column
-164       curr <- next curr
-165       prev <- next prev
-166       loop +next-character
-167     }
-168     {
-169       # at right? wrap. even if there's only one more letter left; we need
-170       # room for clicking on the cursor after it.
-171       at-right?:bool <- equal column, right
-172       break-unless at-right?
-173       # print wrap icon
-174       wrap-icon:char <- copy 8617/loop-back-to-left
-175       print screen, wrap-icon, 245/grey
-176       column <- copy left
-177       row <- add row, 1
-178       screen <- move-cursor screen, row, column
-179       # don't increment curr
-180       loop +next-character
-181     }
-182     print screen, c, color
-183     curr <- next curr
-184     prev <- next prev
-185     column <- add column, 1
-186     loop
-187   }
-188   # save first character off-screen
-189   *editor <- put *editor, bottom-of-screen:offset, curr
-190   # is cursor to the right of the last line? move to end
-191   {
-192     at-cursor-row?:bool <- equal row, cursor-row
-193     cursor-outside-line?:bool <- lesser-or-equal column, cursor-column
-194     before-cursor-on-same-line?:bool <- and at-cursor-row?, cursor-outside-line?
-195     above-cursor-row?:bool <- lesser-than row, cursor-row
-196     before-cursor?:bool <- or before-cursor-on-same-line?, above-cursor-row?
-197     break-unless before-cursor?
-198     cursor-row <- copy row
-199     cursor-column <- copy column
-200     before-cursor <- copy prev
-201   }
-202   *editor <- put *editor, bottom:offset, row
-203   *editor <- put *editor, cursor-row:offset, cursor-row
-204   *editor <- put *editor, cursor-column:offset, cursor-column
-205   *editor <- put *editor, before-cursor:offset, before-cursor
-206   clear-line-until screen, right
-207   row <- add row, 1
-208   return row, left/column
-209 ]
-210 
-211 def clear-screen-from screen:&:screen, row:num, column:num, left:num, right:num -> screen:&:screen [
-212   local-scope
-213   load-inputs
-214   # if it's the real screen, use the optimized primitive
-215   {
-216     break-if screen
-217     clear-display-from row, column, left, right
-218     return
-219   }
-220   # if not, go the slower route
-221   screen <- move-cursor screen, row, column
-222   clear-line-until screen, right
-223   clear-rest-of-screen screen, row, left, right
-224 ]
-225 
-226 def clear-rest-of-screen screen:&:screen, row:num, left:num, right:num -> screen:&:screen [
-227   local-scope
-228   load-inputs
-229   row <- add row, 1
-230   # if it's the real screen, use the optimized primitive
-231   {
-232     break-if screen
-233     clear-display-from row, left, left, right
-234     return
-235   }
-236   screen <- move-cursor screen, row, left
-237   screen-height:num <- screen-height screen
-238   {
-239     at-bottom-of-screen?:bool <- greater-or-equal row, screen-height
-240     break-if at-bottom-of-screen?
-241     screen <- move-cursor screen, row, left
-242     clear-line-until screen, right
-243     row <- add row, 1
-244     loop
-245   }
-246 ]
-247 
-248 scenario editor-prints-multiple-lines [
-249   local-scope
-250   assume-screen 5/width, 5/height
-251   s:text <- new [abc
-252 def]
-253   e:&:editor <- new-editor s, 0/left, 5/right
-254   run [
-255     render screen, e
-256   ]
-257   screen-should-contain [
-258     .     .
-259     .abc  .
-260     .def  .
-261     .     .
-262   ]
-263 ]
-264 
-265 scenario editor-handles-offsets [
-266   local-scope
-267   assume-screen 5/width, 5/height
-268   e:&:editor <- new-editor [abc], 1/left, 5/right
-269   run [
-270     render screen, e
-271   ]
-272   screen-should-contain [
-273     .     .
-274     . abc .
-275     .     .
-276   ]
-277 ]
-278 
-279 scenario editor-prints-multiple-lines-at-offset [
-280   local-scope
-281   assume-screen 5/width, 5/height
-282   s:text <- new [abc
-283 def]
-284   e:&:editor <- new-editor s, 1/left, 5/right
-285   run [
-286     render screen, e
-287   ]
-288   screen-should-contain [
-289     .     .
-290     . abc .
-291     . def .
-292     .     .
-293   ]
-294 ]
-295 
-296 scenario editor-wraps-long-lines [
-297   local-scope
-298   assume-screen 5/width, 5/height
-299   e:&:editor <- new-editor [abc def], 0/left, 5/right
-300   run [
-301     render screen, e
-302   ]
-303   screen-should-contain [
-304     .     .
-305     .abc ↩.
-306     .def  .
-307     .     .
-308   ]
-309   screen-should-contain-in-color 245/grey [
-310     .     .
-311     .    ↩.
-312     .     .
-313     .     .
-314   ]
-315 ]
-316 
-317 scenario editor-wraps-barely-long-lines [
-318   local-scope
-319   assume-screen 5/width, 5/height
-320   e:&:editor <- new-editor [abcde], 0/left, 5/right
-321   run [
-322     render screen, e
-323   ]
-324   # still wrap, even though the line would fit. We need room to click on the
-325   # end of the line
-326   screen-should-contain [
-327     .     .
-328     .abcd↩.
-329     .e    .
-330     .     .
-331   ]
-332   screen-should-contain-in-color 245/grey [
-333     .     .
-334     .    ↩.
-335     .     .
-336     .     .
-337   ]
-338 ]
-339 
-340 scenario editor-with-empty-text [
-341   local-scope
-342   assume-screen 5/width, 5/height
-343   e:&:editor <- new-editor [], 0/left, 5/right
-344   run [
-345     render screen, e
-346     3:num/raw <- get *e, cursor-row:offset
-347     4:num/raw <- get *e, cursor-column:offset
-348   ]
-349   screen-should-contain [
-350     .     .
-351     .     .
-352     .     .
-353   ]
-354   memory-should-contain [
-355     3 <- 1  # cursor row
-356     4 <- 0  # cursor column
-357   ]
-358 ]
-359 
-360 # just a little color for Mu code
-361 
-362 scenario render-colors-comments [
-363   local-scope
-364   assume-screen 5/width, 5/height
-365   s:text <- new [abc
-366 # de
-367 f]
-368   e:&:editor <- new-editor s, 0/left, 5/right
-369   run [
-370     render screen, e
-371   ]
-372   screen-should-contain [
-373     .     .
-374     .abc  .
-375     .# de .
-376     .f    .
-377     .     .
-378   ]
-379   screen-should-contain-in-color 12/lightblue, [
-380     .     .
-381     .     .
-382     .# de .
-383     .     .
-384     .     .
-385   ]
-386   screen-should-contain-in-color 7/white, [
-387     .     .
-388     .abc  .
-389     .     .
-390     .f    .
-391     .     .
-392   ]
-393 ]
-394 
-395 after <character-c-received> [
-396   color <- get-color color, c
-397 ]
-398 
-399 # so far the previous color is all the information we need; that may change
-400 def get-color color:num, c:char -> color:num [
-401   local-scope
-402   load-inputs
-403   color-is-white?:bool <- equal color, 7/white
-404   # if color is white and next character is '#', switch color to blue
-405   {
-406     break-unless color-is-white?
-407     starting-comment?:bool <- equal c, 35/#
-408     break-unless starting-comment?
-409     trace 90, [app], [switch color back to blue]
-410     return 12/lightblue
-411   }
-412   # if color is blue and next character is newline, switch color to white
-413   {
-414     color-is-blue?:bool <- equal color, 12/lightblue
-415     break-unless color-is-blue?
-416     ending-comment?:bool <- equal c, 10/newline
-417     break-unless ending-comment?
-418     trace 90, [app], [switch color back to white]
-419     return 7/white
-420   }
-421   # if color is white (no comments) and next character is '<', switch color to red
-422   {
-423     break-unless color-is-white?
-424     starting-assignment?:bool <- equal c, 60/<
-425     break-unless starting-assignment?
-426     return 1/red
-427   }
-428   # if color is red and next character is space, switch color to white
-429   {
-430     color-is-red?:bool <- equal color, 1/red
-431     break-unless color-is-red?
-432     ending-assignment?:bool <- equal c, 32/space
-433     break-unless ending-assignment?
-434     return 7/white
-435   }
-436   # otherwise no change
-437   return color
-438 ]
-439 
-440 scenario render-colors-assignment [
-441   local-scope
-442   assume-screen 8/width, 5/height
-443   s:text <- new [abc
-444 d <- e
-445 f]
-446   e:&:editor <- new-editor s, 0/left, 8/right
-447   run [
-448     render screen, e
-449   ]
-450   screen-should-contain [
-451     .        .
-452     .abc     .
-453     .d <- e  .
-454     .f       .
-455     .        .
-456   ]
-457   screen-should-contain-in-color 1/red, [
-458     .        .
-459     .        .
-460     .  <-    .
-461     .        .
-462     .        .
-463   ]
-464 ]
-
- - - diff --git a/html/edit/002-typing.mu.html b/html/edit/002-typing.mu.html deleted file mode 100644 index 39422b93..00000000 --- a/html/edit/002-typing.mu.html +++ /dev/null @@ -1,1209 +0,0 @@ - - - - -Mu - edit/002-typing.mu - - - - - - - - - - -https://github.com/akkartik/mu/blob/master/edit/002-typing.mu -
-   1 ## handling events from the keyboard, mouse, touch screen, ...
-   2 
-   3 # temporary main: interactive editor
-   4 # hit ctrl-c to exit
-   5 def! main text:text [
-   6   local-scope
-   7   load-inputs
-   8   open-console
-   9   clear-screen null/screen  # non-scrolling app
-  10   editor:&:editor <- new-editor text, 5/left, 45/right
-  11   editor-render null/screen, editor
-  12   editor-event-loop null/screen, null/console, editor
-  13   close-console
-  14 ]
-  15 
-  16 def editor-event-loop screen:&:screen, console:&:console, editor:&:editor -> screen:&:screen, console:&:console, editor:&:editor [
-  17   local-scope
-  18   load-inputs
-  19   {
-  20     # looping over each (keyboard or touch) event as it occurs
-  21     +next-event
-  22     cursor-row:num <- get *editor, cursor-row:offset
-  23     cursor-column:num <- get *editor, cursor-column:offset
-  24     screen <- move-cursor screen, cursor-row, cursor-column
-  25     e:event, found?:bool, quit?:bool, console <- read-event console
-  26     loop-unless found?
-  27     break-if quit?  # only in tests
-  28     trace 10, [app], [next-event]
-  29     # 'touch' event
-  30     t:touch-event, is-touch?:bool <- maybe-convert e, touch:variant
-  31     {
-  32       break-unless is-touch?
-  33       move-cursor editor, screen, t
-  34       loop +next-event
-  35     }
-  36     # keyboard events
-  37     {
-  38       break-if is-touch?
-  39       go-render?:bool <- handle-keyboard-event screen, editor, e
-  40       {
-  41         break-unless go-render?
-  42         screen <- editor-render screen, editor
-  43       }
-  44     }
-  45     loop
-  46   }
-  47 ]
-  48 
-  49 # process click, return if it was on current editor
-  50 def move-cursor editor:&:editor, screen:&:screen, t:touch-event -> in-focus?:bool, editor:&:editor [
-  51   local-scope
-  52   load-inputs
-  53   return-unless editor, false
-  54   click-row:num <- get t, row:offset
-  55   return-unless click-row, false  # ignore clicks on 'menu'
-  56   click-column:num <- get t, column:offset
-  57   left:num <- get *editor, left:offset
-  58   too-far-left?:bool <- lesser-than click-column, left
-  59   return-if too-far-left?, false
-  60   right:num <- get *editor, right:offset
-  61   too-far-right?:bool <- greater-than click-column, right
-  62   return-if too-far-right?, false
-  63   # position cursor
-  64   <begin-move-cursor>
-  65   editor <- snap-cursor editor, screen, click-row, click-column
-  66   undo-coalesce-tag:num <- copy 0/never
-  67   <end-move-cursor>
-  68   # gain focus
-  69   return true
-  70 ]
-  71 
-  72 # Variant of 'render' that only moves the cursor (coordinates and
-  73 # before-cursor). If it's past the end of a line, it 'slides' it left. If it's
-  74 # past the last line it positions at end of last line.
-  75 def snap-cursor editor:&:editor, screen:&:screen, target-row:num, target-column:num -> editor:&:editor [
-  76   local-scope
-  77   load-inputs
-  78   return-unless editor
-  79   left:num <- get *editor, left:offset
-  80   right:num <- get *editor, right:offset
-  81   screen-height:num <- screen-height screen
-  82   # count newlines until screen row
-  83   curr:&:duplex-list:char <- get *editor, top-of-screen:offset
-  84   prev:&:duplex-list:char <- copy curr  # just in case curr becomes null and we can't compute prev
-  85   curr <- next curr
-  86   row:num <- copy 1/top
-  87   column:num <- copy left
-  88   *editor <- put *editor, cursor-row:offset, target-row
-  89   cursor-row:num <- copy target-row
-  90   *editor <- put *editor, cursor-column:offset, target-column
-  91   cursor-column:num <- copy target-column
-  92   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-  93   {
-  94     +next-character
-  95     break-unless curr
-  96     off-screen?:bool <- greater-or-equal row, screen-height
-  97     break-if off-screen?
-  98     # update editor.before-cursor
-  99     # Doing so at the start of each iteration ensures it stays one step behind
- 100     # the current character.
- 101     {
- 102       at-cursor-row?:bool <- equal row, cursor-row
- 103       break-unless at-cursor-row?
- 104       at-cursor?:bool <- equal column, cursor-column
- 105       break-unless at-cursor?
- 106       before-cursor <- copy prev
- 107       *editor <- put *editor, before-cursor:offset, before-cursor
- 108     }
- 109     c:char <- get *curr, value:offset
- 110     {
- 111       # newline? move to left rather than 0
- 112       newline?:bool <- equal c, 10/newline
- 113       break-unless newline?
- 114       # adjust cursor if necessary
- 115       {
- 116         at-cursor-row?:bool <- equal row, cursor-row
- 117         break-unless at-cursor-row?
- 118         left-of-cursor?:bool <- lesser-than column, cursor-column
- 119         break-unless left-of-cursor?
- 120         cursor-column <- copy column
- 121         *editor <- put *editor, cursor-column:offset, cursor-column
- 122         before-cursor <- copy prev
- 123         *editor <- put *editor, before-cursor:offset, before-cursor
- 124       }
- 125       # skip to next line
- 126       row <- add row, 1
- 127       column <- copy left
- 128       curr <- next curr
- 129       prev <- next prev
- 130       loop +next-character
- 131     }
- 132     {
- 133       # at right? wrap. even if there's only one more letter left; we need
- 134       # room for clicking on the cursor after it.
- 135       at-right?:bool <- equal column, right
- 136       break-unless at-right?
- 137       column <- copy left
- 138       row <- add row, 1
- 139       # don't increment curr/prev
- 140       loop +next-character
- 141     }
- 142     curr <- next curr
- 143     prev <- next prev
- 144     column <- add column, 1
- 145     loop
- 146   }
- 147   # is cursor to the right of the last line? move to end
- 148   {
- 149     at-cursor-row?:bool <- equal row, cursor-row
- 150     cursor-outside-line?:bool <- lesser-or-equal column, cursor-column
- 151     before-cursor-on-same-line?:bool <- and at-cursor-row?, cursor-outside-line?
- 152     above-cursor-row?:bool <- lesser-than row, cursor-row
- 153     before-cursor?:bool <- or before-cursor-on-same-line?, above-cursor-row?
- 154     break-unless before-cursor?
- 155     cursor-row <- copy row
- 156     *editor <- put *editor, cursor-row:offset, cursor-row
- 157     cursor-column <- copy column
- 158     *editor <- put *editor, cursor-column:offset, cursor-column
- 159     before-cursor <- copy prev
- 160     *editor <- put *editor, before-cursor:offset, before-cursor
- 161   }
- 162 ]
- 163 
- 164 # Process an event 'e' and try to minimally update the screen.
- 165 # Set 'go-render?' to true to indicate the caller must perform a non-minimal update.
- 166 def handle-keyboard-event screen:&:screen, editor:&:editor, e:event -> go-render?:bool, screen:&:screen, editor:&:editor [
- 167   local-scope
- 168   load-inputs
- 169   return-unless editor, false/don't-render
- 170   screen-width:num <- screen-width screen
- 171   screen-height:num <- screen-height screen
- 172   left:num <- get *editor, left:offset
- 173   right:num <- get *editor, right:offset
- 174   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
- 175   cursor-row:num <- get *editor, cursor-row:offset
- 176   cursor-column:num <- get *editor, cursor-column:offset
- 177   save-row:num <- copy cursor-row
- 178   save-column:num <- copy cursor-column
- 179   # character
- 180   {
- 181     c:char, is-unicode?:bool <- maybe-convert e, text:variant
- 182     break-unless is-unicode?
- 183     trace 10, [app], [handle-keyboard-event: special character]
- 184     # exceptions for special characters go here
- 185     <handle-special-character>
- 186     # ignore any other special characters
- 187     regular-character?:bool <- greater-or-equal c, 32/space
- 188     return-unless regular-character?, false/don't-render
- 189     # otherwise type it in
- 190     <begin-insert-character>
- 191     go-render? <- insert-at-cursor editor, c, screen
- 192     <end-insert-character>
- 193     return
- 194   }
- 195   # special key to modify the text or move the cursor
- 196   k:num, is-keycode?:bool <- maybe-convert e:event, keycode:variant
- 197   assert is-keycode?, [event was of unknown type; neither keyboard nor mouse]
- 198   # handlers for each special key will go here
- 199   <handle-special-key>
- 200   return true/go-render
- 201 ]
- 202 
- 203 def insert-at-cursor editor:&:editor, c:char, screen:&:screen -> go-render?:bool, editor:&:editor, screen:&:screen [
- 204   local-scope
- 205   load-inputs
- 206   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
- 207   insert c, before-cursor
- 208   before-cursor <- next before-cursor
- 209   *editor <- put *editor, before-cursor:offset, before-cursor
- 210   cursor-row:num <- get *editor, cursor-row:offset
- 211   cursor-column:num <- get *editor, cursor-column:offset
- 212   left:num <- get *editor, left:offset
- 213   right:num <- get *editor, right:offset
- 214   save-row:num <- copy cursor-row
- 215   save-column:num <- copy cursor-column
- 216   screen-width:num <- screen-width screen
- 217   screen-height:num <- screen-height screen
- 218   # occasionally we'll need to mess with the cursor
- 219   <insert-character-special-case>
- 220   # but mostly we'll just move the cursor right
- 221   cursor-column <- add cursor-column, 1
- 222   *editor <- put *editor, cursor-column:offset, cursor-column
- 223   next:&:duplex-list:char <- next before-cursor
- 224   {
- 225     # at end of all text? no need to scroll? just print the character and leave
- 226     at-end?:bool <- equal next, null
- 227     break-unless at-end?
- 228     bottom:num <- subtract screen-height, 1
- 229     at-bottom?:bool <- equal save-row, bottom
- 230     at-right?:bool <- equal save-column, right
- 231     overflow?:bool <- and at-bottom?, at-right?
- 232     break-if overflow?
- 233     move-cursor screen, save-row, save-column
- 234     print screen, c
- 235     return false/don't-render
- 236   }
- 237   {
- 238     # not at right margin? print the character and rest of line
- 239     break-unless next
- 240     at-right?:bool <- greater-or-equal cursor-column, screen-width
- 241     break-if at-right?
- 242     curr:&:duplex-list:char <- copy before-cursor
- 243     move-cursor screen, save-row, save-column
- 244     curr-column:num <- copy save-column
- 245     {
- 246       # hit right margin? give up and let caller render
- 247       at-right?:bool <- greater-than curr-column, right
- 248       return-if at-right?, true/go-render
- 249       break-unless curr
- 250       # newline? done.
- 251       currc:char <- get *curr, value:offset
- 252       at-newline?:bool <- equal currc, 10/newline
- 253       break-if at-newline?
- 254       print screen, currc
- 255       curr-column <- add curr-column, 1
- 256       curr <- next curr
- 257       loop
- 258     }
- 259     return false/don't-render
- 260   }
- 261   return true/go-render
- 262 ]
- 263 
- 264 # helper for tests
- 265 def editor-render screen:&:screen, editor:&:editor -> screen:&:screen, editor:&:editor [
- 266   local-scope
- 267   load-inputs
- 268   old-top-idx:num <- save-top-idx screen
- 269   left:num <- get *editor, left:offset
- 270   right:num <- get *editor, right:offset
- 271   row:num, column:num <- render screen, editor
- 272   draw-horizontal screen, row, left, right, 9480/horizontal-dotted
- 273   row <- add row, 1
- 274   clear-screen-from screen, row, left, left, right
- 275   assert-no-scroll screen, old-top-idx
- 276 ]
- 277 
- 278 scenario editor-handles-empty-event-queue [
- 279   local-scope
- 280   assume-screen 10/width, 5/height
- 281   e:&:editor <- new-editor [abc], 0/left, 10/right
- 282   editor-render screen, e
- 283   assume-console []
- 284   run [
- 285     editor-event-loop screen, console, e
- 286   ]
- 287   screen-should-contain [
- 288     .          .
- 289     .abc       .
- 290     .╌╌╌╌╌╌╌╌╌╌.
- 291     .          .
- 292   ]
- 293 ]
- 294 
- 295 scenario editor-handles-mouse-clicks [
- 296   local-scope
- 297   assume-screen 10/width, 5/height
- 298   e:&:editor <- new-editor [abc], 0/left, 10/right
- 299   editor-render screen, e
- 300   $clear-trace
- 301   assume-console [
- 302     left-click 1, 1  # on the 'b'
- 303   ]
- 304   run [
- 305     editor-event-loop screen, console, e
- 306     3:num/raw <- get *e, cursor-row:offset
- 307     4:num/raw <- get *e, cursor-column:offset
- 308   ]
- 309   screen-should-contain [
- 310     .          .
- 311     .abc       .
- 312     .╌╌╌╌╌╌╌╌╌╌.
- 313     .          .
- 314   ]
- 315   memory-should-contain [
- 316     3 <- 1  # cursor is at row 0..
- 317     4 <- 1  # ..and column 1
- 318   ]
- 319   check-trace-count-for-label 0, [print-character]
- 320 ]
- 321 
- 322 scenario editor-handles-mouse-clicks-outside-text [
- 323   local-scope
- 324   assume-screen 10/width, 5/height
- 325   e:&:editor <- new-editor [abc], 0/left, 10/right
- 326   $clear-trace
- 327   assume-console [
- 328     left-click 1, 7  # last line, to the right of text
- 329   ]
- 330   run [
- 331     editor-event-loop screen, console, e
- 332     3:num/raw <- get *e, cursor-row:offset
- 333     4:num/raw <- get *e, cursor-column:offset
- 334   ]
- 335   memory-should-contain [
- 336     3 <- 1  # cursor row
- 337     4 <- 3  # cursor column
- 338   ]
- 339   check-trace-count-for-label 0, [print-character]
- 340 ]
- 341 
- 342 scenario editor-handles-mouse-clicks-outside-text-2 [
- 343   local-scope
- 344   assume-screen 10/width, 5/height
- 345   s:text <- new [abc
- 346 def]
- 347   e:&:editor <- new-editor s, 0/left, 10/right
- 348   $clear-trace
- 349   assume-console [
- 350     left-click 1, 7  # interior line, to the right of text
- 351   ]
- 352   run [
- 353     editor-event-loop screen, console, e
- 354     3:num/raw <- get *e, cursor-row:offset
- 355     4:num/raw <- get *e, cursor-column:offset
- 356   ]
- 357   memory-should-contain [
- 358     3 <- 1  # cursor row
- 359     4 <- 3  # cursor column
- 360   ]
- 361   check-trace-count-for-label 0, [print-character]
- 362 ]
- 363 
- 364 scenario editor-handles-mouse-clicks-outside-text-3 [
- 365   local-scope
- 366   assume-screen 10/width, 5/height
- 367   s:text <- new [abc
- 368 def]
- 369   e:&:editor <- new-editor s, 0/left, 10/right
- 370   $clear-trace
- 371   assume-console [
- 372     left-click 3, 7  # below text
- 373   ]
- 374   run [
- 375     editor-event-loop screen, console, e
- 376     3:num/raw <- get *e, cursor-row:offset
- 377     4:num/raw <- get *e, cursor-column:offset
- 378   ]
- 379   memory-should-contain [
- 380     3 <- 2  # cursor row
- 381     4 <- 3  # cursor column
- 382   ]
- 383   check-trace-count-for-label 0, [print-character]
- 384 ]
- 385 
- 386 scenario editor-handles-mouse-clicks-outside-column [
- 387   local-scope
- 388   assume-screen 10/width, 5/height
- 389   # editor occupies only left half of screen
- 390   e:&:editor <- new-editor [abc], 0/left, 5/right
- 391   editor-render screen, e
- 392   $clear-trace
- 393   assume-console [
- 394     # click on right half of screen
- 395     left-click 3, 8
- 396   ]
- 397   run [
- 398     editor-event-loop screen, console, e
- 399     3:num/raw <- get *e, cursor-row:offset
- 400     4:num/raw <- get *e, cursor-column:offset
- 401   ]
- 402   screen-should-contain [
- 403     .          .
- 404     .abc       .
- 405     .╌╌╌╌╌     .
- 406     .          .
- 407   ]
- 408   memory-should-contain [
- 409     3 <- 1  # no change to cursor row
- 410     4 <- 0  # ..or column
- 411   ]
- 412   check-trace-count-for-label 0, [print-character]
- 413 ]
- 414 
- 415 scenario editor-handles-mouse-clicks-in-menu-area [
- 416   local-scope
- 417   assume-screen 10/width, 5/height
- 418   e:&:editor <- new-editor [abc], 0/left, 5/right
- 419   editor-render screen, e
- 420   $clear-trace
- 421   assume-console [
- 422     # click on first, 'menu' row
- 423     left-click 0, 3
- 424   ]
- 425   run [
- 426     editor-event-loop screen, console, e
- 427     3:num/raw <- get *e, cursor-row:offset
- 428     4:num/raw <- get *e, cursor-column:offset
- 429   ]
- 430   # no change to cursor
- 431   memory-should-contain [
- 432     3 <- 1
- 433     4 <- 0
- 434   ]
- 435 ]
- 436 
- 437 scenario editor-inserts-characters-into-empty-editor [
- 438   local-scope
- 439   assume-screen 10/width, 5/height
- 440   e:&:editor <- new-editor [], 0/left, 5/right
- 441   editor-render screen, e
- 442   $clear-trace
- 443   assume-console [
- 444     type [abc]
- 445   ]
- 446   run [
- 447     editor-event-loop screen, console, e
- 448   ]
- 449   screen-should-contain [
- 450     .          .
- 451     .abc       .
- 452     .╌╌╌╌╌     .
- 453     .          .
- 454   ]
- 455   check-trace-count-for-label 3, [print-character]
- 456 ]
- 457 
- 458 scenario editor-inserts-characters-at-cursor [
- 459   local-scope
- 460   assume-screen 10/width, 5/height
- 461   e:&:editor <- new-editor [abc], 0/left, 10/right
- 462   editor-render screen, e
- 463   $clear-trace
- 464   # type two letters at different places
- 465   assume-console [
- 466     type [0]
- 467     left-click 1, 2
- 468     type [d]
- 469   ]
- 470   run [
- 471     editor-event-loop screen, console, e
- 472   ]
- 473   screen-should-contain [
- 474     .          .
- 475     .0adbc     .
- 476     .╌╌╌╌╌╌╌╌╌╌.
- 477     .          .
- 478   ]
- 479   check-trace-count-for-label 7, [print-character]  # 4 for first letter, 3 for second
- 480 ]
- 481 
- 482 scenario editor-inserts-characters-at-cursor-2 [
- 483   local-scope
- 484   assume-screen 10/width, 5/height
- 485   e:&:editor <- new-editor [abc], 0/left, 10/right
- 486   editor-render screen, e
- 487   $clear-trace
- 488   assume-console [
- 489     left-click 1, 5  # right of last line
- 490     type [d]
- 491   ]
- 492   run [
- 493     editor-event-loop screen, console, e
- 494   ]
- 495   screen-should-contain [
- 496     .          .
- 497     .abcd      .
- 498     .╌╌╌╌╌╌╌╌╌╌.
- 499     .          .
- 500   ]
- 501   check-trace-count-for-label 1, [print-character]
- 502 ]
- 503 
- 504 scenario editor-inserts-characters-at-cursor-5 [
- 505   local-scope
- 506   assume-screen 10/width, 5/height
- 507   s:text <- new [abc
- 508 d]
- 509   e:&:editor <- new-editor s, 0/left, 10/right
- 510   editor-render screen, e
- 511   $clear-trace
- 512   assume-console [
- 513     left-click 1, 5  # right of non-last line
- 514     type [e]
- 515   ]
- 516   run [
- 517     editor-event-loop screen, console, e
- 518   ]
- 519   screen-should-contain [
- 520     .          .
- 521     .abce      .
- 522     .d         .
- 523     .╌╌╌╌╌╌╌╌╌╌.
- 524     .          .
- 525   ]
- 526   check-trace-count-for-label 1, [print-character]
- 527 ]
- 528 
- 529 scenario editor-inserts-characters-at-cursor-3 [
- 530   local-scope
- 531   assume-screen 10/width, 5/height
- 532   e:&:editor <- new-editor [abc], 0/left, 10/right
- 533   editor-render screen, e
- 534   $clear-trace
- 535   assume-console [
- 536     left-click 3, 5  # below all text
- 537     type [d]
- 538   ]
- 539   run [
- 540     editor-event-loop screen, console, e
- 541   ]
- 542   screen-should-contain [
- 543     .          .
- 544     .abcd      .
- 545     .╌╌╌╌╌╌╌╌╌╌.
- 546     .          .
- 547   ]
- 548   check-trace-count-for-label 1, [print-character]
- 549 ]
- 550 
- 551 scenario editor-inserts-characters-at-cursor-4 [
- 552   local-scope
- 553   assume-screen 10/width, 5/height
- 554   s:text <- new [abc
- 555 d]
- 556   e:&:editor <- new-editor s, 0/left, 10/right
- 557   editor-render screen, e
- 558   $clear-trace
- 559   assume-console [
- 560     left-click 3, 5  # below all text
- 561     type [e]
- 562   ]
- 563   run [
- 564     editor-event-loop screen, console, e
- 565   ]
- 566   screen-should-contain [
- 567     .          .
- 568     .abc       .
- 569     .de        .
- 570     .╌╌╌╌╌╌╌╌╌╌.
- 571     .          .
- 572   ]
- 573   check-trace-count-for-label 1, [print-character]
- 574 ]
- 575 
- 576 scenario editor-inserts-characters-at-cursor-6 [
- 577   local-scope
- 578   assume-screen 10/width, 5/height
- 579   s:text <- new [abc
- 580 d]
- 581   e:&:editor <- new-editor s, 0/left, 10/right
- 582   editor-render screen, e
- 583   $clear-trace
- 584   assume-console [
- 585     left-click 3, 5  # below all text
- 586     type [ef]
- 587   ]
- 588   run [
- 589     editor-event-loop screen, console, e
- 590   ]
- 591   screen-should-contain [
- 592     .          .
- 593     .abc       .
- 594     .def       .
- 595     .╌╌╌╌╌╌╌╌╌╌.
- 596     .          .
- 597   ]
- 598   check-trace-count-for-label 2, [print-character]
- 599 ]
- 600 
- 601 scenario editor-moves-cursor-after-inserting-characters [
- 602   local-scope
- 603   assume-screen 10/width, 5/height
- 604   e:&:editor <- new-editor [ab], 0/left, 5/right
- 605   editor-render screen, e
- 606   assume-console [
- 607     type [01]
- 608   ]
- 609   run [
- 610     editor-event-loop screen, console, e
- 611   ]
- 612   screen-should-contain [
- 613     .          .
- 614     .01ab      .
- 615     .╌╌╌╌╌     .
- 616     .          .
- 617   ]
- 618 ]
- 619 
- 620 # if the cursor reaches the right margin, wrap the line
- 621 
- 622 scenario editor-wraps-line-on-insert [
- 623   local-scope
- 624   assume-screen 5/width, 5/height
- 625   e:&:editor <- new-editor [abc], 0/left, 5/right
- 626   editor-render screen, e
- 627   # type a letter
- 628   assume-console [
- 629     type [e]
- 630   ]
- 631   run [
- 632     editor-event-loop screen, console, e
- 633   ]
- 634   # no wrap yet
- 635   screen-should-contain [
- 636     .     .
- 637     .eabc .
- 638     .╌╌╌╌╌.
- 639     .     .
- 640     .     .
- 641   ]
- 642   # type a second letter
- 643   assume-console [
- 644     type [f]
- 645   ]
- 646   run [
- 647     editor-event-loop screen, console, e
- 648   ]
- 649   # now wrap
- 650   screen-should-contain [
- 651     .     .
- 652     .efab↩.
- 653     .c    .
- 654     .╌╌╌╌╌.
- 655     .     .
- 656   ]
- 657 ]
- 658 
- 659 scenario editor-wraps-line-on-insert-2 [
- 660   local-scope
- 661   # create an editor with some text
- 662   assume-screen 10/width, 5/height
- 663   s:text <- new [abcdefg
- 664 defg]
- 665   e:&:editor <- new-editor s, 0/left, 5/right
- 666   editor-render screen, e
- 667   # type more text at the start
- 668   assume-console [
- 669     left-click 3, 0
- 670     type [abc]
- 671   ]
- 672   run [
- 673     editor-event-loop screen, console, e
- 674     3:num/raw <- get *e, cursor-row:offset
- 675     4:num/raw <- get *e, cursor-column:offset
- 676   ]
- 677   # cursor is not wrapped
- 678   memory-should-contain [
- 679     3 <- 3
- 680     4 <- 3
- 681   ]
- 682   # but line is wrapped
- 683   screen-should-contain [
- 684     .          .
- 685     .abcd↩     .
- 686     .efg       .
- 687     .abcd↩     .
- 688     .efg       .
- 689   ]
- 690 ]
- 691 
- 692 after <insert-character-special-case> [
- 693   # if the line wraps at the cursor, move cursor to start of next row
- 694   {
- 695     # if either:
- 696     # a) we're at the end of the line and at the column of the wrap indicator, or
- 697     # b) we're not at end of line and just before the column of the wrap indicator
- 698     wrap-column:num <- copy right
- 699     before-wrap-column:num <- subtract wrap-column, 1
- 700     at-wrap?:bool <- greater-or-equal cursor-column, wrap-column
- 701     just-before-wrap?:bool <- greater-or-equal cursor-column, before-wrap-column
- 702     next:&:duplex-list:char <- next before-cursor
- 703     # at end of line? next == 0 || next.value == 10/newline
- 704     at-end-of-line?:bool <- equal next, null
- 705     {
- 706       break-if at-end-of-line?
- 707       next-character:char <- get *next, value:offset
- 708       at-end-of-line? <- equal next-character, 10/newline
- 709     }
- 710     # break unless ((eol? and at-wrap?) or (~eol? and just-before-wrap?))
- 711     move-cursor-to-next-line?:bool <- copy false
- 712     {
- 713       break-if at-end-of-line?
- 714       move-cursor-to-next-line? <- copy just-before-wrap?
- 715       # if we're moving the cursor because it's in the middle of a wrapping
- 716       # line, adjust it to left-most column
- 717       potential-new-cursor-column:num <- copy left
- 718     }
- 719     {
- 720       break-unless at-end-of-line?
- 721       move-cursor-to-next-line? <- copy at-wrap?
- 722       # if we're moving the cursor because it's at the end of a wrapping line,
- 723       # adjust it to one past the left-most column to make room for the
- 724       # newly-inserted wrap-indicator
- 725       potential-new-cursor-column:num <- add left, 1/make-room-for-wrap-indicator
- 726     }
- 727     break-unless move-cursor-to-next-line?
- 728     cursor-column <- copy potential-new-cursor-column
- 729     *editor <- put *editor, cursor-column:offset, cursor-column
- 730     cursor-row <- add cursor-row, 1
- 731     *editor <- put *editor, cursor-row:offset, cursor-row
- 732     # if we're out of the screen, scroll down
- 733     {
- 734       below-screen?:bool <- greater-or-equal cursor-row, screen-height
- 735       break-unless below-screen?
- 736       <scroll-down>
- 737     }
- 738     return true/go-render
- 739   }
- 740 ]
- 741 
- 742 scenario editor-wraps-cursor-after-inserting-characters-in-middle-of-line [
- 743   local-scope
- 744   assume-screen 10/width, 5/height
- 745   e:&:editor <- new-editor [abcde], 0/left, 5/right
- 746   assume-console [
- 747     left-click 1, 3  # right before the wrap icon
- 748     type [f]
- 749   ]
- 750   run [
- 751     editor-event-loop screen, console, e
- 752     3:num/raw <- get *e, cursor-row:offset
- 753     4:num/raw <- get *e, cursor-column:offset
- 754   ]
- 755   screen-should-contain [
- 756     .          .
- 757     .abcf↩     .
- 758     .de        .
- 759     .╌╌╌╌╌     .
- 760     .          .
- 761   ]
- 762   memory-should-contain [
- 763     3 <- 2  # cursor row
- 764     4 <- 0  # cursor column
- 765   ]
- 766 ]
- 767 
- 768 scenario editor-wraps-cursor-after-inserting-characters-at-end-of-line [
- 769   local-scope
- 770   assume-screen 10/width, 5/height
- 771   # create an editor containing two lines
- 772   s:text <- new [abc
- 773 xyz]
- 774   e:&:editor <- new-editor s, 0/left, 5/right
- 775   editor-render screen, e
- 776   screen-should-contain [
- 777     .          .
- 778     .abc       .
- 779     .xyz       .
- 780     .╌╌╌╌╌     .
- 781     .          .
- 782   ]
- 783   assume-console [
- 784     left-click 1, 4  # at end of first line
- 785     type [de]  # trigger wrap
- 786   ]
- 787   run [
- 788     editor-event-loop screen, console, e
- 789   ]
- 790   screen-should-contain [
- 791     .          .
- 792     .abcd↩     .
- 793     .e         .
- 794     .xyz       .
- 795     .╌╌╌╌╌     .
- 796   ]
- 797 ]
- 798 
- 799 scenario editor-wraps-cursor-to-left-margin [
- 800   local-scope
- 801   assume-screen 10/width, 5/height
- 802   e:&:editor <- new-editor [abcde], 2/left, 7/right
- 803   assume-console [
- 804     left-click 1, 5  # line is full; no wrap icon yet
- 805     type [01]
- 806   ]
- 807   run [
- 808     editor-event-loop screen, console, e
- 809     3:num/raw <- get *e, cursor-row:offset
- 810     4:num/raw <- get *e, cursor-column:offset
- 811   ]
- 812   screen-should-contain [
- 813     .          .
- 814     .  abc0↩   .
- 815     .  1de     .
- 816     .  ╌╌╌╌╌   .
- 817     .          .
- 818   ]
- 819   memory-should-contain [
- 820     3 <- 2  # cursor row
- 821     4 <- 3  # cursor column
- 822   ]
- 823 ]
- 824 
- 825 # if newline, move cursor to start of next line, and maybe align indent with previous line
- 826 
- 827 container editor [
- 828   indent?:bool
- 829 ]
- 830 
- 831 after <editor-initialization> [
- 832   *result <- put *result, indent?:offset, true
- 833 ]
- 834 
- 835 scenario editor-moves-cursor-down-after-inserting-newline [
- 836   local-scope
- 837   assume-screen 10/width, 5/height
- 838   e:&:editor <- new-editor [abc], 0/left, 10/right
- 839   assume-console [
- 840     type [0
- 841 1]
- 842   ]
- 843   run [
- 844     editor-event-loop screen, console, e
- 845   ]
- 846   screen-should-contain [
- 847     .          .
- 848     .0         .
- 849     .1abc      .
- 850     .╌╌╌╌╌╌╌╌╌╌.
- 851     .          .
- 852   ]
- 853 ]
- 854 
- 855 after <handle-special-character> [
- 856   {
- 857     newline?:bool <- equal c, 10/newline
- 858     break-unless newline?
- 859     <begin-insert-enter>
- 860     insert-new-line-and-indent editor, screen
- 861     <end-insert-enter>
- 862     return true/go-render
- 863   }
- 864 ]
- 865 
- 866 def insert-new-line-and-indent editor:&:editor, screen:&:screen -> editor:&:editor, screen:&:screen [
- 867   local-scope
- 868   load-inputs
- 869   cursor-row:num <- get *editor, cursor-row:offset
- 870   cursor-column:num <- get *editor, cursor-column:offset
- 871   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
- 872   left:num <- get *editor, left:offset
- 873   right:num <- get *editor, right:offset
- 874   screen-height:num <- screen-height screen
- 875   # update cursor coordinates
- 876   at-start-of-wrapped-line?:bool <- at-start-of-wrapped-line? editor
- 877   {
- 878     break-if at-start-of-wrapped-line?
- 879     cursor-row <- add cursor-row, 1
- 880     *editor <- put *editor, cursor-row:offset, cursor-row
- 881   }
- 882   cursor-column <- copy left
- 883   *editor <- put *editor, cursor-column:offset, cursor-column
- 884   # maybe scroll
- 885   {
- 886     below-screen?:bool <- greater-or-equal cursor-row, screen-height  # must be equal, never greater
- 887     break-unless below-screen?
- 888     <scroll-down2>
- 889     cursor-row <- subtract cursor-row, 1  # bring back into screen range
- 890     *editor <- put *editor, cursor-row:offset, cursor-row
- 891   }
- 892   # insert newline
- 893   insert 10/newline, before-cursor
- 894   before-cursor <- next before-cursor
- 895   *editor <- put *editor, before-cursor:offset, before-cursor
- 896   # indent if necessary
- 897   indent?:bool <- get *editor, indent?:offset
- 898   return-unless indent?
- 899   d:&:duplex-list:char <- get *editor, data:offset
- 900   end-of-previous-line:&:duplex-list:char <- prev before-cursor
- 901   indent:num <- line-indent end-of-previous-line, d
- 902   i:num <- copy 0
- 903   {
- 904     indent-done?:bool <- greater-or-equal i, indent
- 905     break-if indent-done?
- 906     insert-at-cursor editor, 32/space, screen
- 907     i <- add i, 1
- 908     loop
- 909   }
- 910 ]
- 911 
- 912 def at-start-of-wrapped-line? editor:&:editor -> result:bool [
- 913   local-scope
- 914   load-inputs
- 915   left:num <- get *editor, left:offset
- 916   cursor-column:num <- get *editor, cursor-column:offset
- 917   cursor-at-left?:bool <- equal cursor-column, left
- 918   return-unless cursor-at-left?, false
- 919   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
- 920   before-before-cursor:&:duplex-list:char <- prev before-cursor
- 921   return-unless before-before-cursor, false  # cursor is at start of editor
- 922   char-before-cursor:char <- get *before-cursor, value:offset
- 923   cursor-after-newline?:bool <- equal char-before-cursor, 10/newline
- 924   return-if cursor-after-newline?, false
- 925   # if cursor is at left margin and not at start, but previous character is not a newline,
- 926   # then we're at start of a wrapped line
- 927   return true
- 928 ]
- 929 
- 930 # takes a pointer 'curr' into the doubly-linked list and its sentinel, counts
- 931 # the number of spaces at the start of the line containing 'curr'.
- 932 def line-indent curr:&:duplex-list:char, start:&:duplex-list:char -> result:num [
- 933   local-scope
- 934   load-inputs
- 935   result:num <- copy 0
- 936   return-unless curr
- 937   at-start?:bool <- equal curr, start
- 938   return-if at-start?
- 939   {
- 940     curr <- prev curr
- 941     break-unless curr
- 942     at-start?:bool <- equal curr, start
- 943     break-if at-start?
- 944     c:char <- get *curr, value:offset
- 945     at-newline?:bool <- equal c, 10/newline
- 946     break-if at-newline?
- 947     # if c is a space, increment result
- 948     is-space?:bool <- equal c, 32/space
- 949     {
- 950       break-unless is-space?
- 951       result <- add result, 1
- 952     }
- 953     # if c is not a space, reset result
- 954     {
- 955       break-if is-space?
- 956       result <- copy 0
- 957     }
- 958     loop
- 959   }
- 960 ]
- 961 
- 962 scenario editor-moves-cursor-down-after-inserting-newline-2 [
- 963   local-scope
- 964   assume-screen 10/width, 5/height
- 965   e:&:editor <- new-editor [abc], 1/left, 10/right
- 966   assume-console [
- 967     type [0
- 968 1]
- 969   ]
- 970   run [
- 971     editor-event-loop screen, console, e
- 972   ]
- 973   screen-should-contain [
- 974     .          .
- 975     . 0        .
- 976     . 1abc     .
- 977     . ╌╌╌╌╌╌╌╌╌.
- 978     .          .
- 979   ]
- 980 ]
- 981 
- 982 scenario editor-clears-previous-line-completely-after-inserting-newline [
- 983   local-scope
- 984   assume-screen 10/width, 5/height
- 985   e:&:editor <- new-editor [abcde], 0/left, 5/right
- 986   editor-render screen, e
- 987   screen-should-contain [
- 988     .          .
- 989     .abcd↩     .
- 990     .e         .
- 991     .╌╌╌╌╌     .
- 992     .          .
- 993   ]
- 994   assume-console [
- 995     press enter
- 996   ]
- 997   run [
- 998     editor-event-loop screen, console, e
- 999   ]
-1000   # line should be fully cleared
-1001   screen-should-contain [
-1002     .          .
-1003     .          .
-1004     .abcd↩     .
-1005     .e         .
-1006     .╌╌╌╌╌     .
-1007   ]
-1008 ]
-1009 
-1010 scenario editor-splits-wrapped-line-after-inserting-newline [
-1011   local-scope
-1012   assume-screen 10/width, 5/height
-1013   e:&:editor <- new-editor [abcdef], 0/left, 5/right
-1014   editor-render screen, e
-1015   screen-should-contain [
-1016     .          .
-1017     .abcd↩     .
-1018     .ef        .
-1019     .╌╌╌╌╌     .
-1020     .          .
-1021   ]
-1022   assume-console [
-1023     left-click 2, 0
-1024     press enter
-1025   ]
-1026   run [
-1027     editor-event-loop screen, console, e
-1028     10:num/raw <- get *e, cursor-row:offset
-1029     11:num/raw <- get *e, cursor-column:offset
-1030   ]
-1031   screen-should-contain [
-1032     .          .
-1033     .abcd      .
-1034     .ef        .
-1035     .╌╌╌╌╌     .
-1036   ]
-1037   memory-should-contain [
-1038     10 <- 2  # cursor-row
-1039     11 <- 0  # cursor-column
-1040   ]
-1041 ]
-1042 
-1043 scenario editor-inserts-indent-after-newline [
-1044   local-scope
-1045   assume-screen 10/width, 10/height
-1046   s:text <- new [ab
-1047   cd
-1048 ef]
-1049   e:&:editor <- new-editor s, 0/left, 10/right
-1050   # position cursor after 'cd' and hit 'newline'
-1051   assume-console [
-1052     left-click 2, 8
-1053     type [
-1054 ]
-1055   ]
-1056   run [
-1057     editor-event-loop screen, console, e
-1058     3:num/raw <- get *e, cursor-row:offset
-1059     4:num/raw <- get *e, cursor-column:offset
-1060   ]
-1061   # cursor should be below start of previous line
-1062   memory-should-contain [
-1063     3 <- 3  # cursor row
-1064     4 <- 2  # cursor column (indented)
-1065   ]
-1066 ]
-1067 
-1068 scenario editor-skips-indent-around-paste [
-1069   local-scope
-1070   assume-screen 10/width, 10/height
-1071   s:text <- new [ab
-1072   cd
-1073 ef]
-1074   e:&:editor <- new-editor s, 0/left, 10/right
-1075   # position cursor after 'cd' and hit 'newline' surrounded by paste markers
-1076   assume-console [
-1077     left-click 2, 8
-1078     press 65507  # start paste
-1079     press enter
-1080     press 65506  # end paste
-1081   ]
-1082   run [
-1083     editor-event-loop screen, console, e
-1084     3:num/raw <- get *e, cursor-row:offset
-1085     4:num/raw <- get *e, cursor-column:offset
-1086   ]
-1087   # cursor should be below start of previous line
-1088   memory-should-contain [
-1089     3 <- 3  # cursor row
-1090     4 <- 0  # cursor column (not indented)
-1091   ]
-1092 ]
-1093 
-1094 after <handle-special-key> [
-1095   {
-1096     paste-start?:bool <- equal k, 65507/paste-start
-1097     break-unless paste-start?
-1098     *editor <- put *editor, indent?:offset, false
-1099     return true/go-render
-1100   }
-1101 ]
-1102 
-1103 after <handle-special-key> [
-1104   {
-1105     paste-end?:bool <- equal k, 65506/paste-end
-1106     break-unless paste-end?
-1107     *editor <- put *editor, indent?:offset, true
-1108     return true/go-render
-1109   }
-1110 ]
-1111 
-1112 ## helpers
-1113 
-1114 def draw-horizontal screen:&:screen, row:num, x:num, right:num -> screen:&:screen [
-1115   local-scope
-1116   load-inputs
-1117   height:num <- screen-height screen
-1118   past-bottom?:bool <- greater-or-equal row, height
-1119   return-if past-bottom?
-1120   style:char, style-found?:bool <- next-input
-1121   {
-1122     break-if style-found?
-1123     style <- copy 9472/horizontal
-1124   }
-1125   color:num, color-found?:bool <- next-input
-1126   {
-1127     # default color to white
-1128     break-if color-found?
-1129     color <- copy 245/grey
-1130   }
-1131   bg-color:num, bg-color-found?:bool <- next-input
-1132   {
-1133     break-if bg-color-found?
-1134     bg-color <- copy 0/black
-1135   }
-1136   screen <- move-cursor screen, row, x
-1137   {
-1138     continue?:bool <- lesser-or-equal x, right  # right is inclusive, to match editor semantics
-1139     break-unless continue?
-1140     print screen, style, color, bg-color
-1141     x <- add x, 1
-1142     loop
-1143   }
-1144 ]
-
- - - diff --git a/html/edit/003-shortcuts.mu.html b/html/edit/003-shortcuts.mu.html deleted file mode 100644 index ec4096b6..00000000 --- a/html/edit/003-shortcuts.mu.html +++ /dev/null @@ -1,4526 +0,0 @@ - - - - -Mu - edit/003-shortcuts.mu - - - - - - - - - - -https://github.com/akkartik/mu/blob/master/edit/003-shortcuts.mu -
-   1 ## special shortcuts for manipulating the editor
-   2 # Some keys on the keyboard generate unicode characters, others generate
-   3 # terminfo key codes. We need to modify different places in the two cases.
-   4 
-   5 # tab - insert two spaces
-   6 
-   7 scenario editor-inserts-two-spaces-on-tab [
-   8   local-scope
-   9   assume-screen 10/width, 5/height
-  10   s:text <- new [ab
-  11 cd]
-  12   e:&:editor <- new-editor s, 0/left, 5/right
-  13   editor-render screen, e
-  14   $clear-trace
-  15   assume-console [
-  16     press tab
-  17   ]
-  18   run [
-  19     editor-event-loop screen, console, e
-  20   ]
-  21   screen-should-contain [
-  22     .          .
-  23     .  ab      .
-  24     .cd        .
-  25   ]
-  26   # we render at most two editor rows worth (one row for each space)
-  27   check-trace-count-for-label-lesser-than 10, [print-character]
-  28 ]
-  29 
-  30 scenario editor-inserts-two-spaces-and-wraps-line-on-tab [
-  31   local-scope
-  32   assume-screen 10/width, 5/height
-  33   e:&:editor <- new-editor [abcd], 0/left, 5/right
-  34   editor-render screen, e
-  35   $clear-trace
-  36   assume-console [
-  37     press tab
-  38   ]
-  39   run [
-  40     editor-event-loop screen, console, e
-  41   ]
-  42   screen-should-contain [
-  43     .          .
-  44     .  ab↩     .
-  45     .cd        .
-  46   ]
-  47   # we re-render the whole editor
-  48   check-trace-count-for-label-greater-than 10, [print-character]
-  49 ]
-  50 
-  51 after <handle-special-character> [
-  52   {
-  53     tab?:bool <- equal c, 9/tab
-  54     break-unless tab?
-  55     <begin-insert-character>
-  56     # todo: decompose insert-at-cursor into editor update and screen update,
-  57     # so that 'tab' doesn't render the current line multiple times
-  58     insert-at-cursor editor, 32/space, screen
-  59     go-render? <- insert-at-cursor editor, 32/space, screen
-  60     <end-insert-character>
-  61     return
-  62   }
-  63 ]
-  64 
-  65 # backspace - delete character before cursor
-  66 
-  67 scenario editor-handles-backspace-key [
-  68   local-scope
-  69   assume-screen 10/width, 5/height
-  70   e:&:editor <- new-editor [abc], 0/left, 10/right
-  71   editor-render screen, e
-  72   $clear-trace
-  73   assume-console [
-  74     left-click 1, 1
-  75     press backspace
-  76   ]
-  77   run [
-  78     editor-event-loop screen, console, e
-  79     4:num/raw <- get *e, cursor-row:offset
-  80     5:num/raw <- get *e, cursor-column:offset
-  81   ]
-  82   screen-should-contain [
-  83     .          .
-  84     .bc        .
-  85     .╌╌╌╌╌╌╌╌╌╌.
-  86     .          .
-  87   ]
-  88   memory-should-contain [
-  89     4 <- 1
-  90     5 <- 0
-  91   ]
-  92   check-trace-count-for-label 3, [print-character]  # length of original line to overwrite
-  93 ]
-  94 
-  95 after <handle-special-character> [
-  96   {
-  97     delete-previous-character?:bool <- equal c, 8/backspace
-  98     break-unless delete-previous-character?
-  99     <begin-backspace-character>
- 100     go-render?:bool, backspaced-cell:&:duplex-list:char <- delete-before-cursor editor, screen
- 101     <end-backspace-character>
- 102     return
- 103   }
- 104 ]
- 105 
- 106 # return values:
- 107 #   go-render? - whether caller needs to update the screen
- 108 #   backspaced-cell - value deleted (or 0 if nothing was deleted) so we can save it for undo, etc.
- 109 def delete-before-cursor editor:&:editor, screen:&:screen -> go-render?:bool, backspaced-cell:&:duplex-list:char, editor:&:editor, screen:&:screen [
- 110   local-scope
- 111   load-inputs
- 112   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
- 113   data:&:duplex-list:char <- get *editor, data:offset
- 114   # if at start of text (before-cursor at § sentinel), return
- 115   prev:&:duplex-list:char <- prev before-cursor
- 116   return-unless prev, false/no-more-render, null/nothing-deleted
- 117   trace 10, [app], [delete-before-cursor]
- 118   original-row:num <- get *editor, cursor-row:offset
- 119   scroll?:bool <- move-cursor-coordinates-left editor
- 120   backspaced-cell:&:duplex-list:char <- copy before-cursor
- 121   data <- remove before-cursor, data  # will also neatly trim next/prev pointers in backspaced-cell/before-cursor
- 122   before-cursor <- copy prev
- 123   *editor <- put *editor, before-cursor:offset, before-cursor
- 124   return-if scroll?, true/go-render
- 125   screen-width:num <- screen-width screen
- 126   cursor-row:num <- get *editor, cursor-row:offset
- 127   cursor-column:num <- get *editor, cursor-column:offset
- 128   # did we just backspace over a newline?
- 129   same-row?:bool <- equal cursor-row, original-row
- 130   return-unless same-row?, true/go-render
- 131   left:num <- get *editor, left:offset
- 132   right:num <- get *editor, right:offset
- 133   curr:&:duplex-list:char <- next before-cursor
- 134   screen <- move-cursor screen, cursor-row, cursor-column
- 135   curr-column:num <- copy cursor-column
- 136   {
- 137     # hit right margin? give up and let caller render
- 138     at-right?:bool <- greater-or-equal curr-column, right
- 139     return-if at-right?, true/go-render
- 140     break-unless curr
- 141     # newline? done.
- 142     currc:char <- get *curr, value:offset
- 143     at-newline?:bool <- equal currc, 10/newline
- 144     break-if at-newline?
- 145     screen <- print screen, currc
- 146     curr-column <- add curr-column, 1
- 147     curr <- next curr
- 148     loop
- 149   }
- 150   # we're guaranteed not to be at the right margin
- 151   space:char <- copy 32/space
- 152   screen <- print screen, space
- 153   go-render? <- copy false
- 154 ]
- 155 
- 156 def move-cursor-coordinates-left editor:&:editor -> go-render?:bool, editor:&:editor [
- 157   local-scope
- 158   load-inputs
- 159   go-render?:bool <- copy false
- 160   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
- 161   cursor-row:num <- get *editor, cursor-row:offset
- 162   cursor-column:num <- get *editor, cursor-column:offset
- 163   left:num <- get *editor, left:offset
- 164   # if not at left margin, move one character left
- 165   {
- 166     at-left-margin?:bool <- equal cursor-column, left
- 167     break-if at-left-margin?
- 168     trace 10, [app], [decrementing cursor column]
- 169     cursor-column <- subtract cursor-column, 1
- 170     *editor <- put *editor, cursor-column:offset, cursor-column
- 171     return
- 172   }
- 173   # if at left margin, we must move to previous row:
- 174   top-of-screen?:bool <- equal cursor-row, 1  # exclude menu bar
- 175   {
- 176     break-if top-of-screen?
- 177     cursor-row <- subtract cursor-row, 1
- 178     *editor <- put *editor, cursor-row:offset, cursor-row
- 179   }
- 180   {
- 181     break-unless top-of-screen?
- 182     <scroll-up>
- 183     go-render? <- copy true
- 184   }
- 185   {
- 186     # case 1: if previous character was newline, figure out how long the previous line is
- 187     previous-character:char <- get *before-cursor, value:offset
- 188     previous-character-is-newline?:bool <- equal previous-character, 10/newline
- 189     break-unless previous-character-is-newline?
- 190     # compute length of previous line
- 191     trace 10, [app], [switching to previous line]
- 192     d:&:duplex-list:char <- get *editor, data:offset
- 193     end-of-line:num <- previous-line-length before-cursor, d
- 194     right:num <- get *editor, right:offset
- 195     width:num <- subtract right, left
- 196     wrap?:bool <- greater-than end-of-line, width
- 197     {
- 198       break-unless wrap?
- 199       _, column-offset:num <- divide-with-remainder end-of-line, width
- 200       cursor-column <- add left, column-offset
- 201       *editor <- put *editor, cursor-column:offset, cursor-column
- 202     }
- 203     {
- 204       break-if wrap?
- 205       cursor-column <- add left, end-of-line
- 206       *editor <- put *editor, cursor-column:offset, cursor-column
- 207     }
- 208     return
- 209   }
- 210   # case 2: if previous-character was not newline, we're just at a wrapped line
- 211   trace 10, [app], [wrapping to previous line]
- 212   right:num <- get *editor, right:offset
- 213   cursor-column <- subtract right, 1  # leave room for wrap icon
- 214   *editor <- put *editor, cursor-column:offset, cursor-column
- 215 ]
- 216 
- 217 # takes a pointer 'curr' into the doubly-linked list and its sentinel, counts
- 218 # the length of the previous line before the 'curr' pointer.
- 219 def previous-line-length curr:&:duplex-list:char, start:&:duplex-list:char -> result:num [
- 220   local-scope
- 221   load-inputs
- 222   result:num <- copy 0
- 223   return-unless curr
- 224   at-start?:bool <- equal curr, start
- 225   return-if at-start?
- 226   {
- 227     curr <- prev curr
- 228     break-unless curr
- 229     at-start?:bool <- equal curr, start
- 230     break-if at-start?
- 231     c:char <- get *curr, value:offset
- 232     at-newline?:bool <- equal c, 10/newline
- 233     break-if at-newline?
- 234     result <- add result, 1
- 235     loop
- 236   }
- 237 ]
- 238 
- 239 scenario editor-clears-last-line-on-backspace [
- 240   local-scope
- 241   assume-screen 10/width, 5/height
- 242   s:text <- new [ab
- 243 cd]
- 244   e:&:editor <- new-editor s, 0/left, 10/right
- 245   assume-console [
- 246     left-click 2, 0
- 247     press backspace
- 248   ]
- 249   run [
- 250     editor-event-loop screen, console, e
- 251     4:num/raw <- get *e, cursor-row:offset
- 252     5:num/raw <- get *e, cursor-column:offset
- 253   ]
- 254   screen-should-contain [
- 255     .          .
- 256     .abcd      .
- 257     .╌╌╌╌╌╌╌╌╌╌.
- 258     .          .
- 259   ]
- 260   memory-should-contain [
- 261     4 <- 1
- 262     5 <- 2
- 263   ]
- 264 ]
- 265 
- 266 scenario editor-joins-and-wraps-lines-on-backspace [
- 267   local-scope
- 268   assume-screen 10/width, 5/height
- 269   # initialize editor with two long-ish but non-wrapping lines
- 270   s:text <- new [abc def
- 271 ghi jkl]
- 272   e:&:editor <- new-editor s, 0/left, 10/right
- 273   editor-render screen, e
- 274   $clear-trace
- 275   # position the cursor at the start of the second and hit backspace
- 276   assume-console [
- 277     left-click 2, 0
- 278     press backspace
- 279   ]
- 280   run [
- 281     editor-event-loop screen, console, e
- 282   ]
- 283   # resulting single line should wrap correctly
- 284   screen-should-contain [
- 285     .          .
- 286     .abc defgh↩.
- 287     .i jkl     .
- 288     .╌╌╌╌╌╌╌╌╌╌.
- 289     .          .
- 290   ]
- 291 ]
- 292 
- 293 scenario editor-wraps-long-lines-on-backspace [
- 294   local-scope
- 295   assume-screen 10/width, 5/height
- 296   # initialize editor in part of the screen with a long line
- 297   e:&:editor <- new-editor [abc def ghij], 0/left, 8/right
- 298   editor-render screen, e
- 299   # confirm that it wraps
- 300   screen-should-contain [
- 301     .          .
- 302     .abc def↩  .
- 303     . ghij     .
- 304     .╌╌╌╌╌╌╌╌  .
- 305   ]
- 306   $clear-trace
- 307   # position the cursor somewhere in the middle of the top screen line and hit backspace
- 308   assume-console [
- 309     left-click 1, 4
- 310     press backspace
- 311   ]
- 312   run [
- 313     editor-event-loop screen, console, e
- 314   ]
- 315   # resulting single line should wrap correctly and not overflow its bounds
- 316   screen-should-contain [
- 317     .          .
- 318     .abcdef ↩  .
- 319     .ghij      .
- 320     .╌╌╌╌╌╌╌╌  .
- 321     .          .
- 322   ]
- 323 ]
- 324 
- 325 # delete - delete character at cursor
- 326 
- 327 scenario editor-handles-delete-key [
- 328   local-scope
- 329   assume-screen 10/width, 5/height
- 330   e:&:editor <- new-editor [abc], 0/left, 10/right
- 331   editor-render screen, e
- 332   $clear-trace
- 333   assume-console [
- 334     press delete
- 335   ]
- 336   run [
- 337     editor-event-loop screen, console, e
- 338   ]
- 339   screen-should-contain [
- 340     .          .
- 341     .bc        .
- 342     .╌╌╌╌╌╌╌╌╌╌.
- 343     .          .
- 344   ]
- 345   check-trace-count-for-label 3, [print-character]  # length of original line to overwrite
- 346   $clear-trace
- 347   assume-console [
- 348     press delete
- 349   ]
- 350   run [
- 351     editor-event-loop screen, console, e
- 352   ]
- 353   screen-should-contain [
- 354     .          .
- 355     .c         .
- 356     .╌╌╌╌╌╌╌╌╌╌.
- 357     .          .
- 358   ]
- 359   check-trace-count-for-label 2, [print-character]  # new length to overwrite
- 360 ]
- 361 
- 362 after <handle-special-key> [
- 363   {
- 364     delete-next-character?:bool <- equal k, 65522/delete
- 365     break-unless delete-next-character?
- 366     <begin-delete-character>
- 367     go-render?:bool, deleted-cell:&:duplex-list:char <- delete-at-cursor editor, screen
- 368     <end-delete-character>
- 369     return
- 370   }
- 371 ]
- 372 
- 373 def delete-at-cursor editor:&:editor, screen:&:screen -> go-render?:bool, deleted-cell:&:duplex-list:char, editor:&:editor, screen:&:screen [
- 374   local-scope
- 375   load-inputs
- 376   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
- 377   data:&:duplex-list:char <- get *editor, data:offset
- 378   deleted-cell:&:duplex-list:char <- next before-cursor
- 379   return-unless deleted-cell, false/don't-render
- 380   currc:char <- get *deleted-cell, value:offset
- 381   data <- remove deleted-cell, data
- 382   deleted-newline?:bool <- equal currc, 10/newline
- 383   return-if deleted-newline?, true/go-render
- 384   # wasn't a newline? render rest of line
- 385   curr:&:duplex-list:char <- next before-cursor  # refresh after remove above
- 386   cursor-row:num <- get *editor, cursor-row:offset
- 387   cursor-column:num <- get *editor, cursor-column:offset
- 388   screen <- move-cursor screen, cursor-row, cursor-column
- 389   curr-column:num <- copy cursor-column
- 390   screen-width:num <- screen-width screen
- 391   {
- 392     # hit right margin? give up and let caller render
- 393     at-right?:bool <- greater-or-equal curr-column, screen-width
- 394     return-if at-right?, true/go-render
- 395     break-unless curr
- 396     currc:char <- get *curr, value:offset
- 397     at-newline?:bool <- equal currc, 10/newline
- 398     break-if at-newline?
- 399     screen <- print screen, currc
- 400     curr-column <- add curr-column, 1
- 401     curr <- next curr
- 402     loop
- 403   }
- 404   # we're guaranteed not to be at the right margin
- 405   space:char <- copy 32/space
- 406   screen <- print screen, space
- 407   go-render? <- copy false
- 408 ]
- 409 
- 410 # right arrow
- 411 
- 412 scenario editor-moves-cursor-right-with-key [
- 413   local-scope
- 414   assume-screen 10/width, 5/height
- 415   e:&:editor <- new-editor [abc], 0/left, 10/right
- 416   editor-render screen, e
- 417   $clear-trace
- 418   assume-console [
- 419     press right-arrow
- 420     type [0]
- 421   ]
- 422   run [
- 423     editor-event-loop screen, console, e
- 424   ]
- 425   screen-should-contain [
- 426     .          .
- 427     .a0bc      .
- 428     .╌╌╌╌╌╌╌╌╌╌.
- 429     .          .
- 430   ]
- 431   check-trace-count-for-label 3, [print-character]  # 0 and following characters
- 432 ]
- 433 
- 434 after <handle-special-key> [
- 435   {
- 436     move-to-next-character?:bool <- equal k, 65514/right-arrow
- 437     break-unless move-to-next-character?
- 438     # if not at end of text
- 439     next-cursor:&:duplex-list:char <- next before-cursor
- 440     break-unless next-cursor
- 441     # scan to next character
- 442     <begin-move-cursor>
- 443     before-cursor <- copy next-cursor
- 444     *editor <- put *editor, before-cursor:offset, before-cursor
- 445     go-render?:bool <- move-cursor-coordinates-right editor, screen-height
- 446     screen <- move-cursor screen, cursor-row, cursor-column
- 447     undo-coalesce-tag:num <- copy 2/right-arrow
- 448     <end-move-cursor>
- 449     return
- 450   }
- 451 ]
- 452 
- 453 def move-cursor-coordinates-right editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
- 454   local-scope
- 455   load-inputs
- 456   before-cursor:&:duplex-list:char <- get *editor before-cursor:offset
- 457   cursor-row:num <- get *editor, cursor-row:offset
- 458   cursor-column:num <- get *editor, cursor-column:offset
- 459   left:num <- get *editor, left:offset
- 460   right:num <- get *editor, right:offset
- 461   # if crossed a newline, move cursor to start of next row
- 462   {
- 463     old-cursor-character:char <- get *before-cursor, value:offset
- 464     was-at-newline?:bool <- equal old-cursor-character, 10/newline
- 465     break-unless was-at-newline?
- 466     cursor-row <- add cursor-row, 1
- 467     *editor <- put *editor, cursor-row:offset, cursor-row
- 468     cursor-column <- copy left
- 469     *editor <- put *editor, cursor-column:offset, cursor-column
- 470     below-screen?:bool <- greater-or-equal cursor-row, screen-height  # must be equal
- 471     return-unless below-screen?, false/don't-render
- 472     <scroll-down>
- 473     cursor-row <- subtract cursor-row, 1  # bring back into screen range
- 474     *editor <- put *editor, cursor-row:offset, cursor-row
- 475     return true/go-render
- 476   }
- 477   # if the line wraps, move cursor to start of next row
- 478   {
- 479     # if we're at the column just before the wrap indicator
- 480     wrap-column:num <- subtract right, 1
- 481     at-wrap?:bool <- equal cursor-column, wrap-column
- 482     break-unless at-wrap?
- 483     # and if next character isn't newline
- 484     next:&:duplex-list:char <- next before-cursor
- 485     break-unless next
- 486     next-character:char <- get *next, value:offset
- 487     newline?:bool <- equal next-character, 10/newline
- 488     break-if newline?
- 489     cursor-row <- add cursor-row, 1
- 490     *editor <- put *editor, cursor-row:offset, cursor-row
- 491     cursor-column <- copy left
- 492     *editor <- put *editor, cursor-column:offset, cursor-column
- 493     below-screen?:bool <- greater-or-equal cursor-row, screen-height  # must be equal
- 494     return-unless below-screen?, false/no-more-render
- 495     <scroll-down>
- 496     cursor-row <- subtract cursor-row, 1  # bring back into screen range
- 497     *editor <- put *editor, cursor-row:offset, cursor-row
- 498     return true/go-render
- 499   }
- 500   # otherwise move cursor one character right
- 501   cursor-column <- add cursor-column, 1
- 502   *editor <- put *editor, cursor-column:offset, cursor-column
- 503   go-render? <- copy false
- 504 ]
- 505 
- 506 scenario editor-moves-cursor-to-next-line-with-right-arrow [
- 507   local-scope
- 508   assume-screen 10/width, 5/height
- 509   s:text <- new [abc
- 510 d]
- 511   e:&:editor <- new-editor s, 0/left, 10/right
- 512   editor-render screen, e
- 513   $clear-trace
- 514   # type right-arrow a few times to get to start of second line
- 515   assume-console [
- 516     press right-arrow
- 517     press right-arrow
- 518     press right-arrow
- 519     press right-arrow  # next line
- 520   ]
- 521   run [
- 522     editor-event-loop screen, console, e
- 523   ]
- 524   check-trace-count-for-label 0, [print-character]
- 525   # type something and ensure it goes where it should
- 526   assume-console [
- 527     type [0]
- 528   ]
- 529   run [
- 530     editor-event-loop screen, console, e
- 531   ]
- 532   screen-should-contain [
- 533     .          .
- 534     .abc       .
- 535     .0d        .
- 536     .╌╌╌╌╌╌╌╌╌╌.
- 537     .          .
- 538   ]
- 539   check-trace-count-for-label 2, [print-character]  # new length of second line
- 540 ]
- 541 
- 542 scenario editor-moves-cursor-to-next-line-with-right-arrow-2 [
- 543   local-scope
- 544   assume-screen 10/width, 5/height
- 545   s:text <- new [abc
- 546 d]
- 547   e:&:editor <- new-editor s, 1/left, 10/right
- 548   editor-render screen, e
- 549   assume-console [
- 550     press right-arrow
- 551     press right-arrow
- 552     press right-arrow
- 553     press right-arrow  # next line
- 554     type [0]
- 555   ]
- 556   run [
- 557     editor-event-loop screen, console, e
- 558   ]
- 559   screen-should-contain [
- 560     .          .
- 561     . abc      .
- 562     . 0d       .
- 563     . ╌╌╌╌╌╌╌╌╌.
- 564     .          .
- 565   ]
- 566 ]
- 567 
- 568 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow [
- 569   local-scope
- 570   assume-screen 10/width, 5/height
- 571   e:&:editor <- new-editor [abcdef], 0/left, 5/right
- 572   editor-render screen, e
- 573   $clear-trace
- 574   assume-console [
- 575     left-click 1, 3
- 576     press right-arrow
- 577   ]
- 578   run [
- 579     editor-event-loop screen, console, e
- 580     3:num/raw <- get *e, cursor-row:offset
- 581     4:num/raw <- get *e, cursor-column:offset
- 582   ]
- 583   screen-should-contain [
- 584     .          .
- 585     .abcd↩     .
- 586     .ef        .
- 587     .╌╌╌╌╌     .
- 588     .          .
- 589   ]
- 590   memory-should-contain [
- 591     3 <- 2
- 592     4 <- 0
- 593   ]
- 594   check-trace-count-for-label 0, [print-character]
- 595 ]
- 596 
- 597 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-2 [
- 598   local-scope
- 599   assume-screen 10/width, 5/height
- 600   # line just barely wrapping
- 601   e:&:editor <- new-editor [abcde], 0/left, 5/right
- 602   editor-render screen, e
- 603   $clear-trace
- 604   # position cursor at last character before wrap and hit right-arrow
- 605   assume-console [
- 606     left-click 1, 3
- 607     press right-arrow
- 608   ]
- 609   run [
- 610     editor-event-loop screen, console, e
- 611     3:num/raw <- get *e, cursor-row:offset
- 612     4:num/raw <- get *e, cursor-column:offset
- 613   ]
- 614   memory-should-contain [
- 615     3 <- 2
- 616     4 <- 0
- 617   ]
- 618   # now hit right arrow again
- 619   assume-console [
- 620     press right-arrow
- 621   ]
- 622   run [
- 623     editor-event-loop screen, console, e
- 624     3:num/raw <- get *e, cursor-row:offset
- 625     4:num/raw <- get *e, cursor-column:offset
- 626   ]
- 627   memory-should-contain [
- 628     3 <- 2
- 629     4 <- 1
- 630   ]
- 631   check-trace-count-for-label 0, [print-character]
- 632 ]
- 633 
- 634 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-3 [
- 635   local-scope
- 636   assume-screen 10/width, 5/height
- 637   e:&:editor <- new-editor [abcdef], 1/left, 6/right
- 638   editor-render screen, e
- 639   $clear-trace
- 640   assume-console [
- 641     left-click 1, 4
- 642     press right-arrow
- 643   ]
- 644   run [
- 645     editor-event-loop screen, console, e
- 646     3:num/raw <- get *e, cursor-row:offset
- 647     4:num/raw <- get *e, cursor-column:offset
- 648   ]
- 649   screen-should-contain [
- 650     .          .
- 651     . abcd↩    .
- 652     . ef       .
- 653     . ╌╌╌╌╌    .
- 654     .          .
- 655   ]
- 656   memory-should-contain [
- 657     3 <- 2
- 658     4 <- 1
- 659   ]
- 660   check-trace-count-for-label 0, [print-character]
- 661 ]
- 662 
- 663 scenario editor-moves-cursor-to-next-line-with-right-arrow-at-end-of-line [
- 664   local-scope
- 665   assume-screen 10/width, 5/height
- 666   s:text <- new [abc
- 667 d]
- 668   e:&:editor <- new-editor s, 0/left, 10/right
- 669   editor-render screen, e
- 670   $clear-trace
- 671   # move to end of line, press right-arrow, type a character
- 672   assume-console [
- 673     left-click 1, 3
- 674     press right-arrow
- 675     type [0]
- 676   ]
- 677   run [
- 678     editor-event-loop screen, console, e
- 679   ]
- 680   # new character should be in next line
- 681   screen-should-contain [
- 682     .          .
- 683     .abc       .
- 684     .0d        .
- 685     .╌╌╌╌╌╌╌╌╌╌.
- 686     .          .
- 687   ]
- 688   check-trace-count-for-label 2, [print-character]
- 689 ]
- 690 
- 691 # todo: ctrl-right: next word-end
- 692 
- 693 # left arrow
- 694 
- 695 scenario editor-moves-cursor-left-with-key [
- 696   local-scope
- 697   assume-screen 10/width, 5/height
- 698   e:&:editor <- new-editor [abc], 0/left, 10/right
- 699   editor-render screen, e
- 700   $clear-trace
- 701   assume-console [
- 702     left-click 1, 2
- 703     press left-arrow
- 704     type [0]
- 705   ]
- 706   run [
- 707     editor-event-loop screen, console, e
- 708   ]
- 709   screen-should-contain [
- 710     .          .
- 711     .a0bc      .
- 712     .╌╌╌╌╌╌╌╌╌╌.
- 713     .          .
- 714   ]
- 715   check-trace-count-for-label 3, [print-character]
- 716 ]
- 717 
- 718 after <handle-special-key> [
- 719   {
- 720     move-to-previous-character?:bool <- equal k, 65515/left-arrow
- 721     break-unless move-to-previous-character?
- 722     trace 10, [app], [left arrow]
- 723     # if not at start of text (before-cursor at § sentinel)
- 724     prev:&:duplex-list:char <- prev before-cursor
- 725     return-unless prev, false/don't-render
- 726     <begin-move-cursor>
- 727     go-render? <- move-cursor-coordinates-left editor
- 728     before-cursor <- copy prev
- 729     *editor <- put *editor, before-cursor:offset, before-cursor
- 730     undo-coalesce-tag:num <- copy 1/left-arrow
- 731     <end-move-cursor>
- 732     return
- 733   }
- 734 ]
- 735 
- 736 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line [
- 737   local-scope
- 738   assume-screen 10/width, 5/height
- 739   # initialize editor with two lines
- 740   s:text <- new [abc
- 741 d]
- 742   e:&:editor <- new-editor s, 0/left, 10/right
- 743   editor-render screen, e
- 744   $clear-trace
- 745   # position cursor at start of second line (so there's no previous newline)
- 746   assume-console [
- 747     left-click 2, 0
- 748     press left-arrow
- 749   ]
- 750   run [
- 751     editor-event-loop screen, console, e
- 752     3:num/raw <- get *e, cursor-row:offset
- 753     4:num/raw <- get *e, cursor-column:offset
- 754   ]
- 755   memory-should-contain [
- 756     3 <- 1
- 757     4 <- 3
- 758   ]
- 759   check-trace-count-for-label 0, [print-character]
- 760 ]
- 761 
- 762 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-2 [
- 763   local-scope
- 764   assume-screen 10/width, 5/height
- 765   # initialize editor with three lines
- 766   s:text <- new [abc
- 767 def
- 768 g]
- 769   e:&:editor <- new-editor s:text, 0/left, 10/right
- 770   editor-render screen, e
- 771   $clear-trace
- 772   # position cursor further down (so there's a newline before the character at
- 773   # the cursor)
- 774   assume-console [
- 775     left-click 3, 0
- 776     press left-arrow
- 777     type [0]
- 778   ]
- 779   run [
- 780     editor-event-loop screen, console, e
- 781   ]
- 782   screen-should-contain [
- 783     .          .
- 784     .abc       .
- 785     .def0      .
- 786     .g         .
- 787     .╌╌╌╌╌╌╌╌╌╌.
- 788   ]
- 789   check-trace-count-for-label 1, [print-character]  # just the '0'
- 790 ]
- 791 
- 792 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-3 [
- 793   local-scope
- 794   assume-screen 10/width, 5/height
- 795   s:text <- new [abc
- 796 def
- 797 g]
- 798   e:&:editor <- new-editor s, 0/left, 10/right
- 799   editor-render screen, e
- 800   $clear-trace
- 801   # position cursor at start of text, press left-arrow, then type a character
- 802   assume-console [
- 803     left-click 1, 0
- 804     press left-arrow
- 805     type [0]
- 806   ]
- 807   run [
- 808     editor-event-loop screen, console, e
- 809   ]
- 810   # left-arrow should have had no effect
- 811   screen-should-contain [
- 812     .          .
- 813     .0abc      .
- 814     .def       .
- 815     .g         .
- 816     .╌╌╌╌╌╌╌╌╌╌.
- 817   ]
- 818   check-trace-count-for-label 4, [print-character]  # length of first line
- 819 ]
- 820 
- 821 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-4 [
- 822   local-scope
- 823   assume-screen 10/width, 5/height
- 824   # initialize editor with text containing an empty line
- 825   s:text <- new [abc
- 826 
- 827 d]
- 828   e:&:editor <- new-editor s, 0/left, 10/right
- 829   editor-render screen, e:&:editor
- 830   $clear-trace
- 831   # position cursor right after empty line
- 832   assume-console [
- 833     left-click 3, 0
- 834     press left-arrow
- 835     type [0]
- 836   ]
- 837   run [
- 838     editor-event-loop screen, console, e
- 839   ]
- 840   screen-should-contain [
- 841     .          .
- 842     .abc       .
- 843     .0         .
- 844     .d         .
- 845     .╌╌╌╌╌╌╌╌╌╌.
- 846   ]
- 847   check-trace-count-for-label 1, [print-character]  # just the '0'
- 848 ]
- 849 
- 850 scenario editor-moves-across-screen-lines-across-wrap-with-left-arrow [
- 851   local-scope
- 852   assume-screen 10/width, 5/height
- 853   # initialize editor with a wrapping line
- 854   e:&:editor <- new-editor [abcdef], 0/left, 5/right
- 855   editor-render screen, e
- 856   $clear-trace
- 857   screen-should-contain [
- 858     .          .
- 859     .abcd↩     .
- 860     .ef        .
- 861     .╌╌╌╌╌     .
- 862     .          .
- 863   ]
- 864   # position cursor right after empty line
- 865   assume-console [
- 866     left-click 2, 0
- 867     press left-arrow
- 868   ]
- 869   run [
- 870     editor-event-loop screen, console, e
- 871     3:num/raw <- get *e, cursor-row:offset
- 872     4:num/raw <- get *e, cursor-column:offset
- 873   ]
- 874   memory-should-contain [
- 875     3 <- 1  # previous row
- 876     4 <- 3  # right margin except wrap icon
- 877   ]
- 878   check-trace-count-for-label 0, [print-character]
- 879 ]
- 880 
- 881 scenario editor-moves-across-screen-lines-to-wrapping-line-with-left-arrow [
- 882   local-scope
- 883   assume-screen 10/width, 5/height
- 884   # initialize editor with a wrapping line followed by a second line
- 885   s:text <- new [abcdef
- 886 g]
- 887   e:&:editor <- new-editor s, 0/left, 5/right
- 888   editor-render screen, e
- 889   $clear-trace
- 890   screen-should-contain [
- 891     .          .
- 892     .abcd↩     .
- 893     .ef        .
- 894     .g         .
- 895     .╌╌╌╌╌     .
- 896   ]
- 897   # position cursor right after empty line
- 898   assume-console [
- 899     left-click 3, 0
- 900     press left-arrow
- 901   ]
- 902   run [
- 903     editor-event-loop screen, console, e
- 904     3:num/raw <- get *e, cursor-row:offset
- 905     4:num/raw <- get *e, cursor-column:offset
- 906   ]
- 907   memory-should-contain [
- 908     3 <- 2  # previous row
- 909     4 <- 2  # end of wrapped line
- 910   ]
- 911   check-trace-count-for-label 0, [print-character]
- 912 ]
- 913 
- 914 scenario editor-moves-across-screen-lines-to-non-wrapping-line-with-left-arrow [
- 915   local-scope
- 916   assume-screen 10/width, 5/height
- 917   # initialize editor with a line on the verge of wrapping, followed by a second line
- 918   s:text <- new [abcd
- 919 e]
- 920   e:&:editor <- new-editor s, 0/left, 5/right
- 921   editor-render screen, e
- 922   $clear-trace
- 923   screen-should-contain [
- 924     .          .
- 925     .abcd      .
- 926     .e         .
- 927     .╌╌╌╌╌     .
- 928     .          .
- 929   ]
- 930   # position cursor right after empty line
- 931   assume-console [
- 932     left-click 2, 0
- 933     press left-arrow
- 934   ]
- 935   run [
- 936     editor-event-loop screen, console, e
- 937     3:num/raw <- get *e, cursor-row:offset
- 938     4:num/raw <- get *e, cursor-column:offset
- 939   ]
- 940   memory-should-contain [
- 941     3 <- 1  # previous row
- 942     4 <- 4  # end of wrapped line
- 943   ]
- 944   check-trace-count-for-label 0, [print-character]
- 945 ]
- 946 
- 947 # todo: ctrl-left: previous word-start
- 948 
- 949 # up arrow
- 950 
- 951 scenario editor-moves-to-previous-line-with-up-arrow [
- 952   local-scope
- 953   assume-screen 10/width, 5/height
- 954   s:text <- new [abc
- 955 def]
- 956   e:&:editor <- new-editor s, 0/left, 10/right
- 957   editor-render screen, e
- 958   $clear-trace
- 959   assume-console [
- 960     left-click 2, 1
- 961     press up-arrow
- 962   ]
- 963   run [
- 964     editor-event-loop screen, console, e
- 965     3:num/raw <- get *e, cursor-row:offset
- 966     4:num/raw <- get *e, cursor-column:offset
- 967   ]
- 968   memory-should-contain [
- 969     3 <- 1
- 970     4 <- 1
- 971   ]
- 972   check-trace-count-for-label 0, [print-character]
- 973   assume-console [
- 974     type [0]
- 975   ]
- 976   run [
- 977     editor-event-loop screen, console, e
- 978   ]
- 979   screen-should-contain [
- 980     .          .
- 981     .a0bc      .
- 982     .def       .
- 983     .╌╌╌╌╌╌╌╌╌╌.
- 984     .          .
- 985   ]
- 986 ]
- 987 
- 988 after <handle-special-key> [
- 989   {
- 990     move-to-previous-line?:bool <- equal k, 65517/up-arrow
- 991     break-unless move-to-previous-line?
- 992     <begin-move-cursor>
- 993     go-render? <- move-to-previous-line editor
- 994     undo-coalesce-tag:num <- copy 3/up-arrow
- 995     <end-move-cursor>
- 996     return
- 997   }
- 998 ]
- 999 
-1000 def move-to-previous-line editor:&:editor -> go-render?:bool, editor:&:editor [
-1001   local-scope
-1002   load-inputs
-1003   go-render?:bool <- copy false
-1004   cursor-row:num <- get *editor, cursor-row:offset
-1005   cursor-column:num <- get *editor, cursor-column:offset
-1006   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-1007   left:num <- get *editor, left:offset
-1008   right:num <- get *editor, right:offset
-1009   already-at-top?:bool <- lesser-or-equal cursor-row, 1/top
-1010   {
-1011     # if cursor not at top, move it
-1012     break-if already-at-top?
-1013     # if not at start of screen line, move to start of screen line (previous newline)
-1014     # then scan back another line
-1015     # if either step fails, give up without modifying cursor or coordinates
-1016     curr:&:duplex-list:char <- copy before-cursor
-1017     old:&:duplex-list:char <- copy curr
-1018     {
-1019       at-left?:bool <- equal cursor-column, left
-1020       break-if at-left?
-1021       curr <- before-previous-screen-line curr, editor
-1022       no-motion?:bool <- equal curr, old
-1023       return-if no-motion?
-1024     }
-1025     {
-1026       curr <- before-previous-screen-line curr, editor
-1027       no-motion?:bool <- equal curr, old
-1028       return-if no-motion?
-1029     }
-1030     before-cursor <- copy curr
-1031     *editor <- put *editor, before-cursor:offset, before-cursor
-1032     cursor-row <- subtract cursor-row, 1
-1033     *editor <- put *editor, cursor-row:offset, cursor-row
-1034     # scan ahead to right column or until end of line
-1035     target-column:num <- copy cursor-column
-1036     cursor-column <- copy left
-1037     *editor <- put *editor, cursor-column:offset, cursor-column
-1038     {
-1039       done?:bool <- greater-or-equal cursor-column, target-column
-1040       break-if done?
-1041       curr:&:duplex-list:char <- next before-cursor
-1042       break-unless curr
-1043       currc:char <- get *curr, value:offset
-1044       at-newline?:bool <- equal currc, 10/newline
-1045       break-if at-newline?
-1046       #
-1047       before-cursor <- copy curr
-1048       *editor <- put *editor, before-cursor:offset, before-cursor
-1049       cursor-column <- add cursor-column, 1
-1050       *editor <- put *editor, cursor-column:offset, cursor-column
-1051       loop
-1052     }
-1053     return
-1054   }
-1055   {
-1056     # if cursor already at top, scroll up
-1057     break-unless already-at-top?
-1058     <scroll-up>
-1059     return true/go-render
-1060   }
-1061 ]
-1062 
-1063 # Takes a pointer into the doubly-linked list, scans back to before start of
-1064 # previous *wrapped* line.
-1065 # Returns original if no next newline.
-1066 # Beware: never return null pointer.
-1067 def before-previous-screen-line in:&:duplex-list:char, editor:&:editor -> out:&:duplex-list:char [
-1068   local-scope
-1069   load-inputs
-1070   curr:&:duplex-list:char <- copy in
-1071   c:char <- get *curr, value:offset
-1072   # compute max, number of characters to skip
-1073   #   1 + len%(width-1)
-1074   #   except rotate second term to vary from 1 to width-1 rather than 0 to width-2
-1075   left:num <- get *editor, left:offset
-1076   right:num <- get *editor, right:offset
-1077   max-line-length:num <- subtract right, left, -1/exclusive-right, 1/wrap-icon
-1078   sentinel:&:duplex-list:char <- get *editor, data:offset
-1079   len:num <- previous-line-length curr, sentinel
-1080   {
-1081     break-if len
-1082     # empty line; just skip this newline
-1083     prev:&:duplex-list:char <- prev curr
-1084     return-unless prev, curr
-1085     return prev
-1086   }
-1087   _, max:num <- divide-with-remainder len, max-line-length
-1088   # remainder 0 => scan one width-worth
-1089   {
-1090     break-if max
-1091     max <- copy max-line-length
-1092   }
-1093   max <- add max, 1
-1094   count:num <- copy 0
-1095   # skip 'max' characters
-1096   {
-1097     done?:bool <- greater-or-equal count, max
-1098     break-if done?
-1099     prev:&:duplex-list:char <- prev curr
-1100     break-unless prev
-1101     curr <- copy prev
-1102     count <- add count, 1
-1103     loop
-1104   }
-1105   return curr
-1106 ]
-1107 
-1108 scenario editor-adjusts-column-at-previous-line [
-1109   local-scope
-1110   assume-screen 10/width, 5/height
-1111   s:text <- new [ab
-1112 def]
-1113   e:&:editor <- new-editor s, 0/left, 10/right
-1114   editor-render screen, e
-1115   $clear-trace
-1116   assume-console [
-1117     left-click 2, 3
-1118     press up-arrow
-1119   ]
-1120   run [
-1121     editor-event-loop screen, console, e
-1122     3:num/raw <- get *e, cursor-row:offset
-1123     4:num/raw <- get *e, cursor-column:offset
-1124   ]
-1125   memory-should-contain [
-1126     3 <- 1
-1127     4 <- 2
-1128   ]
-1129   check-trace-count-for-label 0, [print-character]
-1130   assume-console [
-1131     type [0]
-1132   ]
-1133   run [
-1134     editor-event-loop screen, console, e
-1135   ]
-1136   screen-should-contain [
-1137     .          .
-1138     .ab0       .
-1139     .def       .
-1140     .╌╌╌╌╌╌╌╌╌╌.
-1141     .          .
-1142   ]
-1143 ]
-1144 
-1145 scenario editor-adjusts-column-at-empty-line [
-1146   local-scope
-1147   assume-screen 10/width, 5/height
-1148   s:text <- new [
-1149 def]
-1150   e:&:editor <- new-editor s, 0/left, 10/right
-1151   editor-render screen, e
-1152   $clear-trace
-1153   assume-console [
-1154     left-click 2, 3
-1155     press up-arrow
-1156   ]
-1157   run [
-1158     editor-event-loop screen, console, e
-1159     3:num/raw <- get *e, cursor-row:offset
-1160     4:num/raw <- get *e, cursor-column:offset
-1161   ]
-1162   memory-should-contain [
-1163     3 <- 1
-1164     4 <- 0
-1165   ]
-1166   check-trace-count-for-label 0, [print-character]
-1167   assume-console [
-1168     type [0]
-1169   ]
-1170   run [
-1171     editor-event-loop screen, console, e
-1172   ]
-1173   screen-should-contain [
-1174     .          .
-1175     .0         .
-1176     .def       .
-1177     .╌╌╌╌╌╌╌╌╌╌.
-1178     .          .
-1179   ]
-1180 ]
-1181 
-1182 scenario editor-moves-to-previous-line-from-zero-margin [
-1183   local-scope
-1184   assume-screen 10/width, 5/height
-1185   # start out with three lines
-1186   s:text <- new [abc
-1187 def
-1188 ghi]
-1189   e:&:editor <- new-editor s, 0/left, 10/right
-1190   editor-render screen, e
-1191   $clear-trace
-1192   # click on the third line and hit up-arrow, so you end up just after a newline
-1193   assume-console [
-1194     left-click 3, 0
-1195     press up-arrow
-1196   ]
-1197   run [
-1198     editor-event-loop screen, console, e
-1199     3:num/raw <- get *e, cursor-row:offset
-1200     4:num/raw <- get *e, cursor-column:offset
-1201   ]
-1202   memory-should-contain [
-1203     3 <- 2
-1204     4 <- 0
-1205   ]
-1206   check-trace-count-for-label 0, [print-character]
-1207   assume-console [
-1208     type [0]
-1209   ]
-1210   run [
-1211     editor-event-loop screen, console, e
-1212   ]
-1213   screen-should-contain [
-1214     .          .
-1215     .abc       .
-1216     .0def      .
-1217     .ghi       .
-1218     .╌╌╌╌╌╌╌╌╌╌.
-1219   ]
-1220 ]
-1221 
-1222 scenario editor-moves-to-previous-line-from-left-margin [
-1223   local-scope
-1224   assume-screen 10/width, 5/height
-1225   # start out with three lines
-1226   s:text <- new [abc
-1227 def
-1228 ghi]
-1229   e:&:editor <- new-editor s, 1/left, 10/right
-1230   editor-render screen, e
-1231   $clear-trace
-1232   # click on the third line and hit up-arrow, so you end up just after a newline
-1233   assume-console [
-1234     left-click 3, 1
-1235     press up-arrow
-1236   ]
-1237   run [
-1238     editor-event-loop screen, console, e
-1239     3:num/raw <- get *e, cursor-row:offset
-1240     4:num/raw <- get *e, cursor-column:offset
-1241   ]
-1242   memory-should-contain [
-1243     3 <- 2
-1244     4 <- 1
-1245   ]
-1246   check-trace-count-for-label 0, [print-character]
-1247   assume-console [
-1248     type [0]
-1249   ]
-1250   run [
-1251     editor-event-loop screen, console, e
-1252   ]
-1253   screen-should-contain [
-1254     .          .
-1255     . abc      .
-1256     . 0def     .
-1257     . ghi      .
-1258     . ╌╌╌╌╌╌╌╌╌.
-1259   ]
-1260 ]
-1261 
-1262 scenario editor-moves-to-top-line-in-presence-of-wrapped-line [
-1263   local-scope
-1264   assume-screen 10/width, 5/height
-1265   e:&:editor <- new-editor [abcde], 0/left, 5/right
-1266   editor-render screen, e
-1267   screen-should-contain [
-1268     .          .
-1269     .abcd↩     .
-1270     .e         .
-1271     .╌╌╌╌╌     .
-1272   ]
-1273   $clear-trace
-1274   assume-console [
-1275     left-click 2, 0
-1276     press up-arrow
-1277   ]
-1278   run [
-1279     editor-event-loop screen, console, e
-1280     3:num/raw <- get *e, cursor-row:offset
-1281     4:num/raw <- get *e, cursor-column:offset
-1282   ]
-1283   memory-should-contain [
-1284     3 <- 1
-1285     4 <- 0
-1286   ]
-1287   check-trace-count-for-label 0, [print-character]
-1288   assume-console [
-1289     type [0]
-1290   ]
-1291   run [
-1292     editor-event-loop screen, console, e
-1293   ]
-1294   screen-should-contain [
-1295     .          .
-1296     .0abc↩     .
-1297     .de        .
-1298     .╌╌╌╌╌     .
-1299   ]
-1300 ]
-1301 
-1302 scenario editor-moves-to-top-line-in-presence-of-wrapped-line-2 [
-1303   local-scope
-1304   assume-screen 10/width, 5/height
-1305   s:text <- new [abc
-1306 defgh]
-1307   e:&:editor <- new-editor s, 0/left, 5/right
-1308   editor-render screen, e
-1309   screen-should-contain [
-1310     .          .
-1311     .abc       .
-1312     .defg↩     .
-1313     .h         .
-1314     .╌╌╌╌╌     .
-1315   ]
-1316   $clear-trace
-1317   assume-console [
-1318     left-click 3, 0
-1319     press up-arrow
-1320     press up-arrow
-1321   ]
-1322   run [
-1323     editor-event-loop screen, console, e
-1324     3:num/raw <- get *e, cursor-row:offset
-1325     4:num/raw <- get *e, cursor-column:offset
-1326   ]
-1327   memory-should-contain [
-1328     3 <- 1
-1329     4 <- 0
-1330   ]
-1331   check-trace-count-for-label 0, [print-character]
-1332   assume-console [
-1333     type [0]
-1334   ]
-1335   run [
-1336     editor-event-loop screen, console, e
-1337   ]
-1338   screen-should-contain [
-1339     .          .
-1340     .0abc      .
-1341     .defg↩     .
-1342     .h         .
-1343     .╌╌╌╌╌     .
-1344   ]
-1345 ]
-1346 
-1347 # down arrow
-1348 
-1349 scenario editor-moves-to-next-line-with-down-arrow [
-1350   local-scope
-1351   assume-screen 10/width, 5/height
-1352   s:text <- new [abc
-1353 def]
-1354   e:&:editor <- new-editor s, 0/left, 10/right
-1355   editor-render screen, e
-1356   $clear-trace
-1357   # cursor starts out at (1, 0)
-1358   assume-console [
-1359     press down-arrow
-1360   ]
-1361   run [
-1362     editor-event-loop screen, console, e
-1363     3:num/raw <- get *e, cursor-row:offset
-1364     4:num/raw <- get *e, cursor-column:offset
-1365   ]
-1366   # ..and ends at (2, 0)
-1367   memory-should-contain [
-1368     3 <- 2
-1369     4 <- 0
-1370   ]
-1371   check-trace-count-for-label 0, [print-character]
-1372   assume-console [
-1373     type [0]
-1374   ]
-1375   run [
-1376     editor-event-loop screen, console, e
-1377   ]
-1378   screen-should-contain [
-1379     .          .
-1380     .abc       .
-1381     .0def      .
-1382     .╌╌╌╌╌╌╌╌╌╌.
-1383     .          .
-1384   ]
-1385 ]
-1386 
-1387 after <handle-special-key> [
-1388   {
-1389     move-to-next-line?:bool <- equal k, 65516/down-arrow
-1390     break-unless move-to-next-line?
-1391     <begin-move-cursor>
-1392     go-render? <- move-to-next-line editor, screen-height
-1393     undo-coalesce-tag:num <- copy 4/down-arrow
-1394     <end-move-cursor>
-1395     return
-1396   }
-1397 ]
-1398 
-1399 def move-to-next-line editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
-1400   local-scope
-1401   load-inputs
-1402   cursor-row:num <- get *editor, cursor-row:offset
-1403   cursor-column:num <- get *editor, cursor-column:offset
-1404   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-1405   left:num <- get *editor, left:offset
-1406   right:num <- get *editor, right:offset
-1407   last-line:num <- subtract screen-height, 1
-1408   bottom:num <- get *editor, bottom:offset
-1409   at-bottom-of-screen?:bool <- greater-or-equal bottom, last-line
-1410   {
-1411     break-if before-cursor
-1412     {
-1413       break-if at-bottom-of-screen?
-1414       return false/don't-render
-1415     }
-1416     {
-1417       break-unless at-bottom-of-screen?
-1418       jump +try-to-scroll
-1419     }
-1420   }
-1421   next:&:duplex-list:char <- next before-cursor
-1422   {
-1423     break-if next
-1424     {
-1425       break-if at-bottom-of-screen?
-1426       return false/don't-render
-1427     }
-1428     {
-1429       break-unless at-bottom-of-screen?
-1430       jump +try-to-scroll
-1431     }
-1432   }
-1433   already-at-bottom?:bool <- greater-or-equal cursor-row, last-line
-1434   {
-1435     # if cursor not at bottom, move it
-1436     break-if already-at-bottom?
-1437     target-column:num <- copy cursor-column
-1438     # scan to start of next line
-1439     {
-1440       next:&:duplex-list:char <- next before-cursor
-1441       break-unless next
-1442       done?:bool <- greater-or-equal cursor-column, right
-1443       break-if done?
-1444       cursor-column <- add cursor-column, 1
-1445       before-cursor <- copy next
-1446       c:char <- get *next, value:offset
-1447       at-newline?:bool <- equal c, 10/newline
-1448       break-if at-newline?
-1449       loop
-1450     }
-1451     {
-1452       break-if next
-1453       {
-1454         break-if at-bottom-of-screen?
-1455         return false/don't-render
-1456       }
-1457       {
-1458         break-unless at-bottom-of-screen?
-1459         jump +try-to-scroll
-1460       }
-1461     }
-1462     cursor-row <- add cursor-row, 1
-1463     cursor-column <- copy left
-1464     {
-1465       next:&:duplex-list:char <- next before-cursor
-1466       break-unless next
-1467       c:char <- get *next, value:offset
-1468       at-newline?:bool <- equal c, 10/newline
-1469       break-if at-newline?
-1470       done?:bool <- greater-or-equal cursor-column, target-column
-1471       break-if done?
-1472       cursor-column <- add cursor-column, 1
-1473       before-cursor <- copy next
-1474       loop
-1475     }
-1476     *editor <- put *editor, before-cursor:offset, before-cursor
-1477     *editor <- put *editor, cursor-column:offset, cursor-column
-1478     *editor <- put *editor, cursor-row:offset, cursor-row
-1479     return false/don't-render
-1480   }
-1481   +try-to-scroll
-1482   <scroll-down>
-1483   go-render? <- copy true
-1484 ]
-1485 
-1486 scenario editor-adjusts-column-at-next-line [
-1487   local-scope
-1488   assume-screen 10/width, 5/height
-1489   # second line is shorter than first
-1490   s:text <- new [abcde
-1491 fg
-1492 hi]
-1493   e:&:editor <- new-editor s, 0/left, 10/right
-1494   editor-render screen, e
-1495   $clear-trace
-1496   # move to end of first line, then press down
-1497   assume-console [
-1498     left-click 1, 8
-1499     press down-arrow
-1500   ]
-1501   run [
-1502     editor-event-loop screen, console, e
-1503     3:num/raw <- get *e, cursor-row:offset
-1504     4:num/raw <- get *e, cursor-column:offset
-1505   ]
-1506   # cursor doesn't go vertically down, it goes to end of shorter line
-1507   memory-should-contain [
-1508     3 <- 2
-1509     4 <- 2
-1510   ]
-1511   check-trace-count-for-label 0, [print-character]
-1512   assume-console [
-1513     type [0]
-1514   ]
-1515   run [
-1516     editor-event-loop screen, console, e
-1517   ]
-1518   screen-should-contain [
-1519     .          .
-1520     .abcde     .
-1521     .fg0       .
-1522     .hi        .
-1523     .╌╌╌╌╌╌╌╌╌╌.
-1524   ]
-1525 ]
-1526 
-1527 scenario editor-moves-down-within-wrapped-line [
-1528   local-scope
-1529   assume-screen 10/width, 5/height
-1530   e:&:editor <- new-editor [abcdefghijklmno], 0/left, 10/right
-1531   editor-render screen, e
-1532   screen-should-contain [
-1533     .          .
-1534     .abcdefghi↩.
-1535     .jklmno    .
-1536     .╌╌╌╌╌╌╌╌╌╌.
-1537     .          .
-1538   ]
-1539   # position cursor on first screen line, but past end of second screen line
-1540   assume-console [
-1541     left-click 1, 8
-1542     press down-arrow
-1543   ]
-1544   run [
-1545     editor-event-loop screen, console, e
-1546     3:num/raw <- get *e, cursor-row:offset
-1547     4:num/raw <- get *e, cursor-column:offset
-1548   ]
-1549   # cursor should be at end of second screen line
-1550   memory-should-contain [
-1551     3 <- 2
-1552     4 <- 6
-1553   ]
-1554 ]
-1555 
-1556 # ctrl-a/home - move cursor to start of line
-1557 
-1558 scenario editor-moves-to-start-of-line-with-ctrl-a [
-1559   local-scope
-1560   assume-screen 10/width, 5/height
-1561   s:text <- new [123
-1562 456]
-1563   e:&:editor <- new-editor s, 0/left, 10/right
-1564   editor-render screen, e
-1565   $clear-trace
-1566   # start on second line, press ctrl-a
-1567   assume-console [
-1568     left-click 2, 3
-1569     press ctrl-a
-1570   ]
-1571   run [
-1572     editor-event-loop screen, console, e
-1573     4:num/raw <- get *e, cursor-row:offset
-1574     5:num/raw <- get *e, cursor-column:offset
-1575   ]
-1576   # cursor moves to start of line
-1577   memory-should-contain [
-1578     4 <- 2
-1579     5 <- 0
-1580   ]
-1581   check-trace-count-for-label 0, [print-character]
-1582 ]
-1583 
-1584 after <handle-special-character> [
-1585   {
-1586     move-to-start-of-line?:bool <- equal c, 1/ctrl-a
-1587     break-unless move-to-start-of-line?
-1588     <begin-move-cursor>
-1589     move-to-start-of-screen-line editor
-1590     undo-coalesce-tag:num <- copy 0/never
-1591     <end-move-cursor>
-1592     return false/don't-render
-1593   }
-1594 ]
-1595 
-1596 after <handle-special-key> [
-1597   {
-1598     move-to-start-of-line?:bool <- equal k, 65521/home
-1599     break-unless move-to-start-of-line?
-1600     <begin-move-cursor>
-1601     move-to-start-of-screen-line editor
-1602     undo-coalesce-tag:num <- copy 0/never
-1603     <end-move-cursor>
-1604     return false/don't-render
-1605   }
-1606 ]
-1607 
-1608 # handles wrapped lines
-1609 # precondition: cursor-column should be in a consistent state
-1610 def move-to-start-of-screen-line editor:&:editor -> editor:&:editor [
-1611   local-scope
-1612   load-inputs
-1613   # update cursor column
-1614   left:num <- get *editor, left:offset
-1615   col:num <- get *editor, cursor-column:offset
-1616   # update before-cursor
-1617   curr:&:duplex-list:char <- get *editor, before-cursor:offset
-1618   # while not at start of line, move
-1619   {
-1620     done?:bool <- equal col, left
-1621     break-if done?
-1622     assert curr, [move-to-start-of-line tried to move before start of text]
-1623     curr <- prev curr
-1624     col <- subtract col, 1
-1625     loop
-1626   }
-1627   *editor <- put *editor, cursor-column:offset, col
-1628   *editor <- put *editor, before-cursor:offset, curr
-1629 ]
-1630 
-1631 scenario editor-moves-to-start-of-line-with-ctrl-a-2 [
-1632   local-scope
-1633   assume-screen 10/width, 5/height
-1634   s:text <- new [123
-1635 456]
-1636   e:&:editor <- new-editor s, 0/left, 10/right
-1637   editor-render screen, e
-1638   $clear-trace
-1639   # start on first line (no newline before), press ctrl-a
-1640   assume-console [
-1641     left-click 1, 3
-1642     press ctrl-a
-1643   ]
-1644   run [
-1645     editor-event-loop screen, console, e
-1646     4:num/raw <- get *e, cursor-row:offset
-1647     5:num/raw <- get *e, cursor-column:offset
-1648   ]
-1649   # cursor moves to start of line
-1650   memory-should-contain [
-1651     4 <- 1
-1652     5 <- 0
-1653   ]
-1654   check-trace-count-for-label 0, [print-character]
-1655 ]
-1656 
-1657 scenario editor-moves-to-start-of-line-with-home [
-1658   local-scope
-1659   assume-screen 10/width, 5/height
-1660   s:text <- new [123
-1661 456]
-1662   e:&:editor <- new-editor s, 0/left, 10/right
-1663   $clear-trace
-1664   # start on second line, press 'home'
-1665   assume-console [
-1666     left-click 2, 3
-1667     press home
-1668   ]
-1669   run [
-1670     editor-event-loop screen, console, e
-1671     3:num/raw <- get *e, cursor-row:offset
-1672     4:num/raw <- get *e, cursor-column:offset
-1673   ]
-1674   # cursor moves to start of line
-1675   memory-should-contain [
-1676     3 <- 2
-1677     4 <- 0
-1678   ]
-1679   check-trace-count-for-label 0, [print-character]
-1680 ]
-1681 
-1682 scenario editor-moves-to-start-of-line-with-home-2 [
-1683   local-scope
-1684   assume-screen 10/width, 5/height
-1685   s:text <- new [123
-1686 456]
-1687   e:&:editor <- new-editor s, 0/left, 10/right
-1688   editor-render screen, e
-1689   $clear-trace
-1690   # start on first line (no newline before), press 'home'
-1691   assume-console [
-1692     left-click 1, 3
-1693     press home
-1694   ]
-1695   run [
-1696     editor-event-loop screen, console, e
-1697     3:num/raw <- get *e, cursor-row:offset
-1698     4:num/raw <- get *e, cursor-column:offset
-1699   ]
-1700   # cursor moves to start of line
-1701   memory-should-contain [
-1702     3 <- 1
-1703     4 <- 0
-1704   ]
-1705   check-trace-count-for-label 0, [print-character]
-1706 ]
-1707 
-1708 scenario editor-moves-to-start-of-screen-line-with-ctrl-a [
-1709   local-scope
-1710   assume-screen 10/width, 5/height
-1711   e:&:editor <- new-editor [123456], 0/left, 5/right
-1712   editor-render screen, e
-1713   screen-should-contain [
-1714     .          .
-1715     .1234↩     .
-1716     .56        .
-1717     .╌╌╌╌╌     .
-1718     .          .
-1719   ]
-1720   $clear-trace
-1721   # start on second line, press ctrl-a then up
-1722   assume-console [
-1723     left-click 2, 1
-1724     press ctrl-a
-1725     press up-arrow
-1726   ]
-1727   run [
-1728     editor-event-loop screen, console, e
-1729     4:num/raw <- get *e, cursor-row:offset
-1730     5:num/raw <- get *e, cursor-column:offset
-1731   ]
-1732   # cursor moves to start of first line
-1733   memory-should-contain [
-1734     4 <- 1  # cursor-row
-1735     5 <- 0  # cursor-column
-1736   ]
-1737   check-trace-count-for-label 0, [print-character]
-1738   # make sure before-cursor is in sync
-1739   assume-console [
-1740     type [a]
-1741   ]
-1742   run [
-1743     editor-event-loop screen, console, e
-1744     4:num/raw <- get *e, cursor-row:offset
-1745     5:num/raw <- get *e, cursor-column:offset
-1746   ]
-1747   screen-should-contain [
-1748     .          .
-1749     .a123↩     .
-1750     .456       .
-1751     .╌╌╌╌╌     .
-1752     .          .
-1753   ]
-1754   memory-should-contain [
-1755     4 <- 1  # cursor-row
-1756     5 <- 1  # cursor-column
-1757   ]
-1758 ]
-1759 
-1760 # ctrl-e/end - move cursor to end of line
-1761 
-1762 scenario editor-moves-to-end-of-line-with-ctrl-e [
-1763   local-scope
-1764   assume-screen 10/width, 5/height
-1765   s:text <- new [123
-1766 456]
-1767   e:&:editor <- new-editor s, 0/left, 10/right
-1768   editor-render screen, e
-1769   $clear-trace
-1770   # start on first line, press ctrl-e
-1771   assume-console [
-1772     left-click 1, 1
-1773     press ctrl-e
-1774   ]
-1775   run [
-1776     editor-event-loop screen, console, e
-1777     4:num/raw <- get *e, cursor-row:offset
-1778     5:num/raw <- get *e, cursor-column:offset
-1779   ]
-1780   # cursor moves to end of line
-1781   memory-should-contain [
-1782     4 <- 1
-1783     5 <- 3
-1784   ]
-1785   check-trace-count-for-label 0, [print-character]
-1786   # editor inserts future characters at cursor
-1787   assume-console [
-1788     type [z]
-1789   ]
-1790   run [
-1791     editor-event-loop screen, console, e
-1792     4:num/raw <- get *e, cursor-row:offset
-1793     5:num/raw <- get *e, cursor-column:offset
-1794   ]
-1795   memory-should-contain [
-1796     4 <- 1
-1797     5 <- 4
-1798   ]
-1799   screen-should-contain [
-1800     .          .
-1801     .123z      .
-1802     .456       .
-1803     .╌╌╌╌╌╌╌╌╌╌.
-1804     .          .
-1805   ]
-1806   check-trace-count-for-label 1, [print-character]
-1807 ]
-1808 
-1809 after <handle-special-character> [
-1810   {
-1811     move-to-end-of-line?:bool <- equal c, 5/ctrl-e
-1812     break-unless move-to-end-of-line?
-1813     <begin-move-cursor>
-1814     move-to-end-of-line editor
-1815     undo-coalesce-tag:num <- copy 0/never
-1816     <end-move-cursor>
-1817     return false/don't-render
-1818   }
-1819 ]
-1820 
-1821 after <handle-special-key> [
-1822   {
-1823     move-to-end-of-line?:bool <- equal k, 65520/end
-1824     break-unless move-to-end-of-line?
-1825     <begin-move-cursor>
-1826     move-to-end-of-line editor
-1827     undo-coalesce-tag:num <- copy 0/never
-1828     <end-move-cursor>
-1829     return false/don't-render
-1830   }
-1831 ]
-1832 
-1833 def move-to-end-of-line editor:&:editor -> editor:&:editor [
-1834   local-scope
-1835   load-inputs
-1836   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-1837   cursor-column:num <- get *editor, cursor-column:offset
-1838   right:num <- get *editor, right:offset
-1839   # while not at end of line, move
-1840   {
-1841     next:&:duplex-list:char <- next before-cursor
-1842     break-unless next  # end of text
-1843     nextc:char <- get *next, value:offset
-1844     at-end-of-line?:bool <- equal nextc, 10/newline
-1845     break-if at-end-of-line?
-1846     cursor-column <- add cursor-column, 1
-1847     at-right?:bool <- equal cursor-column, right
-1848     break-if at-right?
-1849     *editor <- put *editor, cursor-column:offset, cursor-column
-1850     before-cursor <- copy next
-1851     *editor <- put *editor, before-cursor:offset, before-cursor
-1852     loop
-1853   }
-1854 ]
-1855 
-1856 scenario editor-moves-to-end-of-line-with-ctrl-e-2 [
-1857   local-scope
-1858   assume-screen 10/width, 5/height
-1859   s:text <- new [123
-1860 456]
-1861   e:&:editor <- new-editor s, 0/left, 10/right
-1862   editor-render screen, e
-1863   $clear-trace
-1864   # start on second line (no newline after), press ctrl-e
-1865   assume-console [
-1866     left-click 2, 1
-1867     press ctrl-e
-1868   ]
-1869   run [
-1870     editor-event-loop screen, console, e
-1871     4:num/raw <- get *e, cursor-row:offset
-1872     5:num/raw <- get *e, cursor-column:offset
-1873   ]
-1874   # cursor moves to end of line
-1875   memory-should-contain [
-1876     4 <- 2
-1877     5 <- 3
-1878   ]
-1879   check-trace-count-for-label 0, [print-character]
-1880 ]
-1881 
-1882 scenario editor-moves-to-end-of-line-with-end [
-1883   local-scope
-1884   assume-screen 10/width, 5/height
-1885   s:text <- new [123
-1886 456]
-1887   e:&:editor <- new-editor s, 0/left, 10/right
-1888   editor-render screen, e
-1889   $clear-trace
-1890   # start on first line, press 'end'
-1891   assume-console [
-1892     left-click 1, 1
-1893     press end
-1894   ]
-1895   run [
-1896     editor-event-loop screen, console, e
-1897     3:num/raw <- get *e, cursor-row:offset
-1898     4:num/raw <- get *e, cursor-column:offset
-1899   ]
-1900   # cursor moves to end of line
-1901   memory-should-contain [
-1902     3 <- 1
-1903     4 <- 3
-1904   ]
-1905   check-trace-count-for-label 0, [print-character]
-1906 ]
-1907 
-1908 scenario editor-moves-to-end-of-line-with-end-2 [
-1909   local-scope
-1910   assume-screen 10/width, 5/height
-1911   s:text <- new [123
-1912 456]
-1913   e:&:editor <- new-editor s, 0/left, 10/right
-1914   editor-render screen, e
-1915   $clear-trace
-1916   # start on second line (no newline after), press 'end'
-1917   assume-console [
-1918     left-click 2, 1
-1919     press end
-1920   ]
-1921   run [
-1922     editor-event-loop screen, console, e
-1923     3:num/raw <- get *e, cursor-row:offset
-1924     4:num/raw <- get *e, cursor-column:offset
-1925   ]
-1926   # cursor moves to end of line
-1927   memory-should-contain [
-1928     3 <- 2
-1929     4 <- 3
-1930   ]
-1931   check-trace-count-for-label 0, [print-character]
-1932 ]
-1933 
-1934 scenario editor-moves-to-end-of-wrapped-line [
-1935   local-scope
-1936   assume-screen 10/width, 5/height
-1937   s:text <- new [123456
-1938 789]
-1939   e:&:editor <- new-editor s, 0/left, 5/right
-1940   editor-render screen, e
-1941   $clear-trace
-1942   # start on first line, press 'end'
-1943   assume-console [
-1944     left-click 1, 1
-1945     press end
-1946   ]
-1947   run [
-1948     editor-event-loop screen, console, e
-1949     10:num/raw <- get *e, cursor-row:offset
-1950     11:num/raw <- get *e, cursor-column:offset
-1951   ]
-1952   # cursor moves to end of line
-1953   memory-should-contain [
-1954     10 <- 1
-1955     11 <- 3
-1956   ]
-1957   # no prints
-1958   check-trace-count-for-label 0, [print-character]
-1959   # before-cursor is also consistent
-1960   assume-console [
-1961     type [a]
-1962   ]
-1963   run [
-1964     editor-event-loop screen, console, e
-1965   ]
-1966   screen-should-contain [
-1967     .          .
-1968     .123a↩     .
-1969     .456       .
-1970     .789       .
-1971     .╌╌╌╌╌     .
-1972   ]
-1973 ]
-1974 
-1975 # ctrl-u - delete text from start of line until (but not at) cursor
-1976 
-1977 scenario editor-deletes-to-start-of-line-with-ctrl-u [
-1978   local-scope
-1979   assume-screen 10/width, 5/height
-1980   s:text <- new [123
-1981 456]
-1982   e:&:editor <- new-editor s, 0/left, 10/right
-1983   editor-render screen, e
-1984   $clear-trace
-1985   # start on second line, press ctrl-u
-1986   assume-console [
-1987     left-click 2, 2
-1988     press ctrl-u
-1989   ]
-1990   run [
-1991     editor-event-loop screen, console, e
-1992   ]
-1993   # cursor deletes to start of line
-1994   screen-should-contain [
-1995     .          .
-1996     .123       .
-1997     .6         .
-1998     .╌╌╌╌╌╌╌╌╌╌.
-1999     .          .
-2000   ]
-2001   check-trace-count-for-label 10, [print-character]
-2002 ]
-2003 
-2004 after <handle-special-character> [
-2005   {
-2006     delete-to-start-of-line?:bool <- equal c, 21/ctrl-u
-2007     break-unless delete-to-start-of-line?
-2008     <begin-delete-to-start-of-line>
-2009     deleted-cells:&:duplex-list:char <- delete-to-start-of-line editor
-2010     <end-delete-to-start-of-line>
-2011     go-render?:bool <- minimal-render-for-ctrl-u screen, editor, deleted-cells
-2012     return
-2013   }
-2014 ]
-2015 
-2016 def minimal-render-for-ctrl-u screen:&:screen, editor:&:editor, deleted-cells:&:duplex-list:char -> go-render?:bool, screen:&:screen [
-2017   local-scope
-2018   load-inputs
-2019   curr-column:num <- get *editor, cursor-column:offset
-2020   # accumulate the current line as text and render it
-2021   buf:&:buffer:char <- new-buffer 30  # accumulator for the text we need to render
-2022   curr:&:duplex-list:char <- get *editor, before-cursor:offset
-2023   i:num <- copy curr-column
-2024   right:num <- get *editor, right:offset
-2025   {
-2026     # if we have a wrapped line, give up and render the whole screen
-2027     wrap?:bool <- greater-or-equal i, right
-2028     return-if wrap?, true/go-render
-2029     curr <- next curr
-2030     break-unless curr
-2031     c:char <- get *curr, value:offset
-2032     b:bool <- equal c, 10
-2033     break-if b
-2034     buf <- append buf, c
-2035     i <- add i, 1
-2036     loop
-2037   }
-2038   # if the line used to be wrapped, give up and render the whole screen
-2039   num-deleted-cells:num <- length deleted-cells
-2040   old-row-len:num <- add i, num-deleted-cells
-2041   left:num <- get *editor, left:offset
-2042   end:num <- subtract right, left
-2043   wrap?:bool <- greater-or-equal old-row-len, end
-2044   return-if wrap?, true/go-render
-2045   curr-line:text <- buffer-to-array buf
-2046   curr-row:num <- get *editor, cursor-row:offset
-2047   render-code screen, curr-line, curr-column, right, curr-row
-2048   return false/dont-render
-2049 ]
-2050 
-2051 def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [
-2052   local-scope
-2053   load-inputs
-2054   # compute range to delete
-2055   init:&:duplex-list:char <- get *editor, data:offset
-2056   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
-2057   update-top-of-screen?:bool <- copy false
-2058   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-2059   start:&:duplex-list:char <- copy before-cursor
-2060   end:&:duplex-list:char <- next before-cursor
-2061   {
-2062     at-start-of-text?:bool <- equal start, init
-2063     break-if at-start-of-text?
-2064     curr:char <- get *start, value:offset
-2065     at-start-of-line?:bool <- equal curr, 10/newline
-2066     break-if at-start-of-line?
-2067     # if we went past top-of-screen, make a note to update it as well
-2068     at-top-of-screen?:bool <- equal start, top-of-screen
-2069     update-top-of-screen?:bool <- or update-top-of-screen?, at-top-of-screen?
-2070     start <- prev start
-2071     assert start, [delete-to-start-of-line tried to move before start of text]
-2072     loop
-2073   }
-2074   # snip it out
-2075   result:&:duplex-list:char <- next start
-2076   remove-between start, end
-2077   # update top-of-screen if it's just been invalidated
-2078   {
-2079     break-unless update-top-of-screen?
-2080     put *editor, top-of-screen:offset, start
-2081   }
-2082   # adjust cursor
-2083   before-cursor <- copy start
-2084   *editor <- put *editor, before-cursor:offset, before-cursor
-2085   left:num <- get *editor, left:offset
-2086   *editor <- put *editor, cursor-column:offset, left
-2087   # if the line wrapped before, we may need to adjust cursor-row as well
-2088   right:num <- get *editor, right:offset
-2089   width:num <- subtract right, left
-2090   num-deleted:num <- length result
-2091   cursor-row-adjustment:num <- divide-with-remainder num-deleted, width
-2092   return-unless cursor-row-adjustment
-2093   cursor-row:num <- get *editor, cursor-row:offset
-2094   cursor-row-in-editor:num <- subtract cursor-row, 1  # ignore menubar
-2095   at-top?:bool <- lesser-or-equal cursor-row-in-editor, cursor-row-adjustment
-2096   {
-2097     break-unless at-top?
-2098     cursor-row <- copy 1  # top of editor, below menubar
-2099   }
-2100   {
-2101     break-if at-top?
-2102     cursor-row <- subtract cursor-row, cursor-row-adjustment
-2103   }
-2104   put *editor, cursor-row:offset, cursor-row
-2105 ]
-2106 
-2107 def render-code screen:&:screen, s:text, left:num, right:num, row:num -> row:num, screen:&:screen [
-2108   local-scope
-2109   load-inputs
-2110   return-unless s
-2111   color:num <- copy 7/white
-2112   column:num <- copy left
-2113   screen <- move-cursor screen, row, column
-2114   screen-height:num <- screen-height screen
-2115   i:num <- copy 0
-2116   len:num <- length *s
-2117   {
-2118     +next-character
-2119     done?:bool <- greater-or-equal i, len
-2120     break-if done?
-2121     done? <- greater-or-equal row, screen-height
-2122     break-if done?
-2123     c:char <- index *s, i
-2124     <character-c-received>
-2125     {
-2126       # newline? move to left rather than 0
-2127       newline?:bool <- equal c, 10/newline
-2128       break-unless newline?
-2129       # clear rest of line in this window
-2130       {
-2131         done?:bool <- greater-than column, right
-2132         break-if done?
-2133         space:char <- copy 32/space
-2134         print screen, space
-2135         column <- add column, 1
-2136         loop
-2137       }
-2138       row <- add row, 1
-2139       column <- copy left
-2140       screen <- move-cursor screen, row, column
-2141       i <- add i, 1
-2142       loop +next-character
-2143     }
-2144     {
-2145       # at right? wrap.
-2146       at-right?:bool <- equal column, right
-2147       break-unless at-right?
-2148       # print wrap icon
-2149       wrap-icon:char <- copy 8617/loop-back-to-left
-2150       print screen, wrap-icon, 245/grey
-2151       column <- copy left
-2152       row <- add row, 1
-2153       screen <- move-cursor screen, row, column
-2154       # don't increment i
-2155       loop +next-character
-2156     }
-2157     i <- add i, 1
-2158     print screen, c, color
-2159     column <- add column, 1
-2160     loop
-2161   }
-2162   was-at-left?:bool <- equal column, left
-2163   clear-line-until screen, right
-2164   {
-2165     break-if was-at-left?
-2166     row <- add row, 1
-2167   }
-2168   move-cursor screen, row, left
-2169 ]
-2170 
-2171 scenario editor-deletes-to-start-of-line-with-ctrl-u-2 [
-2172   local-scope
-2173   assume-screen 10/width, 5/height
-2174   s:text <- new [123
-2175 456]
-2176   e:&:editor <- new-editor s, 0/left, 10/right
-2177   editor-render screen, e
-2178   $clear-trace
-2179   # start on first line (no newline before), press ctrl-u
-2180   assume-console [
-2181     left-click 1, 2
-2182     press ctrl-u
-2183   ]
-2184   run [
-2185     editor-event-loop screen, console, e
-2186   ]
-2187   # cursor deletes to start of line
-2188   screen-should-contain [
-2189     .          .
-2190     .3         .
-2191     .456       .
-2192     .╌╌╌╌╌╌╌╌╌╌.
-2193     .          .
-2194   ]
-2195   check-trace-count-for-label 10, [print-character]
-2196 ]
-2197 
-2198 scenario editor-deletes-to-start-of-line-with-ctrl-u-3 [
-2199   local-scope
-2200   assume-screen 10/width, 5/height
-2201   s:text <- new [123
-2202 456]
-2203   e:&:editor <- new-editor s, 0/left, 10/right
-2204   editor-render screen, e
-2205   $clear-trace
-2206   # start past end of line, press ctrl-u
-2207   assume-console [
-2208     left-click 1, 3
-2209     press ctrl-u
-2210   ]
-2211   run [
-2212     editor-event-loop screen, console, e
-2213   ]
-2214   # cursor deletes to start of line
-2215   screen-should-contain [
-2216     .          .
-2217     .          .
-2218     .456       .
-2219     .╌╌╌╌╌╌╌╌╌╌.
-2220     .          .
-2221   ]
-2222   check-trace-count-for-label 10, [print-character]
-2223 ]
-2224 
-2225 scenario editor-deletes-to-start-of-final-line-with-ctrl-u [
-2226   local-scope
-2227   assume-screen 10/width, 5/height
-2228   s:text <- new [123
-2229 456]
-2230   e:&:editor <- new-editor s, 0/left, 10/right
-2231   editor-render screen, e
-2232   $clear-trace
-2233   # start past end of final line, press ctrl-u
-2234   assume-console [
-2235     left-click 2, 3
-2236     press ctrl-u
-2237   ]
-2238   run [
-2239     editor-event-loop screen, console, e
-2240   ]
-2241   # cursor deletes to start of line
-2242   screen-should-contain [
-2243     .          .
-2244     .123       .
-2245     .          .
-2246     .╌╌╌╌╌╌╌╌╌╌.
-2247     .          .
-2248   ]
-2249   check-trace-count-for-label 10, [print-character]
-2250 ]
-2251 
-2252 scenario editor-deletes-to-start-of-wrapped-line-with-ctrl-u [
-2253   local-scope
-2254   assume-screen 10/width, 10/height
-2255   # first line starts out wrapping
-2256   s:text <- new [123456
-2257 789]
-2258   e:&:editor <- new-editor s, 0/left, 5/right
-2259   editor-render screen, e
-2260   screen-should-contain [
-2261     .          .
-2262     .1234↩     .
-2263     .56        .
-2264     .789       .
-2265     .╌╌╌╌╌     .
-2266     .          .
-2267   ]
-2268   $clear-trace
-2269   # ctrl-u enough of the first line that it's no longer wrapping
-2270   assume-console [
-2271     left-click 1, 3
-2272     press ctrl-u
-2273   ]
-2274   run [
-2275     editor-event-loop screen, console, e
-2276   ]
-2277   # entire screen needs to be refreshed
-2278   screen-should-contain [
-2279     .          .
-2280     .456       .
-2281     .789       .
-2282     .╌╌╌╌╌     .
-2283     .          .
-2284   ]
-2285   check-trace-count-for-label 45, [print-character]
-2286 ]
-2287 
-2288 # sometimes hitting ctrl-u needs to adjust the cursor row
-2289 scenario editor-deletes-to-start-of-wrapped-line-with-ctrl-u-2 [
-2290   local-scope
-2291   assume-screen 10/width, 10/height
-2292   # third line starts out wrapping
-2293   s:text <- new [1
-2294 2
-2295 345678
-2296 9]
-2297   e:&:editor <- new-editor s, 0/left, 5/right
-2298   editor-render screen, e
-2299   screen-should-contain [
-2300     .          .
-2301     .1         .
-2302     .2         .
-2303     .3456↩     .
-2304     .78        .
-2305     .9         .
-2306     .╌╌╌╌╌     .
-2307     .          .
-2308   ]
-2309   # position cursor on screen line after the wrap and hit ctrl-u
-2310   assume-console [
-2311     left-click 4, 1  # on '8'
-2312     press ctrl-u
-2313   ]
-2314   run [
-2315     editor-event-loop screen, console, e
-2316     10:num/raw <- get *e, cursor-row:offset
-2317     11:num/raw <- get *e, cursor-column:offset
-2318   ]
-2319   screen-should-contain [
-2320     .          .
-2321     .1         .
-2322     .2         .
-2323     .8         .
-2324     .9         .
-2325     .╌╌╌╌╌     .
-2326     .          .
-2327   ]
-2328   # cursor moves up one screen line
-2329   memory-should-contain [
-2330     10 <- 3  # cursor-row
-2331     11 <- 0  # cursor-column
-2332   ]
-2333 ]
-2334 
-2335 # line wrapping twice (taking up 3 screen lines)
-2336 scenario editor-deletes-to-start-of-wrapped-line-with-ctrl-u-3 [
-2337   local-scope
-2338   assume-screen 10/width, 10/height
-2339   # third line starts out wrapping
-2340   s:text <- new [1
-2341 2
-2342 3456789abcd
-2343 e]
-2344   e:&:editor <- new-editor s, 0/left, 5/right
-2345   editor-render screen, e
-2346   assume-console [
-2347     left-click 4, 1  # on '8'
-2348   ]
-2349   editor-event-loop screen, console, e
-2350   screen-should-contain [
-2351     .          .
-2352     .1         .
-2353     .2         .
-2354     .3456↩     .
-2355     .789a↩     .
-2356     .bcd       .
-2357     .e         .
-2358     .╌╌╌╌╌     .
-2359     .          .
-2360   ]
-2361   assume-console [
-2362     left-click 5, 1
-2363     press ctrl-u
-2364   ]
-2365   run [
-2366     editor-event-loop screen, console, e
-2367     10:num/raw <- get *e, cursor-row:offset
-2368     11:num/raw <- get *e, cursor-column:offset
-2369   ]
-2370   screen-should-contain [
-2371     .          .
-2372     .1         .
-2373     .2         .
-2374     .cd        .
-2375     .e         .
-2376     .╌╌╌╌╌     .
-2377     .          .
-2378   ]
-2379   # make sure we adjusted cursor-row
-2380   memory-should-contain [
-2381     10 <- 3  # cursor-row
-2382     11 <- 0  # cursor-column
-2383   ]
-2384 ]
-2385 
-2386 # adjusting cursor row at the top of the screen
-2387 scenario editor-deletes-to-start-of-wrapped-line-with-ctrl-u-4 [
-2388   local-scope
-2389   assume-screen 10/width, 10/height
-2390   # first line starts out wrapping
-2391   s:text <- new [1234567
-2392 89]
-2393   e:&:editor <- new-editor s, 0/left, 5/right
-2394   editor-render screen, e
-2395   screen-should-contain [
-2396     .          .
-2397     .1234↩     .
-2398     .567       .
-2399     .89        .
-2400     .╌╌╌╌╌     .
-2401     .          .
-2402   ]
-2403   # position cursor on second screen line (after the wrap) and hit ctrl-u
-2404   assume-console [
-2405     left-click 2, 1
-2406     press ctrl-u
-2407   ]
-2408   run [
-2409     editor-event-loop screen, console, e
-2410     10:num/raw <- get *e, cursor-row:offset
-2411     11:num/raw <- get *e, cursor-column:offset
-2412   ]
-2413   screen-should-contain [
-2414     .          .
-2415     .67        .
-2416     .89        .
-2417     .╌╌╌╌╌     .
-2418     .          .
-2419   ]
-2420   # cursor moves up to screen line 1
-2421   memory-should-contain [
-2422     10 <- 1  # cursor-row
-2423     11 <- 0  # cursor-column
-2424   ]
-2425 ]
-2426 
-2427 # screen begins part-way through a wrapping line
-2428 scenario editor-deletes-to-start-of-wrapped-line-with-ctrl-u-5 [
-2429   local-scope
-2430   assume-screen 10/width, 10/height
-2431   # third line starts out wrapping
-2432   s:text <- new [1
-2433 2
-2434 345678
-2435 9]
-2436   e:&:editor <- new-editor s, 0/left, 5/right
-2437   editor-render screen, e
-2438   # position the '78' line at the top of the screen
-2439   assume-console [
-2440     left-click 4, 1  # on '8'
-2441     press ctrl-t
-2442   ]
-2443   editor-event-loop screen, console, e
-2444   screen-should-contain [
-2445     .          .
-2446     .78        .
-2447     .9         .
-2448     .╌╌╌╌╌     .
-2449     .          .
-2450   ]
-2451   assume-console [
-2452     left-click 1, 1
-2453     press ctrl-u
-2454   ]
-2455   run [
-2456     editor-event-loop screen, console, e
-2457     10:num/raw <- get *e, cursor-row:offset
-2458     11:num/raw <- get *e, cursor-column:offset
-2459   ]
-2460   # make sure we updated top-of-screen correctly
-2461   screen-should-contain [
-2462     .          .
-2463     .8         .
-2464     .9         .
-2465     .╌╌╌╌╌     .
-2466     .          .
-2467   ]
-2468   memory-should-contain [
-2469     10 <- 1  # cursor-row
-2470     11 <- 0  # cursor-column
-2471   ]
-2472   # the entire line is deleted, even the part not shown on screen
-2473   assume-console [
-2474     press up-arrow
-2475   ]
-2476   run [
-2477     editor-event-loop screen, console, e
-2478   ]
-2479   screen-should-contain [
-2480     .          .
-2481     .2         .
-2482     .8         .
-2483     .9         .
-2484     .╌╌╌╌╌     .
-2485     .          .
-2486   ]
-2487 ]
-2488 
-2489 # screen begins part-way through a line wrapping twice (taking up 3 screen lines)
-2490 scenario editor-deletes-to-start-of-wrapped-line-with-ctrl-u-6 [
-2491   local-scope
-2492   assume-screen 10/width, 10/height
-2493   # third line starts out wrapping
-2494   s:text <- new [1
-2495 2
-2496 3456789abcd
-2497 e]
-2498   e:&:editor <- new-editor s, 0/left, 5/right
-2499   editor-render screen, e
-2500   # position the 'bcd' line at the top of the screen
-2501   assume-console [
-2502     left-click 4, 1  # on '8'
-2503     press ctrl-t
-2504     press ctrl-s  # now on 'c'
-2505   ]
-2506   editor-event-loop screen, console, e
-2507   screen-should-contain [
-2508     .          .
-2509     .bcd       .
-2510     .e         .
-2511     .╌╌╌╌╌     .
-2512     .          .
-2513   ]
-2514   assume-console [
-2515     left-click 1, 1
-2516     press ctrl-u
-2517   ]
-2518   run [
-2519     editor-event-loop screen, console, e
-2520     10:num/raw <- get *e, cursor-row:offset
-2521     11:num/raw <- get *e, cursor-column:offset
-2522   ]
-2523   # make sure we updated top-of-screen correctly
-2524   screen-should-contain [
-2525     .          .
-2526     .cd        .
-2527     .e         .
-2528     .╌╌╌╌╌     .
-2529     .          .
-2530   ]
-2531   memory-should-contain [
-2532     10 <- 1  # cursor-row
-2533     11 <- 0  # cursor-column
-2534   ]
-2535   # the entire line is deleted, even the part not shown on screen
-2536   assume-console [
-2537     press up-arrow
-2538   ]
-2539   run [
-2540     editor-event-loop screen, console, e
-2541   ]
-2542   screen-should-contain [
-2543     .          .
-2544     .2         .
-2545     .cd        .
-2546     .e         .
-2547     .╌╌╌╌╌     .
-2548     .          .
-2549   ]
-2550 ]
-2551 
-2552 # ctrl-k - delete text from cursor to end of line (but not the newline)
-2553 
-2554 scenario editor-deletes-to-end-of-line-with-ctrl-k [
-2555   local-scope
-2556   assume-screen 10/width, 5/height
-2557   s:text <- new [123
-2558 456]
-2559   e:&:editor <- new-editor s, 0/left, 10/right
-2560   editor-render screen, e
-2561   $clear-trace
-2562   # start on first line, press ctrl-k
-2563   assume-console [
-2564     left-click 1, 1
-2565     press ctrl-k
-2566   ]
-2567   run [
-2568     editor-event-loop screen, console, e
-2569   ]
-2570   # cursor deletes to end of line
-2571   screen-should-contain [
-2572     .          .
-2573     .1         .
-2574     .456       .
-2575     .╌╌╌╌╌╌╌╌╌╌.
-2576     .          .
-2577   ]
-2578   check-trace-count-for-label 9, [print-character]
-2579 ]
-2580 
-2581 after <handle-special-character> [
-2582   {
-2583     delete-to-end-of-line?:bool <- equal c, 11/ctrl-k
-2584     break-unless delete-to-end-of-line?
-2585     <begin-delete-to-end-of-line>
-2586     deleted-cells:&:duplex-list:char <- delete-to-end-of-line editor
-2587     <end-delete-to-end-of-line>
-2588     # checks if we can do a minimal render and if we can it will do a minimal render
-2589     go-render?:bool <- minimal-render-for-ctrl-k screen, editor, deleted-cells
-2590     return
-2591   }
-2592 ]
-2593 
-2594 def minimal-render-for-ctrl-k screen:&:screen, editor:&:editor, deleted-cells:&:duplex-list:char -> go-render?:bool, screen:&:screen [
-2595   local-scope
-2596   load-inputs
-2597   # if we deleted nothing, there's nothing to render
-2598   return-unless deleted-cells, false/dont-render
-2599   # if the line used to wrap before, give up and render the whole screen
-2600   curr-column:num <- get *editor, cursor-column:offset
-2601   num-deleted-cells:num <- length deleted-cells
-2602   old-row-len:num <- add curr-column, num-deleted-cells
-2603   left:num <- get *editor, left:offset
-2604   right:num <- get *editor, right:offset
-2605   end:num <- subtract right, left
-2606   wrap?:bool <- greater-or-equal old-row-len, end
-2607   return-if wrap?, true/go-render
-2608   clear-line-until screen, right
-2609   return false/dont-render
-2610 ]
-2611 
-2612 def delete-to-end-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [
-2613   local-scope
-2614   load-inputs
-2615   # compute range to delete
-2616   start:&:duplex-list:char <- get *editor, before-cursor:offset
-2617   end:&:duplex-list:char <- next start
-2618   {
-2619     at-end-of-text?:bool <- equal end, null
-2620     break-if at-end-of-text?
-2621     curr:char <- get *end, value:offset
-2622     at-end-of-line?:bool <- equal curr, 10/newline
-2623     break-if at-end-of-line?
-2624     end <- next end
-2625     loop
-2626   }
-2627   # snip it out
-2628   result <- next start
-2629   remove-between start, end
-2630 ]
-2631 
-2632 scenario editor-deletes-to-end-of-line-with-ctrl-k-2 [
-2633   local-scope
-2634   assume-screen 10/width, 5/height
-2635   s:text <- new [123
-2636 456]
-2637   e:&:editor <- new-editor s, 0/left, 10/right
-2638   editor-render screen, e
-2639   $clear-trace
-2640   # start on second line (no newline after), press ctrl-k
-2641   assume-console [
-2642     left-click 2, 1
-2643     press ctrl-k
-2644   ]
-2645   run [
-2646     editor-event-loop screen, console, e
-2647   ]
-2648   # cursor deletes to end of line
-2649   screen-should-contain [
-2650     .          .
-2651     .123       .
-2652     .4         .
-2653     .╌╌╌╌╌╌╌╌╌╌.
-2654     .          .
-2655   ]
-2656   check-trace-count-for-label 9, [print-character]
-2657 ]
-2658 
-2659 scenario editor-deletes-to-end-of-line-with-ctrl-k-3 [
-2660   local-scope
-2661   assume-screen 10/width, 5/height
-2662   s:text <- new [123
-2663 456]
-2664   e:&:editor <- new-editor s, 0/left, 10/right
-2665   editor-render screen, e
-2666   $clear-trace
-2667   # start at end of line
-2668   assume-console [
-2669     left-click 1, 2
-2670     press ctrl-k
-2671   ]
-2672   run [
-2673     editor-event-loop screen, console, e
-2674   ]
-2675   # cursor deletes just last character
-2676   screen-should-contain [
-2677     .          .
-2678     .12        .
-2679     .456       .
-2680     .╌╌╌╌╌╌╌╌╌╌.
-2681     .          .
-2682   ]
-2683   check-trace-count-for-label 8, [print-character]
-2684 ]
-2685 
-2686 scenario editor-deletes-to-end-of-line-with-ctrl-k-4 [
-2687   local-scope
-2688   assume-screen 10/width, 5/height
-2689   s:text <- new [123
-2690 456]
-2691   e:&:editor <- new-editor s, 0/left, 10/right
-2692   editor-render screen, e
-2693   $clear-trace
-2694   # start past end of line
-2695   assume-console [
-2696     left-click 1, 3
-2697     press ctrl-k
-2698   ]
-2699   run [
-2700     editor-event-loop screen, console, e
-2701   ]
-2702   # cursor deletes nothing
-2703   screen-should-contain [
-2704     .          .
-2705     .123       .
-2706     .456       .
-2707     .╌╌╌╌╌╌╌╌╌╌.
-2708     .          .
-2709   ]
-2710   check-trace-count-for-label 7, [print-character]
-2711 ]
-2712 
-2713 scenario editor-deletes-to-end-of-line-with-ctrl-k-5 [
-2714   local-scope
-2715   assume-screen 10/width, 5/height
-2716   s:text <- new [123
-2717 456]
-2718   e:&:editor <- new-editor s, 0/left, 10/right
-2719   editor-render screen, e
-2720   $clear-trace
-2721   # start at end of text
-2722   assume-console [
-2723     left-click 2, 2
-2724     press ctrl-k
-2725   ]
-2726   run [
-2727     editor-event-loop screen, console, e
-2728   ]
-2729   # cursor deletes just the final character
-2730   screen-should-contain [
-2731     .          .
-2732     .123       .
-2733     .45        .
-2734     .╌╌╌╌╌╌╌╌╌╌.
-2735     .          .
-2736   ]
-2737   check-trace-count-for-label 8, [print-character]
-2738 ]
-2739 
-2740 scenario editor-deletes-to-end-of-line-with-ctrl-k-6 [
-2741   local-scope
-2742   assume-screen 10/width, 5/height
-2743   s:text <- new [123
-2744 456]
-2745   e:&:editor <- new-editor s, 0/left, 10/right
-2746   editor-render screen, e
-2747   $clear-trace
-2748   # start past end of text
-2749   assume-console [
-2750     left-click 2, 3
-2751     press ctrl-k
-2752   ]
-2753   run [
-2754     editor-event-loop screen, console, e
-2755   ]
-2756   # cursor deletes nothing
-2757   screen-should-contain [
-2758     .          .
-2759     .123       .
-2760     .456       .
-2761     .╌╌╌╌╌╌╌╌╌╌.
-2762     .          .
-2763   ]
-2764   # no prints necessary
-2765   check-trace-count-for-label 0, [print-character]
-2766 ]
-2767 
-2768 scenario editor-deletes-to-end-of-wrapped-line-with-ctrl-k [
-2769   local-scope
-2770   assume-screen 10/width, 5/height
-2771   # create an editor with the first line wrapping to a second screen row
-2772   s:text <- new [1234
-2773 567]
-2774   e:&:editor <- new-editor s, 0/left, 4/right
-2775   editor-render screen, e
-2776   $clear-trace
-2777   # delete all of the first wrapped line
-2778   assume-console [
-2779     press ctrl-k
-2780   ]
-2781   run [
-2782     editor-event-loop screen, console, e
-2783   ]
-2784   # screen shows an empty unwrapped first line
-2785   screen-should-contain [
-2786     .          .
-2787     .          .
-2788     .567       .
-2789     .╌╌╌╌      .
-2790     .          .
-2791   ]
-2792   # entire screen is refreshed
-2793   check-trace-count-for-label 16, [print-character]
-2794 ]
-2795 
-2796 # scroll down if necessary
-2797 
-2798 scenario editor-can-scroll-down-using-arrow-keys [
-2799   local-scope
-2800   # screen has 1 line for menu + 3 lines
-2801   assume-screen 10/width, 4/height
-2802   # initialize editor with >3 lines
-2803   s:text <- new [a
-2804 b
-2805 c
-2806 d]
-2807   e:&:editor <- new-editor s, 0/left, 10/right
-2808   editor-render screen, e
-2809   screen-should-contain [
-2810     .          .
-2811     .a         .
-2812     .b         .
-2813     .c         .
-2814   ]
-2815   # position cursor at last line, then try to move further down
-2816   assume-console [
-2817     left-click 3, 0
-2818     press down-arrow
-2819   ]
-2820   run [
-2821     editor-event-loop screen, console, e
-2822   ]
-2823   # screen slides by one line
-2824   screen-should-contain [
-2825     .          .
-2826     .b         .
-2827     .c         .
-2828     .d         .
-2829   ]
-2830 ]
-2831 
-2832 after <scroll-down> [
-2833   trace 10, [app], [scroll down]
-2834   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
-2835   left:num <- get *editor, left:offset
-2836   right:num <- get *editor, right:offset
-2837   max:num <- subtract right, left
-2838   old-top:&:duplex-list:char <- copy top-of-screen
-2839   top-of-screen <- before-start-of-next-line top-of-screen, max
-2840   *editor <- put *editor, top-of-screen:offset, top-of-screen
-2841   no-movement?:bool <- equal old-top, top-of-screen
-2842   return-if no-movement?, false/don't-render
-2843 ]
-2844 
-2845 after <scroll-down2> [
-2846   trace 10, [app], [scroll down]
-2847   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
-2848   left:num <- get *editor, left:offset
-2849   right:num <- get *editor, right:offset
-2850   max:num <- subtract right, left
-2851   old-top:&:duplex-list:char <- copy top-of-screen
-2852   top-of-screen <- before-start-of-next-line top-of-screen, max
-2853   *editor <- put *editor, top-of-screen:offset, top-of-screen
-2854   no-movement?:bool <- equal old-top, top-of-screen
-2855   return-if no-movement?
-2856 ]
-2857 
-2858 # Takes a pointer into the doubly-linked list, scans ahead at most 'max'
-2859 # positions until the next newline.
-2860 # Returns original if no next newline.
-2861 # Beware: never return null pointer.
-2862 def before-start-of-next-line original:&:duplex-list:char, max:num -> curr:&:duplex-list:char [
-2863   local-scope
-2864   load-inputs
-2865   count:num <- copy 0
-2866   curr:&:duplex-list:char <- copy original
-2867   # skip the initial newline if it exists
-2868   {
-2869     c:char <- get *curr, value:offset
-2870     at-newline?:bool <- equal c, 10/newline
-2871     break-unless at-newline?
-2872     curr <- next curr
-2873     count <- add count, 1
-2874   }
-2875   {
-2876     return-unless curr, original
-2877     done?:bool <- greater-or-equal count, max
-2878     break-if done?
-2879     c:char <- get *curr, value:offset
-2880     at-newline?:bool <- equal c, 10/newline
-2881     break-if at-newline?
-2882     curr <- next curr
-2883     count <- add count, 1
-2884     loop
-2885   }
-2886   return-unless curr, original
-2887   return curr
-2888 ]
-2889 
-2890 scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys [
-2891   local-scope
-2892   # screen has 1 line for menu + 3 lines
-2893   assume-screen 10/width, 4/height
-2894   # initialize editor with a long, wrapped line and more than a screen of
-2895   # other lines
-2896   s:text <- new [abcdef
-2897 g
-2898 h
-2899 i]
-2900   e:&:editor <- new-editor s, 0/left, 5/right
-2901   editor-render screen, e
-2902   screen-should-contain [
-2903     .          .
-2904     .abcd↩     .
-2905     .ef        .
-2906     .g         .
-2907   ]
-2908   # position cursor at last line, then try to move further down
-2909   assume-console [
-2910     left-click 3, 0
-2911     press down-arrow
-2912   ]
-2913   run [
-2914     editor-event-loop screen, console, e
-2915   ]
-2916   # screen shows partial wrapped line
-2917   screen-should-contain [
-2918     .          .
-2919     .ef        .
-2920     .g         .
-2921     .h         .
-2922   ]
-2923 ]
-2924 
-2925 scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys-2 [
-2926   local-scope
-2927   # screen has 1 line for menu + 3 lines
-2928   assume-screen 10/width, 4/height
-2929   # editor starts with a long line wrapping twice
-2930   s:text <- new [abcdefghij
-2931 k
-2932 l
-2933 m]
-2934   e:&:editor <- new-editor s, 0/left, 5/right
-2935   # position cursor at last line, then try to move further down
-2936   assume-console [
-2937     left-click 3, 0
-2938     press down-arrow
-2939   ]
-2940   run [
-2941     editor-event-loop screen, console, e
-2942   ]
-2943   # screen shows partial wrapped line containing a wrap icon
-2944   screen-should-contain [
-2945     .          .
-2946     .efgh↩     .
-2947     .ij        .
-2948     .k         .
-2949   ]
-2950   # scroll down again
-2951   assume-console [
-2952     press down-arrow
-2953   ]
-2954   run [
-2955     editor-event-loop screen, console, e
-2956   ]
-2957   # screen shows partial wrapped line
-2958   screen-should-contain [
-2959     .          .
-2960     .ij        .
-2961     .k         .
-2962     .l         .
-2963   ]
-2964 ]
-2965 
-2966 scenario editor-scrolls-down-when-line-wraps [
-2967   local-scope
-2968   # screen has 1 line for menu + 3 lines
-2969   assume-screen 5/width, 4/height
-2970   # editor contains a long line in the third line
-2971   s:text <- new [a
-2972 b
-2973 cdef]
-2974   e:&:editor <- new-editor s, 0/left, 5/right
-2975   # position cursor at end, type a character
-2976   assume-console [
-2977     left-click 3, 4
-2978     type [g]
-2979   ]
-2980   run [
-2981     editor-event-loop screen, console, e
-2982     3:num/raw <- get *e, cursor-row:offset
-2983     4:num/raw <- get *e, cursor-column:offset
-2984   ]
-2985   # screen scrolls
-2986   screen-should-contain [
-2987     .     .
-2988     .b    .
-2989     .cdef↩.
-2990     .g    .
-2991   ]
-2992   memory-should-contain [
-2993     3 <- 3
-2994     4 <- 1
-2995   ]
-2996 ]
-2997 
-2998 scenario editor-stops-scrolling-once-bottom-is-visible [
-2999   local-scope
-3000   # screen has 1 line for menu + 3 lines
-3001   assume-screen 10/width, 4/height
-3002   # initialize editor with 2 lines
-3003   s:text <- new [a
-3004 b]
-3005   e:&:editor <- new-editor s, 0/left, 10/right
-3006   editor-render screen, e
-3007   screen-should-contain [
-3008     .          .
-3009     .a         .
-3010     .b         .
-3011     .╌╌╌╌╌╌╌╌╌╌.
-3012   ]
-3013   # position cursor at last line, then try to move further down
-3014   assume-console [
-3015     left-click 3, 0
-3016     press down-arrow
-3017   ]
-3018   run [
-3019     editor-event-loop screen, console, e
-3020   ]
-3021   # no change since the bottom border was already visible
-3022   screen-should-contain [
-3023     .          .
-3024     .a         .
-3025     .b         .
-3026     .╌╌╌╌╌╌╌╌╌╌.
-3027   ]
-3028 ]
-3029 
-3030 scenario editor-scrolls-down-on-newline [
-3031   local-scope
-3032   assume-screen 5/width, 4/height
-3033   # position cursor after last line and type newline
-3034   s:text <- new [a
-3035 b
-3036 c]
-3037   e:&:editor <- new-editor s, 0/left, 5/right
-3038   assume-console [
-3039     left-click 3, 4
-3040     type [
-3041 ]
-3042   ]
-3043   run [
-3044     editor-event-loop screen, console, e
-3045     3:num/raw <- get *e, cursor-row:offset
-3046     4:num/raw <- get *e, cursor-column:offset
-3047   ]
-3048   # screen scrolls
-3049   screen-should-contain [
-3050     .     .
-3051     .b    .
-3052     .c    .
-3053     .     .
-3054   ]
-3055   memory-should-contain [
-3056     3 <- 3
-3057     4 <- 0
-3058   ]
-3059 ]
-3060 
-3061 scenario editor-scrolls-down-on-right-arrow [
-3062   local-scope
-3063   # screen has 1 line for menu + 3 lines
-3064   assume-screen 5/width, 4/height
-3065   # editor contains a wrapped line
-3066   s:text <- new [a
-3067 b
-3068 cdefgh]
-3069   e:&:editor <- new-editor s, 0/left, 5/right
-3070   # position cursor at end of screen and try to move right
-3071   assume-console [
-3072     left-click 3, 3
-3073     press right-arrow
-3074   ]
-3075   run [
-3076     editor-event-loop screen, console, e
-3077     3:num/raw <- get *e, cursor-row:offset
-3078     4:num/raw <- get *e, cursor-column:offset
-3079   ]
-3080   # screen scrolls
-3081   screen-should-contain [
-3082     .     .
-3083     .b    .
-3084     .cdef↩.
-3085     .gh   .
-3086   ]
-3087   memory-should-contain [
-3088     3 <- 3
-3089     4 <- 0
-3090   ]
-3091 ]
-3092 
-3093 scenario editor-scrolls-down-on-right-arrow-2 [
-3094   local-scope
-3095   # screen has 1 line for menu + 3 lines
-3096   assume-screen 5/width, 4/height
-3097   # editor contains more lines than can fit on screen
-3098   s:text <- new [a
-3099 b
-3100 c
-3101 d]
-3102   e:&:editor <- new-editor s, 0/left, 5/right
-3103   # position cursor at end of screen and try to move right
-3104   assume-console [
-3105     left-click 3, 3
-3106     press right-arrow
-3107   ]
-3108   run [
-3109     editor-event-loop screen, console, e
-3110     3:num/raw <- get *e, cursor-row:offset
-3111     4:num/raw <- get *e, cursor-column:offset
-3112   ]
-3113   # screen scrolls
-3114   screen-should-contain [
-3115     .     .
-3116     .b    .
-3117     .c    .
-3118     .d    .
-3119   ]
-3120   memory-should-contain [
-3121     3 <- 3
-3122     4 <- 0
-3123   ]
-3124 ]
-3125 
-3126 scenario editor-scrolls-at-end-on-down-arrow [
-3127   local-scope
-3128   assume-screen 10/width, 5/height
-3129   s:text <- new [abc
-3130 de]
-3131   e:&:editor <- new-editor s, 0/left, 10/right
-3132   editor-render screen, e
-3133   $clear-trace
-3134   # try to move down past end of text
-3135   assume-console [
-3136     left-click 2, 0
-3137     press down-arrow
-3138   ]
-3139   run [
-3140     editor-event-loop screen, console, e
-3141     3:num/raw <- get *e, cursor-row:offset
-3142     4:num/raw <- get *e, cursor-column:offset
-3143   ]
-3144   # no change
-3145   memory-should-contain [
-3146     3 <- 2
-3147     4 <- 0
-3148   ]
-3149 ]
-3150 
-3151 scenario editor-combines-page-and-line-scroll [
-3152   local-scope
-3153   # screen has 1 line for menu + 3 lines
-3154   assume-screen 10/width, 4/height
-3155   # initialize editor with a few pages of lines
-3156   s:text <- new [a
-3157 b
-3158 c
-3159 d
-3160 e
-3161 f
-3162 g]
-3163   e:&:editor <- new-editor s, 0/left, 5/right
-3164   editor-render screen, e
-3165   # scroll down one page and one line
-3166   assume-console [
-3167     press page-down
-3168     left-click 3, 0
-3169     press down-arrow
-3170   ]
-3171   run [
-3172     editor-event-loop screen, console, e
-3173   ]
-3174   # screen scrolls down 3 lines
-3175   screen-should-contain [
-3176     .          .
-3177     .d         .
-3178     .e         .
-3179     .f         .
-3180   ]
-3181 ]
-3182 
-3183 # scroll up if necessary
-3184 
-3185 scenario editor-can-scroll-up-using-arrow-keys [
-3186   local-scope
-3187   # screen has 1 line for menu + 3 lines
-3188   assume-screen 10/width, 4/height
-3189   # initialize editor with >3 lines
-3190   s:text <- new [a
-3191 b
-3192 c
-3193 d]
-3194   e:&:editor <- new-editor s, 0/left, 10/right
-3195   editor-render screen, e
-3196   screen-should-contain [
-3197     .          .
-3198     .a         .
-3199     .b         .
-3200     .c         .
-3201   ]
-3202   # position cursor at top of second page, then try to move up
-3203   assume-console [
-3204     press page-down
-3205     press up-arrow
-3206   ]
-3207   run [
-3208     editor-event-loop screen, console, e
-3209   ]
-3210   # screen slides by one line
-3211   screen-should-contain [
-3212     .          .
-3213     .b         .
-3214     .c         .
-3215     .d         .
-3216   ]
-3217 ]
-3218 
-3219 after <scroll-up> [
-3220   trace 10, [app], [scroll up]
-3221   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
-3222   old-top:&:duplex-list:char <- copy top-of-screen
-3223   top-of-screen <- before-previous-screen-line top-of-screen, editor
-3224   *editor <- put *editor, top-of-screen:offset, top-of-screen
-3225   no-movement?:bool <- equal old-top, top-of-screen
-3226   return-if no-movement?, false/don't-render
-3227 ]
-3228 
-3229 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys [
-3230   local-scope
-3231   # screen has 1 line for menu + 3 lines
-3232   assume-screen 10/width, 4/height
-3233   # initialize editor with a long, wrapped line and more than a screen of
-3234   # other lines
-3235   s:text <- new [abcdef
-3236 g
-3237 h
-3238 i]
-3239   e:&:editor <- new-editor s, 0/left, 5/right
-3240   editor-render screen, e
-3241   screen-should-contain [
-3242     .          .
-3243     .abcd↩     .
-3244     .ef        .
-3245     .g         .
-3246   ]
-3247   # position cursor at top of second page, just below wrapped line
-3248   assume-console [
-3249     press page-down
-3250   ]
-3251   run [
-3252     editor-event-loop screen, console, e
-3253   ]
-3254   screen-should-contain [
-3255     .          .
-3256     .g         .
-3257     .h         .
-3258     .i         .
-3259   ]
-3260   # now move up one line
-3261   assume-console [
-3262     press up-arrow
-3263   ]
-3264   run [
-3265     editor-event-loop screen, console, e
-3266   ]
-3267   # screen shows partial wrapped line
-3268   screen-should-contain [
-3269     .          .
-3270     .ef        .
-3271     .g         .
-3272     .h         .
-3273   ]
-3274 ]
-3275 
-3276 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-2 [
-3277   local-scope
-3278   # screen has 1 line for menu + 4 lines
-3279   assume-screen 10/width, 5/height
-3280   # editor starts with a long line wrapping twice, occupying 3 of the 4 lines
-3281   s:text <- new [abcdefghij
-3282 k
-3283 l
-3284 m]
-3285   e:&:editor <- new-editor s, 0/left, 5/right
-3286   editor-render screen, e
-3287   # position cursor at top of second page
-3288   assume-console [
-3289     press page-down
-3290   ]
-3291   run [
-3292     editor-event-loop screen, console, e
-3293   ]
-3294   screen-should-contain [
-3295     .          .
-3296     .k         .
-3297     .l         .
-3298     .m         .
-3299     .╌╌╌╌╌     .
-3300   ]
-3301   # move up one line
-3302   assume-console [
-3303     press up-arrow
-3304   ]
-3305   run [
-3306     editor-event-loop screen, console, e
-3307   ]
-3308   # screen shows partial wrapped line
-3309   screen-should-contain [
-3310     .          .
-3311     .ij        .
-3312     .k         .
-3313     .l         .
-3314     .m         .
-3315   ]
-3316   # move up a second line
-3317   assume-console [
-3318     press up-arrow
-3319   ]
-3320   run [
-3321     editor-event-loop screen, console, e
-3322   ]
-3323   # screen shows partial wrapped line
-3324   screen-should-contain [
-3325     .          .
-3326     .efgh↩     .
-3327     .ij        .
-3328     .k         .
-3329     .l         .
-3330   ]
-3331   # move up a third line
-3332   assume-console [
-3333     press up-arrow
-3334   ]
-3335   run [
-3336     editor-event-loop screen, console, e
-3337   ]
-3338   # screen shows partial wrapped line
-3339   screen-should-contain [
-3340     .          .
-3341     .abcd↩     .
-3342     .efgh↩     .
-3343     .ij        .
-3344     .k         .
-3345   ]
-3346 ]
-3347 
-3348 # same as editor-scrolls-up-past-wrapped-line-using-arrow-keys but length
-3349 # slightly off, just to prevent over-training
-3350 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-3 [
-3351   local-scope
-3352   # screen has 1 line for menu + 3 lines
-3353   assume-screen 10/width, 4/height
-3354   # initialize editor with a long, wrapped line and more than a screen of
-3355   # other lines
-3356   s:text <- new [abcdef
-3357 g
-3358 h
-3359 i]
-3360   e:&:editor <- new-editor s, 0/left, 6/right
-3361   editor-render screen, e
-3362   screen-should-contain [
-3363     .          .
-3364     .abcde↩    .
-3365     .f         .
-3366     .g         .
-3367   ]
-3368   # position cursor at top of second page, just below wrapped line
-3369   assume-console [
-3370     press page-down
-3371   ]
-3372   run [
-3373     editor-event-loop screen, console, e
-3374   ]
-3375   screen-should-contain [
-3376     .          .
-3377     .g         .
-3378     .h         .
-3379     .i         .
-3380   ]
-3381   # now move up one line
-3382   assume-console [
-3383     press up-arrow
-3384   ]
-3385   run [
-3386     editor-event-loop screen, console, e
-3387   ]
-3388   # screen shows partial wrapped line
-3389   screen-should-contain [
-3390     .          .
-3391     .f         .
-3392     .g         .
-3393     .h         .
-3394   ]
-3395 ]
-3396 
-3397 # check empty lines
-3398 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-4 [
-3399   local-scope
-3400   assume-screen 10/width, 4/height
-3401   # initialize editor with some lines around an empty line
-3402   s:text <- new [a
-3403 b
-3404 
-3405 c
-3406 d
-3407 e]
-3408   e:&:editor <- new-editor s, 0/left, 6/right
-3409   editor-render screen, e
-3410   assume-console [
-3411     press page-down
-3412   ]
-3413   run [
-3414     editor-event-loop screen, console, e
-3415   ]
-3416   screen-should-contain [
-3417     .          .
-3418     .          .
-3419     .c         .
-3420     .d         .
-3421   ]
-3422   assume-console [
-3423     press page-down
-3424   ]
-3425   run [
-3426     editor-event-loop screen, console, e
-3427   ]
-3428   screen-should-contain [
-3429     .          .
-3430     .d         .
-3431     .e         .
-3432     .╌╌╌╌╌╌    .
-3433   ]
-3434   assume-console [
-3435     press page-up
-3436   ]
-3437   run [
-3438     editor-event-loop screen, console, e
-3439   ]
-3440   screen-should-contain [
-3441     .          .
-3442     .          .
-3443     .c         .
-3444     .d         .
-3445   ]
-3446 ]
-3447 
-3448 scenario editor-scrolls-up-on-left-arrow [
-3449   local-scope
-3450   # screen has 1 line for menu + 3 lines
-3451   assume-screen 5/width, 4/height
-3452   # editor contains >3 lines
-3453   s:text <- new [a
-3454 b
-3455 c
-3456 d
-3457 e]
-3458   e:&:editor <- new-editor s, 0/left, 5/right
-3459   editor-render screen, e
-3460   # position cursor at top of second page
-3461   assume-console [
-3462     press page-down
-3463   ]
-3464   run [
-3465     editor-event-loop screen, console, e
-3466   ]
-3467   screen-should-contain [
-3468     .     .
-3469     .c    .
-3470     .d    .
-3471     .e    .
-3472   ]
-3473   # now try to move left
-3474   assume-console [
-3475     press left-arrow
-3476   ]
-3477   run [
-3478     editor-event-loop screen, console, e
-3479     3:num/raw <- get *e, cursor-row:offset
-3480     4:num/raw <- get *e, cursor-column:offset
-3481   ]
-3482   # screen scrolls
-3483   screen-should-contain [
-3484     .     .
-3485     .b    .
-3486     .c    .
-3487     .d    .
-3488   ]
-3489   memory-should-contain [
-3490     3 <- 1
-3491     4 <- 1
-3492   ]
-3493 ]
-3494 
-3495 scenario editor-can-scroll-up-to-start-of-file [
-3496   local-scope
-3497   # screen has 1 line for menu + 3 lines
-3498   assume-screen 10/width, 4/height
-3499   # initialize editor with >3 lines
-3500   s:text <- new [a
-3501 b
-3502 c
-3503 d]
-3504   e:&:editor <- new-editor s, 0/left, 10/right
-3505   editor-render screen, e
-3506   screen-should-contain [
-3507     .          .
-3508     .a         .
-3509     .b         .
-3510     .c         .
-3511   ]
-3512   # position cursor at top of second page, then try to move up to start of
-3513   # text
-3514   assume-console [
-3515     press page-down
-3516     press up-arrow
-3517     press up-arrow
-3518   ]
-3519   run [
-3520     editor-event-loop screen, console, e
-3521   ]
-3522   # screen slides by one line
-3523   screen-should-contain [
-3524     .          .
-3525     .a         .
-3526     .b         .
-3527     .c         .
-3528   ]
-3529   # try to move up again
-3530   assume-console [
-3531     press up-arrow
-3532   ]
-3533   run [
-3534     editor-event-loop screen, console, e
-3535   ]
-3536   # screen remains unchanged
-3537   screen-should-contain [
-3538     .          .
-3539     .a         .
-3540     .b         .
-3541     .c         .
-3542   ]
-3543 ]
-3544 
-3545 # ctrl-f/page-down - render next page if it exists
-3546 
-3547 scenario editor-can-scroll [
-3548   local-scope
-3549   assume-screen 10/width, 4/height
-3550   s:text <- new [a
-3551 b
-3552 c
-3553 d]
-3554   e:&:editor <- new-editor s, 0/left, 10/right
-3555   editor-render screen, e
-3556   screen-should-contain [
-3557     .          .
-3558     .a         .
-3559     .b         .
-3560     .c         .
-3561   ]
-3562   # scroll down
-3563   assume-console [
-3564     press page-down
-3565   ]
-3566   run [
-3567     editor-event-loop screen, console, e
-3568   ]
-3569   # screen shows next page
-3570   screen-should-contain [
-3571     .          .
-3572     .c         .
-3573     .d         .
-3574     .╌╌╌╌╌╌╌╌╌╌.
-3575   ]
-3576 ]
-3577 
-3578 after <handle-special-character> [
-3579   {
-3580     page-down?:bool <- equal c, 6/ctrl-f
-3581     break-unless page-down?
-3582     old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
-3583     <begin-move-cursor>
-3584     page-down editor
-3585     undo-coalesce-tag:num <- copy 0/never
-3586     <end-move-cursor>
-3587     top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
-3588     movement?:bool <- not-equal top-of-screen, old-top
-3589     return movement?/go-render
-3590   }
-3591 ]
-3592 
-3593 after <handle-special-key> [
-3594   {
-3595     page-down?:bool <- equal k, 65518/page-down
-3596     break-unless page-down?
-3597     old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
-3598     <begin-move-cursor>
-3599     page-down editor
-3600     undo-coalesce-tag:num <- copy 0/never
-3601     <end-move-cursor>
-3602     top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
-3603     movement?:bool <- not-equal top-of-screen, old-top
-3604     return movement?/go-render
-3605   }
-3606 ]
-3607 
-3608 # page-down skips entire wrapped lines, so it can't scroll past lines
-3609 # taking up the entire screen
-3610 def page-down editor:&:editor -> editor:&:editor [
-3611   local-scope
-3612   load-inputs
-3613   # if editor contents don't overflow screen, do nothing
-3614   bottom-of-screen:&:duplex-list:char <- get *editor, bottom-of-screen:offset
-3615   return-unless bottom-of-screen
-3616   # if not, position cursor at final character
-3617   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-3618   before-cursor:&:duplex-list:char <- prev bottom-of-screen
-3619   *editor <- put *editor, before-cursor:offset, before-cursor
-3620   # keep one line in common with previous page
-3621   {
-3622     last:char <- get *before-cursor, value:offset
-3623     newline?:bool <- equal last, 10/newline
-3624     break-unless newline?:bool
-3625     before-cursor <- prev before-cursor
-3626     *editor <- put *editor, before-cursor:offset, before-cursor
-3627   }
-3628   # move cursor and top-of-screen to start of that line
-3629   move-to-start-of-line editor
-3630   before-cursor <- get *editor, before-cursor:offset
-3631   *editor <- put *editor, top-of-screen:offset, before-cursor
-3632 ]
-3633 
-3634 # jump to previous newline
-3635 def move-to-start-of-line editor:&:editor -> editor:&:editor [
-3636   local-scope
-3637   load-inputs
-3638   # update cursor column
-3639   left:num <- get *editor, left:offset
-3640   cursor-column:num <- copy left
-3641   *editor <- put *editor, cursor-column:offset, cursor-column
-3642   # update before-cursor
-3643   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-3644   init:&:duplex-list:char <- get *editor, data:offset
-3645   # while not at start of line, move
-3646   {
-3647     at-start-of-text?:bool <- equal before-cursor, init
-3648     break-if at-start-of-text?
-3649     prev:char <- get *before-cursor, value:offset
-3650     at-start-of-line?:bool <- equal prev, 10/newline
-3651     break-if at-start-of-line?
-3652     before-cursor <- prev before-cursor
-3653     *editor <- put *editor, before-cursor:offset, before-cursor
-3654     assert before-cursor, [move-to-start-of-line tried to move before start of text]
-3655     loop
-3656   }
-3657 ]
-3658 
-3659 scenario editor-does-not-scroll-past-end [
-3660   local-scope
-3661   assume-screen 10/width, 4/height
-3662   s:text <- new [a
-3663 b]
-3664   e:&:editor <- new-editor s, 0/left, 10/right
-3665   editor-render screen, e
-3666   screen-should-contain [
-3667     .          .
-3668     .a         .
-3669     .b         .
-3670     .╌╌╌╌╌╌╌╌╌╌.
-3671   ]
-3672   # scroll down
-3673   assume-console [
-3674     press page-down
-3675   ]
-3676   run [
-3677     editor-event-loop screen, console, e
-3678   ]
-3679   # screen remains unmodified
-3680   screen-should-contain [
-3681     .          .
-3682     .a         .
-3683     .b         .
-3684     .╌╌╌╌╌╌╌╌╌╌.
-3685   ]
-3686 ]
-3687 
-3688 scenario editor-starts-next-page-at-start-of-wrapped-line [
-3689   local-scope
-3690   # screen has 1 line for menu + 3 lines for text
-3691   assume-screen 10/width, 4/height
-3692   # editor contains a long last line
-3693   s:text <- new [a
-3694 b
-3695 cdefgh]
-3696   # editor screen triggers wrap of last line
-3697   e:&:editor <- new-editor s, 0/left, 4/right
-3698   editor-render screen, e
-3699   # some part of last line is not displayed
-3700   screen-should-contain [
-3701     .          .
-3702     .a         .
-3703     .b         .
-3704     .cde↩      .
-3705   ]
-3706   # scroll down
-3707   assume-console [
-3708     press page-down
-3709   ]
-3710   run [
-3711     editor-event-loop screen, console, e
-3712   ]
-3713   # screen shows entire wrapped line
-3714   screen-should-contain [
-3715     .          .
-3716     .cde↩      .
-3717     .fgh       .
-3718     .╌╌╌╌      .
-3719   ]
-3720 ]
-3721 
-3722 scenario editor-starts-next-page-at-start-of-wrapped-line-2 [
-3723   local-scope
-3724   # screen has 1 line for menu + 3 lines for text
-3725   assume-screen 10/width, 4/height
-3726   # editor contains a very long line that occupies last two lines of screen
-3727   # and still has something left over
-3728   s:text <- new [a
-3729 bcdefgh]
-3730   e:&:editor <- new-editor s, 0/left, 4/right
-3731   editor-render screen, e
-3732   # some part of last line is not displayed
-3733   screen-should-contain [
-3734     .          .
-3735     .a         .
-3736     .bcd↩      .
-3737     .efg↩      .
-3738   ]
-3739   # scroll down
-3740   assume-console [
-3741     press page-down
-3742   ]
-3743   run [
-3744     editor-event-loop screen, console, e
-3745   ]
-3746   # screen shows entire wrapped line
-3747   screen-should-contain [
-3748     .          .
-3749     .bcd↩      .
-3750     .efg↩      .
-3751     .h         .
-3752   ]
-3753 ]
-3754 
-3755 # ctrl-b/page-up - render previous page if it exists
-3756 
-3757 scenario editor-can-scroll-up [
-3758   local-scope
-3759   assume-screen 10/width, 4/height
-3760   s:text <- new [a
-3761 b
-3762 c
-3763 d]
-3764   e:&:editor <- new-editor s, 0/left, 10/right
-3765   editor-render screen, e
-3766   screen-should-contain [
-3767     .          .
-3768     .a         .
-3769     .b         .
-3770     .c         .
-3771   ]
-3772   # scroll down
-3773   assume-console [
-3774     press page-down
-3775   ]
-3776   run [
-3777     editor-event-loop screen, console, e
-3778   ]
-3779   # screen shows next page
-3780   screen-should-contain [
-3781     .          .
-3782     .c         .
-3783     .d         .
-3784     .╌╌╌╌╌╌╌╌╌╌.
-3785   ]
-3786   # scroll back up
-3787   assume-console [
-3788     press page-up
-3789   ]
-3790   run [
-3791     editor-event-loop screen, console, e
-3792   ]
-3793   # screen shows original page again
-3794   screen-should-contain [
-3795     .          .
-3796     .a         .
-3797     .b         .
-3798     .c         .
-3799   ]
-3800 ]
-3801 
-3802 after <handle-special-character> [
-3803   {
-3804     page-up?:bool <- equal c, 2/ctrl-b
-3805     break-unless page-up?
-3806     old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
-3807     <begin-move-cursor>
-3808     editor <- page-up editor, screen-height
-3809     undo-coalesce-tag:num <- copy 0/never
-3810     <end-move-cursor>
-3811     top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
-3812     movement?:bool <- not-equal top-of-screen, old-top
-3813     return movement?/go-render
-3814   }
-3815 ]
-3816 
-3817 after <handle-special-key> [
-3818   {
-3819     page-up?:bool <- equal k, 65519/page-up
-3820     break-unless page-up?
-3821     old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
-3822     <begin-move-cursor>
-3823     editor <- page-up editor, screen-height
-3824     undo-coalesce-tag:num <- copy 0/never
-3825     <end-move-cursor>
-3826     top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
-3827     movement?:bool <- not-equal top-of-screen, old-top
-3828     # don't bother re-rendering if nothing changed. todo: test this
-3829     return movement?/go-render
-3830   }
-3831 ]
-3832 
-3833 def page-up editor:&:editor, screen-height:num -> editor:&:editor [
-3834   local-scope
-3835   load-inputs
-3836   max:num <- subtract screen-height, 1/menu-bar, 1/overlapping-line
-3837   count:num <- copy 0
-3838   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
-3839   {
-3840     done?:bool <- greater-or-equal count, max
-3841     break-if done?
-3842     prev:&:duplex-list:char <- before-previous-screen-line top-of-screen, editor
-3843     break-unless prev
-3844     top-of-screen <- copy prev
-3845     *editor <- put *editor, top-of-screen:offset, top-of-screen
-3846     count <- add count, 1
-3847     loop
-3848   }
-3849 ]
-3850 
-3851 scenario editor-can-scroll-up-multiple-pages [
-3852   local-scope
-3853   # screen has 1 line for menu + 3 lines
-3854   assume-screen 10/width, 4/height
-3855   # initialize editor with 8 lines
-3856   s:text <- new [a
-3857 b
-3858 c
-3859 d
-3860 e
-3861 f
-3862 g
-3863 h]
-3864   e:&:editor <- new-editor s, 0/left, 10/right
-3865   editor-render screen, e
-3866   screen-should-contain [
-3867     .          .
-3868     .a         .
-3869     .b         .
-3870     .c         .
-3871   ]
-3872   # scroll down two pages
-3873   assume-console [
-3874     press page-down
-3875     press page-down
-3876   ]
-3877   run [
-3878     editor-event-loop screen, console, e
-3879   ]
-3880   # screen shows third page
-3881   screen-should-contain [
-3882     .          .
-3883     .e         .
-3884     .f         .
-3885     .g         .
-3886   ]
-3887   # scroll up
-3888   assume-console [
-3889     press page-up
-3890   ]
-3891   run [
-3892     editor-event-loop screen, console, e
-3893   ]
-3894   # screen shows second page
-3895   screen-should-contain [
-3896     .          .
-3897     .c         .
-3898     .d         .
-3899     .e         .
-3900   ]
-3901   # scroll up again
-3902   assume-console [
-3903     press page-up
-3904   ]
-3905   run [
-3906     editor-event-loop screen, console, e
-3907   ]
-3908   # screen shows original page again
-3909   screen-should-contain [
-3910     .          .
-3911     .a         .
-3912     .b         .
-3913     .c         .
-3914   ]
-3915 ]
-3916 
-3917 scenario editor-can-scroll-up-wrapped-lines [
-3918   local-scope
-3919   # screen has 1 line for menu + 5 lines for text
-3920   assume-screen 10/width, 6/height
-3921   # editor contains a long line in the first page
-3922   s:text <- new [a
-3923 b
-3924 cdefgh
-3925 i
-3926 j
-3927 k
-3928 l
-3929 m
-3930 n
-3931 o]
-3932   # editor screen triggers wrap of last line
-3933   e:&:editor <- new-editor s, 0/left, 4/right
-3934   editor-render screen, e
-3935   # some part of last line is not displayed
-3936   screen-should-contain [
-3937     .          .
-3938     .a         .
-3939     .b         .
-3940     .cde↩      .
-3941     .fgh       .
-3942     .i         .
-3943   ]
-3944   # scroll down a page and a line
-3945   assume-console [
-3946     press page-down
-3947     left-click 5, 0
-3948     press down-arrow
-3949   ]
-3950   run [
-3951     editor-event-loop screen, console, e
-3952   ]
-3953   # screen shows entire wrapped line
-3954   screen-should-contain [
-3955     .          .
-3956     .j         .
-3957     .k         .
-3958     .l         .
-3959     .m         .
-3960     .n         .
-3961   ]
-3962   # now scroll up one page
-3963   assume-console [
-3964     press page-up
-3965   ]
-3966   run [
-3967     editor-event-loop screen, console, e
-3968   ]
-3969   # screen resets
-3970   screen-should-contain [
-3971     .          .
-3972     .b         .
-3973     .cde↩      .
-3974     .fgh       .
-3975     .i         .
-3976     .j         .
-3977   ]
-3978 ]
-3979 
-3980 scenario editor-can-scroll-up-wrapped-lines-2 [
-3981   local-scope
-3982   # screen has 1 line for menu + 3 lines for text
-3983   assume-screen 10/width, 4/height
-3984   # editor contains a very long line that occupies last two lines of screen
-3985   # and still has something left over
-3986   s:text <- new [a
-3987 bcdefgh]
-3988   e:&:editor <- new-editor s, 0/left, 4/right
-3989   editor-render screen, e
-3990   # some part of last line is not displayed
-3991   screen-should-contain [
-3992     .          .
-3993     .a         .
-3994     .bcd↩      .
-3995     .efg↩      .
-3996   ]
-3997   # scroll down
-3998   assume-console [
-3999     press page-down
-4000   ]
-4001   run [
-4002     editor-event-loop screen, console, e
-4003   ]
-4004   # screen shows entire wrapped line
-4005   screen-should-contain [
-4006     .          .
-4007     .bcd↩      .
-4008     .efg↩      .
-4009     .h         .
-4010   ]
-4011   # scroll back up
-4012   assume-console [
-4013     press page-up
-4014   ]
-4015   run [
-4016     editor-event-loop screen, console, e
-4017   ]
-4018   # screen resets
-4019   screen-should-contain [
-4020     .          .
-4021     .a         .
-4022     .bcd↩      .
-4023     .efg↩      .
-4024   ]
-4025 ]
-4026 
-4027 scenario editor-can-scroll-up-past-nonempty-lines [
-4028   local-scope
-4029   assume-screen 10/width, 4/height
-4030   # text with empty line in second screen
-4031   s:text <- new [axx
-4032 bxx
-4033 cxx
-4034 dxx
-4035 exx
-4036 fxx
-4037 gxx
-4038 hxx
-4039 ]
-4040   e:&:editor <- new-editor s, 0/left, 4/right
-4041   editor-render screen, e
-4042   screen-should-contain [
-4043     .          .
-4044     .axx       .
-4045     .bxx       .
-4046     .cxx       .
-4047   ]
-4048   assume-console [
-4049     press page-down
-4050   ]
-4051   run [
-4052     editor-event-loop screen, console, e
-4053   ]
-4054   screen-should-contain [
-4055     .          .
-4056     .cxx       .
-4057     .dxx       .
-4058     .exx       .
-4059   ]
-4060   assume-console [
-4061     press page-down
-4062   ]
-4063   run [
-4064     editor-event-loop screen, console, e
-4065   ]
-4066   screen-should-contain [
-4067     .          .
-4068     .exx       .
-4069     .fxx       .
-4070     .gxx       .
-4071   ]
-4072   # scroll back up past empty line
-4073   assume-console [
-4074     press page-up
-4075   ]
-4076   run [
-4077     editor-event-loop screen, console, e
-4078   ]
-4079   screen-should-contain [
-4080     .          .
-4081     .cxx       .
-4082     .dxx       .
-4083     .exx       .
-4084   ]
-4085 ]
-4086 
-4087 scenario editor-can-scroll-up-past-empty-lines [
-4088   local-scope
-4089   assume-screen 10/width, 4/height
-4090   # text with empty line in second screen
-4091   s:text <- new [axy
-4092 bxy
-4093 cxy
-4094 
-4095 dxy
-4096 exy
-4097 fxy
-4098 gxy
-4099 ]
-4100   e:&:editor <- new-editor s, 0/left, 4/right
-4101   editor-render screen, e
-4102   screen-should-contain [
-4103     .          .
-4104     .axy       .
-4105     .bxy       .
-4106     .cxy       .
-4107   ]
-4108   assume-console [
-4109     press page-down
-4110   ]
-4111   run [
-4112     editor-event-loop screen, console, e
-4113   ]
-4114   screen-should-contain [
-4115     .          .
-4116     .cxy       .
-4117     .          .
-4118     .dxy       .
-4119   ]
-4120   assume-console [
-4121     press page-down
-4122   ]
-4123   run [
-4124     editor-event-loop screen, console, e
-4125   ]
-4126   screen-should-contain [
-4127     .          .
-4128     .dxy       .
-4129     .exy       .
-4130     .fxy       .
-4131   ]
-4132   # scroll back up past empty line
-4133   assume-console [
-4134     press page-up
-4135   ]
-4136   run [
-4137     editor-event-loop screen, console, e
-4138   ]
-4139   screen-should-contain [
-4140     .          .
-4141     .cxy       .
-4142     .          .
-4143     .dxy       .
-4144   ]
-4145 ]
-4146 
-4147 # ctrl-s - scroll up by one line
-4148 # todo: scenarios
-4149 
-4150 after <handle-special-character> [
-4151   {
-4152     scroll-up?:bool <- equal c, 19/ctrl-s
-4153     break-unless scroll-up?
-4154     <begin-move-cursor>
-4155     go-render?:bool, editor <- line-up editor, screen-height
-4156     undo-coalesce-tag:num <- copy 5/line-up
-4157     <end-move-cursor>
-4158     return go-render?
-4159   }
-4160 ]
-4161 
-4162 def line-up editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
-4163   local-scope
-4164   load-inputs
-4165   left:num <- get *editor, left:offset
-4166   right:num <- get *editor, right:offset
-4167   max:num <- subtract right, left
-4168   old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
-4169   new-top:&:duplex-list:char <- before-start-of-next-line old-top, max
-4170   movement?:bool <- not-equal old-top, new-top
-4171   {
-4172     break-unless movement?
-4173     *editor <- put *editor, top-of-screen:offset, new-top
-4174   }
-4175   return movement?
-4176 ]
-4177 
-4178 # ctrl-x - scroll down by one line
-4179 # todo: scenarios
-4180 
-4181 after <handle-special-character> [
-4182   {
-4183     scroll-down?:bool <- equal c, 24/ctrl-x
-4184     break-unless scroll-down?
-4185     <begin-move-cursor>
-4186     go-render?:bool, editor <- line-down editor, screen-height
-4187     undo-coalesce-tag:num <- copy 6/line-down
-4188     <end-move-cursor>
-4189     return go-render?
-4190   }
-4191 ]
-4192 
-4193 def line-down editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
-4194   local-scope
-4195   load-inputs
-4196   old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
-4197   new-top:&:duplex-list:char <- before-previous-screen-line old-top, editor
-4198   movement?:bool <- not-equal old-top, new-top
-4199   {
-4200     break-unless movement?
-4201     *editor <- put *editor, top-of-screen:offset, new-top
-4202   }
-4203   return movement?
-4204 ]
-4205 
-4206 # ctrl-t - move current line to top of screen
-4207 # todo: scenarios
-4208 
-4209 after <handle-special-character> [
-4210   {
-4211     scroll-down?:bool <- equal c, 20/ctrl-t
-4212     break-unless scroll-down?
-4213     <begin-move-cursor>
-4214     old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
-4215     cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-4216     cursor <- next cursor
-4217     new-top:&:duplex-list:char <- before-previous-screen-line cursor, editor
-4218     *editor <- put *editor, top-of-screen:offset, new-top
-4219     *editor <- put *editor, cursor-row:offset, 1
-4220     go-render?:bool <- not-equal new-top, old-top
-4221     undo-coalesce-tag:num <- copy 0/never
-4222     <end-move-cursor>
-4223     return go-render?
-4224   }
-4225 ]
-4226 
-4227 # ctrl-/ - comment/uncomment current line
-4228 
-4229 after <handle-special-character> [
-4230   {
-4231     comment-toggle?:bool <- equal c, 31/ctrl-slash
-4232     break-unless comment-toggle?
-4233     cursor-column:num <- get *editor, cursor-column:offset
-4234     data:&:duplex-list:char <- get *editor, data:offset
-4235     <begin-insert-character>
-4236     before-line-start:&:duplex-list:char <- before-start-of-screen-line editor
-4237     line-start:&:duplex-list:char <- next before-line-start
-4238     commented-out?:bool <- match line-start, [#? ]  # comment prefix
-4239     {
-4240       break-unless commented-out?
-4241       # uncomment
-4242       data <- remove line-start, 3/length-comment-prefix, data
-4243       cursor-column <- subtract cursor-column, 3/length-comment-prefix
-4244       *editor <- put *editor, cursor-column:offset, cursor-column
-4245       go-render? <- render-line-from-start screen, editor, 3/size-of-comment-leader
-4246     }
-4247     {
-4248       break-if commented-out?
-4249       # comment
-4250       insert before-line-start, [#? ]
-4251       cursor-column <- add cursor-column, 3/length-comment-prefix
-4252       *editor <- put *editor, cursor-column:offset, cursor-column
-4253       go-render? <- render-line-from-start screen, editor, 0
-4254     }
-4255     <end-insert-character>
-4256     return
-4257   }
-4258 ]
-4259 
-4260 # Render just from the start of the current line, and only if it wasn't
-4261 # wrapping before (include margin) and isn't wrapping now. Otherwise just tell
-4262 # the caller to go-render? the entire screen.
-4263 def render-line-from-start screen:&:screen, editor:&:editor, right-margin:num -> go-render?:bool, screen:&:screen [
-4264   local-scope
-4265   load-inputs
-4266   before-line-start:&:duplex-list:char <- before-start-of-screen-line editor
-4267   line-start:&:duplex-list:char <- next before-line-start
-4268   color:num <- copy 7/white
-4269   left:num <- get *editor, left:offset
-4270   cursor-row:num <- get *editor, cursor-row:offset
-4271   screen <- move-cursor screen, cursor-row, left
-4272   right:num <- get *editor, right:offset
-4273   end:num <- subtract right, right-margin
-4274   i:num <- copy 0
-4275   curr:&:duplex-list:char <- copy line-start
-4276   {
-4277     render-all?:bool <- greater-or-equal i, end
-4278     return-if render-all?, true/go-render
-4279     break-unless curr
-4280     c:char <- get *curr, value:offset
-4281     newline?:bool <- equal c, 10/newline
-4282     break-if newline?
-4283     color <- get-color color, c
-4284     print screen, c, color
-4285     curr <- next curr
-4286     i <- add i, 1
-4287     loop
-4288   }
-4289   clear-line-until screen, right
-4290   return false/dont-render
-4291 ]
-4292 
-4293 def before-start-of-screen-line editor:&:editor -> result:&:duplex-list:char [
-4294   local-scope
-4295   load-inputs
-4296   cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-4297   {
-4298     next:&:duplex-list:char <- next cursor
-4299     break-unless next
-4300     cursor <- copy next
-4301   }
-4302   result <- before-previous-screen-line cursor, editor
-4303 ]
-4304 
-4305 scenario editor-comments-empty-line [
-4306   local-scope
-4307   assume-screen 10/width, 5/height
-4308   e:&:editor <- new-editor [], 0/left, 5/right
-4309   editor-render screen, e
-4310   $clear-trace
-4311   assume-console [
-4312     press ctrl-slash
-4313   ]
-4314   run [
-4315     editor-event-loop screen, console, e
-4316     4:num/raw <- get *e, cursor-row:offset
-4317     5:num/raw <- get *e, cursor-column:offset
-4318   ]
-4319   screen-should-contain [
-4320     .          .
-4321     .#?        .
-4322     .╌╌╌╌╌     .
-4323     .          .
-4324   ]
-4325   memory-should-contain [
-4326     4 <- 1
-4327     5 <- 3
-4328   ]
-4329   check-trace-count-for-label 5, [print-character]
-4330 ]
-4331 
-4332 scenario editor-comments-at-start-of-contents [
-4333   local-scope
-4334   assume-screen 10/width, 5/height
-4335   e:&:editor <- new-editor [ab], 0/left, 10/right
-4336   editor-render screen, e
-4337   $clear-trace
-4338   assume-console [
-4339     press ctrl-slash
-4340   ]
-4341   run [
-4342     editor-event-loop screen, console, e
-4343     4:num/raw <- get *e, cursor-row:offset
-4344     5:num/raw <- get *e, cursor-column:offset
-4345   ]
-4346   screen-should-contain [
-4347     .          .
-4348     .#? ab     .
-4349     .╌╌╌╌╌╌╌╌╌╌.
-4350     .          .
-4351   ]
-4352   memory-should-contain [
-4353     4 <- 1
-4354     5 <- 3
-4355   ]
-4356   check-trace-count-for-label 10, [print-character]
-4357 ]
-4358 
-4359 scenario editor-comments-at-end-of-contents [
-4360   local-scope
-4361   assume-screen 10/width, 5/height
-4362   e:&:editor <- new-editor [ab], 0/left, 10/right
-4363   editor-render screen, e
-4364   $clear-trace
-4365   assume-console [
-4366     left-click 1, 7
-4367     press ctrl-slash
-4368   ]
-4369   run [
-4370     editor-event-loop screen, console, e
-4371     4:num/raw <- get *e, cursor-row:offset
-4372     5:num/raw <- get *e, cursor-column:offset
-4373   ]
-4374   screen-should-contain [
-4375     .          .
-4376     .#? ab     .
-4377     .╌╌╌╌╌╌╌╌╌╌.
-4378     .          .
-4379   ]
-4380   memory-should-contain [
-4381     4 <- 1
-4382     5 <- 5
-4383   ]
-4384   check-trace-count-for-label 10, [print-character]
-4385   # toggle to uncomment
-4386   $clear-trace
-4387   assume-console [
-4388     press ctrl-slash
-4389   ]
-4390   run [
-4391     editor-event-loop screen, console, e
-4392     4:num/raw <- get *e, cursor-row:offset
-4393     5:num/raw <- get *e, cursor-column:offset
-4394   ]
-4395   screen-should-contain [
-4396     .          .
-4397     .ab        .
-4398     .╌╌╌╌╌╌╌╌╌╌.
-4399     .          .
-4400   ]
-4401   check-trace-count-for-label 10, [print-character]
-4402 ]
-4403 
-4404 scenario editor-comments-almost-wrapping-line [
-4405   local-scope
-4406   assume-screen 10/width, 5/height
-4407   # editor starts out with a non-wrapping line
-4408   e:&:editor <- new-editor [abcd], 0/left, 5/right
-4409   editor-render screen, e
-4410   screen-should-contain [
-4411     .          .
-4412     .abcd      .
-4413     .╌╌╌╌╌     .
-4414     .          .
-4415   ]
-4416   $clear-trace
-4417   # on commenting the line is now wrapped
-4418   assume-console [
-4419     left-click 1, 7
-4420     press ctrl-slash
-4421   ]
-4422   run [
-4423     editor-event-loop screen, console, e
-4424   ]
-4425   screen-should-contain [
-4426     .          .
-4427     .#? a↩     .
-4428     .bcd       .
-4429     .╌╌╌╌╌     .
-4430     .          .
-4431   ]
-4432 ]
-4433 
-4434 scenario editor-uncomments-just-wrapping-line [
-4435   local-scope
-4436   assume-screen 10/width, 5/height
-4437   # editor starts out with a comment that wraps the line
-4438   e:&:editor <- new-editor [#? ab], 0/left, 5/right
-4439   editor-render screen, e
-4440   screen-should-contain [
-4441     .          .
-4442     .#? a↩     .
-4443     .b         .
-4444     .╌╌╌╌╌     .
-4445     .          .
-4446   ]
-4447   $clear-trace
-4448   # on uncommenting the line is no longer wrapped
-4449   assume-console [
-4450     left-click 1, 7
-4451     press ctrl-slash
-4452   ]
-4453   run [
-4454     editor-event-loop screen, console, e
-4455   ]
-4456   screen-should-contain [
-4457     .          .
-4458     .ab        .
-4459     .╌╌╌╌╌     .
-4460     .          .
-4461   ]
-4462 ]
-
- - - diff --git a/html/edit/004-programming-environment.mu.html b/html/edit/004-programming-environment.mu.html deleted file mode 100644 index 4d0ff1c3..00000000 --- a/html/edit/004-programming-environment.mu.html +++ /dev/null @@ -1,614 +0,0 @@ - - - - -Mu - edit/004-programming-environment.mu - - - - - - - - - - -https://github.com/akkartik/mu/blob/master/edit/004-programming-environment.mu -
-  1 ## putting the environment together out of editors
-  2 #
-  3 # Consists of one editor on the left for recipes and one on the right for the
-  4 # sandbox.
-  5 
-  6 def! main [
-  7   local-scope
-  8   open-console
-  9   clear-screen null/screen  # non-scrolling app
- 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  # false => cursor in recipes; true => cursor in current-sandbox
- 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   # recipe editor on the left
- 27   initial-recipe-contents:text <- slurp resources, [lesson/recipes.mu]  # ignore errors
- 28   divider:num, _ <- divide-with-remainder width, 2
- 29   recipes:&:editor <- new-editor initial-recipe-contents, 0/left, divider/right
- 30   # sandbox editor on the right
- 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   # if we fall behind we'll stop updating the screen, but then we have to
- 46   # render the entire screen when we catch up.
- 47   # todo: test this
- 48   render-recipes-on-no-more-events?:bool <- copy false
- 49   render-sandboxes-on-no-more-events?:bool <- copy false
- 50   {
- 51     # looping over each (keyboard or touch) event as it occurs
- 52     +next-event
- 53     e:event, found?:bool, quit?:bool, console <- read-event console
- 54     loop-unless found?
- 55     break-if quit?  # only in tests
- 56     trace 10, [app], [next-event]
- 57     <handle-event>
- 58     # check for global events that will trigger regardless of which editor has focus
- 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     # 'touch' event - send to both sides, see what picks it up
- 70     {
- 71       t:touch-event, is-touch?:bool <- maybe-convert e:event, touch:variant
- 72       break-unless is-touch?
- 73       # ignore all but 'left-click' events for now
- 74       # todo: test this
- 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       # later exceptions for non-editor touches will go here
- 81       <global-touch>
- 82       # send to both editors
- 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     # 'resize' event - redraw editor
- 90     # todo: test this after supporting resize in assume-console
- 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     # if it's not global and not a touch event, send to appropriate editor
- 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  # update screen dimensions
-135   width:num <- screen-width screen
-136   divider:num, _ <- divide-with-remainder width, 2
-137   # update recipe editor
-138   recipes:&:editor <- get *env, recipes:offset
-139   right:num <- subtract divider, 1
-140   *recipes <- put *recipes, right:offset, right
-141   # reset cursor (later we'll try to preserve its position)
-142   *recipes <- put *recipes, cursor-row:offset, 1
-143   *recipes <- put *recipes, cursor-column:offset, 0
-144   # update sandbox editor
-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   # reset cursor (later we'll try to preserve its position)
-151   *current-sandbox <- put *current-sandbox, cursor-row:offset, 1
-152   *current-sandbox <- put *current-sandbox, cursor-column:offset, left
-153 ]
-154 
-155 # Variant of 'render' that updates cursor-row and cursor-column based on
-156 # before-cursor (rather than the other way around). If before-cursor moves
-157 # off-screen, it resets cursor-row and cursor-column.
-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  # just in case curr becomes null and we can't compute prev
-167   curr <- next curr
-168   color:num <- copy 7/white
-169   row:num <- copy 1/top
-170   column:num <- copy left
-171   # save before-cursor
-172   old-before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-173   # initialze cursor-row/cursor-column/before-cursor to the top of the screen
-174   # by default
-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     # if we find old-before-cursor still on the new resized screen, update
-186     # editor.cursor-row and editor.cursor-column based on
-187     # old-before-cursor
-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       # newline? move to left rather than 0
-199       newline?:bool <- equal c, 10/newline
-200       break-unless newline?
-201       # clear rest of line in this window
-202       clear-line-until screen, right
-203       # skip to next line
-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       # at right? wrap. even if there's only one more letter left; we need
-213       # room for clicking on the cursor after it.
-214       at-right?:bool <- equal column, right
-215       break-unless at-right?
-216       # print wrap icon
-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       # don't increment curr
-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   # save first character off-screen
-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  # trace too long
-240   assume-screen 30/width, 5/height
-241   # initialize both halves of screen
-242   assume-resources [
-243     [lesson/recipes.mu] <- [
-244       |abc|
-245     ]
-246   ]
-247   env:&:environment <- new-programming-environment resources, screen, [def]  # contents of sandbox editor
-248   # focus on both sides
-249   assume-console [
-250     left-click 1, 1
-251     left-click 1, 17
-252   ]
-253   # check cursor column in each
-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  # trace too long
-270   assume-screen 30/width, 5/height
-271   # initialize both halves of screen
-272   assume-resources [
-273     [lesson/recipes.mu] <- [
-274       |abc|
-275     ]
-276   ]
-277   env:&:environment <- new-programming-environment resources, screen, [def]  # contents of sandbox
-278   render-all screen, env, render
-279   # type one letter in each of them
-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  # cursor column of recipe editor
-302     7 <- 18  # cursor column of sandbox editor
-303   ]
-304   # show the cursor at the right window
-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  # trace too long
-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   # initialize programming environment and highlight cursor
-330   assume-console []
-331   run [
-332     event-loop screen, console, env, resources
-333     cursor:char <- copy 9251/␣
-334     print screen, cursor
-335   ]
-336   # is cursor at the right place?
-337   screen-should-contain [
-338     .           run (F4)           .
-339     .␣bc            ╎def           .
-340     .               ╎──────────────.
-341     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎              .
-342     .               ╎              .
-343   ]
-344   # now try typing a letter
-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   # cursor should still be right
-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  # trace too long
-366   assume-screen 30/width, 5/height
-367   assume-resources [
-368   ]
-369   # initialize sandbox side with two lines
-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   # position cursor at start of second line and hit backspace
-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   # cursor moves to end of old line
-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   # top menu
-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   # dotted line down the middle
-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>  # no early returns permitted
-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   # render recipes
-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   # draw dotted line after recipes
-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 # replaced in a later layer
-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   # draw solid line after code (you'll see why in later layers)
-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 # ctrl-n - switch focus
-486 # todo: test this
-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 ## helpers
-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     # default color to white
-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  # trace too long
-529   assume-screen 50/width, 15/height
-530   # recipes.mu is empty
-531   assume-resources [
-532   ]
-533   # sandbox editor contains an instruction without storing outputs
-534   env:&:environment <- new-programming-environment resources, screen, []
-535   # run the code in the editors
-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 ]
-
- - - diff --git a/html/edit/005-sandbox.mu.html b/html/edit/005-sandbox.mu.html deleted file mode 100644 index f082c7b1..00000000 --- a/html/edit/005-sandbox.mu.html +++ /dev/null @@ -1,1258 +0,0 @@ - - - - -Mu - edit/005-sandbox.mu - - - - - - - - - - -https://github.com/akkartik/mu/blob/master/edit/005-sandbox.mu -
-   1 ## running code from the editor and creating sandboxes
-   2 #
-   3 # Running code in the sandbox editor prepends its contents to a list of
-   4 # (non-editable) sandboxes below the editor, showing the result and maybe a
-   5 # few other things (later layers).
-   6 #
-   7 # This layer draws the menubar buttons in non-editable sandboxes but they
-   8 # don't do anything yet. Later layers implement each button.
-   9 
-  10 def! main [
-  11   local-scope
-  12   open-console
-  13   clear-screen null/screen  # non-scrolling app
-  14   env:&:environment <- new-programming-environment null/filesystem, null/screen
-  15   env <- restore-sandboxes env, null/filesystem
-  16   render-all null/screen, env, render
-  17   event-loop null/screen, null/console, env, null/filesystem
-  18 ]
-  19 
-  20 container environment [
-  21   sandbox:&:sandbox  # list of sandboxes, from top to bottom. TODO: switch to &:list:sandbox
-  22   render-from:num
-  23   number-of-sandboxes:num
-  24 ]
-  25 
-  26 after <programming-environment-initialization> [
-  27   *result <- put *result, render-from:offset, -1
-  28 ]
-  29 
-  30 container sandbox [
-  31   data:text
-  32   response:text
-  33   # coordinates to track clicks
-  34   # constraint: will be 0 for sandboxes at positions before env.render-from
-  35   starting-row-on-screen:num
-  36   code-ending-row-on-screen:num  # past end of code
-  37   screen:&:screen  # prints in the sandbox go here
-  38   next-sandbox:&:sandbox
-  39 ]
-  40 
-  41 scenario run-and-show-results [
-  42   local-scope
-  43   trace-until 100/app  # trace too long
-  44   assume-screen 100/width, 15/height
-  45   # recipe editor is empty
-  46   assume-resources [
-  47   ]
-  48   # sandbox editor contains an instruction without storing outputs
-  49   env:&:environment <- new-programming-environment resources, screen, [divide-with-remainder 11, 3]
-  50   render-all screen, env, render
-  51   # run the code in the editors
-  52   assume-console [
-  53     press F4
-  54   ]
-  55   run [
-  56     event-loop screen, console, env, resources
-  57   ]
-  58   # check that screen prints the results
-  59   screen-should-contain [
-  60     .                                                                                 run (F4)           .
-  61     .                                                  ╎                                                 .
-  62     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-  63     .                                                  ╎0   edit       copy       to recipe    delete    .
-  64     .                                                  ╎divide-with-remainder 11, 3                      .
-  65     .                                                  ╎3                                                .
-  66     .                                                  ╎2                                                .
-  67     .                                                  ╎─────────────────────────────────────────────────.
-  68     .                                                  ╎                                                 .
-  69   ]
-  70   screen-should-contain-in-color 7/white, [
-  71     .                                                                                                    .
-  72     .                                                                                                    .
-  73     .                                                                                                    .
-  74     .                                                                                                    .
-  75     .                                                   divide-with-remainder 11, 3                      .
-  76     .                                                                                                    .
-  77     .                                                                                                    .
-  78     .                                                                                                    .
-  79     .                                                                                                    .
-  80   ]
-  81   screen-should-contain-in-color 245/grey, [
-  82     .                                                                                                    .
-  83     .                                                  ╎                                                 .
-  84     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-  85     .                                                  ╎                                                 .
-  86     .                                                  ╎                                                 .
-  87     .                                                  ╎3                                                .
-  88     .                                                  ╎2                                                .
-  89     .                                                  ╎─────────────────────────────────────────────────.
-  90     .                                                  ╎                                                 .
-  91   ]
-  92   # sandbox menu in reverse video
-  93   screen-should-contain-in-color 232/black, [
-  94     .                                                                                                    .
-  95     .                                                                                                    .
-  96     .                                                                                                    .
-  97     .                                                   0   edit       copy       to recipe    delete    .
-  98   ]
-  99   # run another command
- 100   assume-console [
- 101     left-click 1, 80
- 102     type [add 2, 2]
- 103     press F4
- 104   ]
- 105   run [
- 106     event-loop screen, console, env, resources
- 107   ]
- 108   # check that screen prints both sandboxes
- 109   screen-should-contain [
- 110     .                                                                                 run (F4)           .
- 111     .                                                  ╎                                                 .
- 112     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 113     .                                                  ╎0   edit       copy       to recipe    delete    .
- 114     .                                                  ╎add 2, 2                                         .
- 115     .                                                  ╎4                                                .
- 116     .                                                  ╎─────────────────────────────────────────────────.
- 117     .                                                  ╎1   edit       copy       to recipe    delete    .
- 118     .                                                  ╎divide-with-remainder 11, 3                      .
- 119     .                                                  ╎3                                                .
- 120     .                                                  ╎2                                                .
- 121     .                                                  ╎─────────────────────────────────────────────────.
- 122     .                                                  ╎                                                 .
- 123   ]
- 124 ]
- 125 
- 126 after <global-keypress> [
- 127   # F4? load all code and run all sandboxes.
- 128   {
- 129     do-run?:bool <- equal k, 65532/F4
- 130     break-unless do-run?
- 131     screen <- update-status screen, [running...       ], 245/grey
- 132     <begin-run-sandboxes-on-F4>
- 133     error?:bool <- run-sandboxes env, resources, screen
- 134     # we could just render-all, but we do some work to minimize the number of prints to screen
- 135     <end-run-sandboxes-on-F4>
- 136     screen <- render-sandbox-side screen, env, render
- 137     {
- 138       break-if error?
- 139       screen <- update-status screen, [                 ], 245/grey
- 140     }
- 141     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
- 142     loop +next-event
- 143   }
- 144 ]
- 145 
- 146 def run-sandboxes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, resources:&:resources, screen:&:screen [
- 147   local-scope
- 148   load-inputs
- 149   errors-found?:bool <- update-recipes env, resources, screen
- 150   jump-if errors-found?, +return
- 151   # check contents of right editor (sandbox)
- 152   <begin-run-sandboxes>
- 153   current-sandbox:&:editor <- get *env, current-sandbox:offset
- 154   {
- 155     sandbox-contents:text <- editor-contents current-sandbox
- 156     break-unless sandbox-contents
- 157     # if contents exist, first save them
- 158     # run them and turn them into a new sandbox
- 159     new-sandbox:&:sandbox <- new sandbox:type
- 160     *new-sandbox <- put *new-sandbox, data:offset, sandbox-contents
- 161     # push to head of sandbox list
- 162     dest:&:sandbox <- get *env, sandbox:offset
- 163     *new-sandbox <- put *new-sandbox, next-sandbox:offset, dest
- 164     *env <- put *env, sandbox:offset, new-sandbox
- 165     # update sandbox count
- 166     sandbox-count:num <- get *env, number-of-sandboxes:offset
- 167     sandbox-count <- add sandbox-count, 1
- 168     *env <- put *env, number-of-sandboxes:offset, sandbox-count
- 169     # save all sandboxes
- 170     # needs to be before running them, in case we die when running
- 171     save-sandboxes env, resources
- 172     # clear sandbox editor
- 173     init:&:duplex-list:char <- push 167/§, null
- 174     *current-sandbox <- put *current-sandbox, data:offset, init
- 175     *current-sandbox <- put *current-sandbox, top-of-screen:offset, init
- 176   }
- 177   # run all sandboxes
- 178   curr:&:sandbox <- get *env, sandbox:offset
- 179   idx:num <- copy 0
- 180   {
- 181     break-unless curr
- 182     curr <- update-sandbox curr, env, idx
- 183     curr <- get *curr, next-sandbox:offset
- 184     idx <- add idx, 1
- 185     loop
- 186   }
- 187   <end-run-sandboxes>
- 188   +return
- 189   {
- 190     break-if resources  # ignore this in tests
- 191     $system [./snapshot_lesson]
- 192   }
- 193 ]
- 194 
- 195 # load code from disk
- 196 # replaced in a later layer (whereupon errors-found? will actually be set)
- 197 def update-recipes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, resources:&:resources, screen:&:screen [
- 198   local-scope
- 199   load-inputs
- 200   recipes:&:editor <- get *env, recipes:offset
- 201   in:text <- editor-contents recipes
- 202   resources <- dump resources, [lesson/recipes.mu], in
- 203   reload in
- 204   errors-found? <- copy false
- 205 ]
- 206 
- 207 # replaced in a later layer
- 208 def update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [
- 209   local-scope
- 210   load-inputs
- 211   data:text <- get *sandbox, data:offset
- 212   response:text, _, fake-screen:&:screen <- run-sandboxed data
- 213   *sandbox <- put *sandbox, response:offset, response
- 214   *sandbox <- put *sandbox, screen:offset, fake-screen
- 215 ]
- 216 
- 217 def update-status screen:&:screen, msg:text, color:num -> screen:&:screen [
- 218   local-scope
- 219   load-inputs
- 220   screen <- move-cursor screen, 0, 2
- 221   screen <- print screen, msg, color, 238/grey/background
- 222 ]
- 223 
- 224 def save-sandboxes env:&:environment, resources:&:resources -> resources:&:resources [
- 225   local-scope
- 226   load-inputs
- 227   trace 11, [app], [save sandboxes]
- 228   current-sandbox:&:editor <- get *env, current-sandbox:offset
- 229   # first clear previous versions, in case we deleted some sandbox
- 230   $system [rm lesson/[0-9]* >/dev/null 2>/dev/null]  # some shells can't handle '>&'
- 231   curr:&:sandbox <- get *env, sandbox:offset
- 232   idx:num <- copy 0
- 233   {
- 234     break-unless curr
- 235     resources <- save-sandbox resources, curr, idx
- 236     idx <- add idx, 1
- 237     curr <- get *curr, next-sandbox:offset
- 238     loop
- 239   }
- 240 ]
- 241 
- 242 def save-sandbox resources:&:resources, sandbox:&:sandbox, sandbox-index:num -> resources:&:resources [
- 243   local-scope
- 244   load-inputs
- 245   data:text <- get *sandbox, data:offset
- 246   filename:text <- append [lesson/], sandbox-index
- 247   resources <- dump resources, filename, data
- 248   <end-save-sandbox>
- 249 ]
- 250 
- 251 def! render-sandbox-side screen:&:screen, env:&:environment, render-editor:render-recipe -> screen:&:screen, env:&:environment [
- 252   local-scope
- 253   load-inputs
- 254   trace 11, [app], [render sandbox side]
- 255   old-top-idx:num <- save-top-idx screen
- 256   current-sandbox:&:editor <- get *env, current-sandbox:offset
- 257   row:num, column:num <- copy 1, 0
- 258   left:num <- get *current-sandbox, left:offset
- 259   right:num <- get *current-sandbox, right:offset
- 260   # render sandbox editor
- 261   render-from:num <- get *env, render-from:offset
- 262   {
- 263     render-current-sandbox?:bool <- equal render-from, -1
- 264     break-unless render-current-sandbox?
- 265     row, column, screen, current-sandbox <- call render-editor, screen, current-sandbox
- 266   }
- 267   # render sandboxes
- 268   draw-horizontal screen, row, left, right
- 269   sandbox:&:sandbox <- get *env, sandbox:offset
- 270   row, screen <- render-sandboxes screen, sandbox, left, right, row, render-from
- 271   clear-rest-of-screen screen, row, left, right
- 272   #
- 273   assert-no-scroll screen, old-top-idx
- 274 ]
- 275 
- 276 def render-sandboxes screen:&:screen, sandbox:&:sandbox, left:num, right:num, row:num, render-from:num, idx:num -> row:num, screen:&:screen, sandbox:&:sandbox [
- 277   local-scope
- 278   load-inputs
- 279   return-unless sandbox
- 280   screen-height:num <- screen-height screen
- 281   hidden?:bool <- lesser-than idx, render-from
- 282   {
- 283     break-if hidden?
- 284     # render sandbox menu
- 285     row <- add row, 1
- 286     at-bottom?:bool <- greater-or-equal row, screen-height
- 287     return-if at-bottom?
- 288     screen <- move-cursor screen, row, left
- 289     screen <- render-sandbox-menu screen, idx, left, right
- 290     # save menu row so we can detect clicks to it later
- 291     *sandbox <- put *sandbox, starting-row-on-screen:offset, row
- 292     # render sandbox contents
- 293     row <- add row, 1
- 294     screen <- move-cursor screen, row, left
- 295     sandbox-data:text <- get *sandbox, data:offset
- 296     row, screen <- render-code screen, sandbox-data, left, right, row
- 297     *sandbox <- put *sandbox, code-ending-row-on-screen:offset, row
- 298     # render sandbox warnings, screen or response, in that order
- 299     sandbox-response:text <- get *sandbox, response:offset
- 300     <render-sandbox-results>
- 301     {
- 302       sandbox-screen:&:screen <- get *sandbox, screen:offset
- 303       empty-screen?:bool <- fake-screen-is-empty? sandbox-screen
- 304       break-if empty-screen?
- 305       row, screen <- render-screen screen, sandbox-screen, left, right, row
- 306     }
- 307     {
- 308       break-unless empty-screen?
- 309       <render-sandbox-response>
- 310       row, screen <- render-text screen, sandbox-response, left, right, 245/grey, row
- 311     }
- 312     +render-sandbox-end
- 313     at-bottom?:bool <- greater-or-equal row, screen-height
- 314     return-if at-bottom?
- 315     # draw solid line after sandbox
- 316     draw-horizontal screen, row, left, right
- 317   }
- 318   # if hidden, reset row attributes
- 319   {
- 320     break-unless hidden?
- 321     *sandbox <- put *sandbox, starting-row-on-screen:offset, 0
- 322     *sandbox <- put *sandbox, code-ending-row-on-screen:offset, 0
- 323     <end-render-sandbox-reset-hidden>
- 324   }
- 325   # draw next sandbox
- 326   next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset
- 327   next-idx:num <- add idx, 1
- 328   row, screen <- render-sandboxes screen, next-sandbox, left, right, row, render-from, next-idx
- 329 ]
- 330 
- 331 def render-sandbox-menu screen:&:screen, sandbox-index:num, left:num, right:num -> screen:&:screen [
- 332   local-scope
- 333   load-inputs
- 334   move-cursor-to-column screen, left
- 335   edit-button-left:num, edit-button-right:num, copy-button-left:num, copy-button-right:num, recipe-button-left:num, recipe-button-right:num, delete-button-left:num <- sandbox-menu-columns left, right
- 336   print screen, sandbox-index, 232/dark-grey, 245/grey
- 337   start-buttons:num <- subtract edit-button-left, 1
- 338   clear-line-until screen, start-buttons, 245/grey
- 339   print screen, [edit], 232/black, 25/background-blue
- 340   clear-line-until screen, edit-button-right, 25/background-blue
- 341   print screen, [copy], 232/black, 58/background-green
- 342   clear-line-until screen, copy-button-right, 58/background-green
- 343   print screen, [to recipe], 232/black, 94/background-orange
- 344   clear-line-until screen, recipe-button-right, 94/background-orange
- 345   print screen, [delete], 232/black, 52/background-red
- 346   clear-line-until screen, right, 52/background-red
- 347 ]
- 348 
- 349 scenario skip-rendering-sandbox-menu-past-bottom-row [
- 350   trace-until 100/app  # trace too long
- 351   assume-screen 100/width, 6/height
- 352   # recipe editor is empty
- 353   assume-resources [
- 354     [lesson/0] <- [|add 2, 2|]
- 355     [lesson/1] <- [|add 1, 1|]
- 356   ]
- 357   # create two sandboxes such that the top one just barely fills the screen
- 358   env:&:environment <- new-programming-environment resources, screen, []
- 359   env <- restore-sandboxes env, resources
- 360   run [
- 361     render-all screen, env, render
- 362   ]
- 363   screen-should-contain [
- 364     .                                                                                 run (F4)           .
- 365     .                                                  ╎                                                 .
- 366     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 367     .                                                  ╎0   edit       copy       to recipe    delete    .
- 368     .                                                  ╎add 2, 2                                         .
- 369     .                                                  ╎─────────────────────────────────────────────────.
- 370   ]
- 371 ]
- 372 
- 373 # divide up the menu bar for a sandbox into 3 segments, for edit/copy/delete buttons
- 374 # delete-button-right == right
- 375 # all left/right pairs are inclusive
- 376 def sandbox-menu-columns left:num, right:num -> edit-button-left:num, edit-button-right:num, copy-button-left:num, copy-button-right:num, recipe-button-left:num, recipe-button-right:num, delete-button-left:num [
- 377   local-scope
- 378   load-inputs
- 379   start-buttons:num <- add left, 4/space-for-sandbox-index
- 380   buttons-space:num <- subtract right, start-buttons
- 381   button-width:num <- divide-with-remainder buttons-space, 4  # integer division
- 382   buttons-wide-enough?:bool <- greater-or-equal button-width, 10
- 383   assert buttons-wide-enough?, [sandbox must be at least 40 or so characters wide]
- 384   edit-button-left:num <- copy start-buttons
- 385   copy-button-left:num <- add start-buttons, button-width
- 386   edit-button-right:num <- subtract copy-button-left, 1
- 387   recipe-button-left:num <- add copy-button-left, button-width
- 388   copy-button-right:num <- subtract recipe-button-left, 1
- 389   delete-button-left:num <- subtract right, button-width, -2  # because 'to recipe' is wider than 'delete'
- 390   recipe-button-right:num <- subtract delete-button-left, 1
- 391 ]
- 392 
- 393 # print a text 's' to 'editor' in 'color' starting at 'row'
- 394 # clear rest of last line, move cursor to next line
- 395 # like 'render-code' but without syntax-based colorization
- 396 def render-text screen:&:screen, s:text, left:num, right:num, color:num, row:num -> row:num, screen:&:screen [
- 397   local-scope
- 398   load-inputs
- 399   return-unless s
- 400   column:num <- copy left
- 401   screen <- move-cursor screen, row, column
- 402   screen-height:num <- screen-height screen
- 403   i:num <- copy 0
- 404   len:num <- length *s
- 405   {
- 406     +next-character
- 407     done?:bool <- greater-or-equal i, len
- 408     break-if done?
- 409     done? <- greater-or-equal row, screen-height
- 410     break-if done?
- 411     c:char <- index *s, i
- 412     {
- 413       # newline? move to left rather than 0
- 414       newline?:bool <- equal c, 10/newline
- 415       break-unless newline?
- 416       # clear rest of line in this window
- 417       {
- 418         done?:bool <- greater-than column, right
- 419         break-if done?
- 420         space:char <- copy 32/space
- 421         print screen, space
- 422         column <- add column, 1
- 423         loop
- 424       }
- 425       row <- add row, 1
- 426       column <- copy left
- 427       screen <- move-cursor screen, row, column
- 428       i <- add i, 1
- 429       loop +next-character
- 430     }
- 431     {
- 432       # at right? wrap.
- 433       at-right?:bool <- equal column, right
- 434       break-unless at-right?
- 435       # print wrap icon
- 436       wrap-icon:char <- copy 8617/loop-back-to-left
- 437       print screen, wrap-icon, 245/grey
- 438       column <- copy left
- 439       row <- add row, 1
- 440       screen <- move-cursor screen, row, column
- 441       # don't increment i
- 442       loop +next-character
- 443     }
- 444     i <- add i, 1
- 445     print screen, c, color
- 446     column <- add column, 1
- 447     loop
- 448   }
- 449   was-at-left?:bool <- equal column, left
- 450   clear-line-until screen, right
- 451   {
- 452     break-if was-at-left?
- 453     row <- add row, 1
- 454   }
- 455   move-cursor screen, row, left
- 456 ]
- 457 
- 458 scenario render-text-wraps-barely-long-lines [
- 459   local-scope
- 460   assume-screen 5/width, 5/height
- 461   run [
- 462     render-text screen, [abcde], 0/left, 4/right, 7/white, 1/row
- 463   ]
- 464   screen-should-contain [
- 465     .     .
- 466     .abcd↩.
- 467     .e    .
- 468     .     .
- 469   ]
- 470 ]
- 471 
- 472 # assumes programming environment has no sandboxes; restores them from previous session
- 473 def restore-sandboxes env:&:environment, resources:&:resources -> env:&:environment [
- 474   local-scope
- 475   load-inputs
- 476   # read all scenarios, pushing them to end of a list of scenarios
- 477   idx:num <- copy 0
- 478   curr:&:sandbox <- copy null
- 479   prev:&:sandbox <- copy null
- 480   {
- 481     filename:text <- append [lesson/], idx
- 482     contents:text <- slurp resources, filename
- 483     break-unless contents  # stop at first error; assuming file didn't exist
- 484                            # todo: handle empty sandbox
- 485     # create new sandbox for file
- 486     curr <- new sandbox:type
- 487     *curr <- put *curr, data:offset, contents
- 488     <end-restore-sandbox>
- 489     {
- 490       break-if idx
- 491       *env <- put *env, sandbox:offset, curr
- 492     }
- 493     {
- 494       break-unless idx
- 495       *prev <- put *prev, next-sandbox:offset, curr
- 496     }
- 497     idx <- add idx, 1
- 498     prev <- copy curr
- 499     loop
- 500   }
- 501   # update sandbox count
- 502   *env <- put *env, number-of-sandboxes:offset, idx
- 503 ]
- 504 
- 505 # print the fake sandbox screen to 'screen' with appropriate delimiters
- 506 # leave cursor at start of next line
- 507 def render-screen screen:&:screen, sandbox-screen:&:screen, left:num, right:num, row:num -> row:num, screen:&:screen [
- 508   local-scope
- 509   load-inputs
- 510   return-unless sandbox-screen
- 511   # print 'screen:'
- 512   row <- render-text screen, [screen:], left, right, 245/grey, row
- 513   screen <- move-cursor screen, row, left
- 514   # start printing sandbox-screen
- 515   column:num <- copy left
- 516   s-width:num <- screen-width sandbox-screen
- 517   s-height:num <- screen-height sandbox-screen
- 518   buf:&:@:screen-cell <- get *sandbox-screen, data:offset
- 519   stop-printing:num <- add left, s-width, 3
- 520   max-column:num <- min stop-printing, right
- 521   i:num <- copy 0
- 522   len:num <- length *buf
- 523   screen-height:num <- screen-height screen
- 524   {
- 525     done?:bool <- greater-or-equal i, len
- 526     break-if done?
- 527     done? <- greater-or-equal row, screen-height
- 528     break-if done?
- 529     column <- copy left
- 530     screen <- move-cursor screen, row, column
- 531     # initial leader for each row: two spaces and a '.'
- 532     space:char <- copy 32/space
- 533     print screen, space, 245/grey
- 534     print screen, space, 245/grey
- 535     full-stop:char <- copy 46/period
- 536     print screen, full-stop, 245/grey
- 537     column <- add left, 3
- 538     {
- 539       # print row
- 540       row-done?:bool <- greater-or-equal column, max-column
- 541       break-if row-done?
- 542       curr:screen-cell <- index *buf, i
- 543       c:char <- get curr, contents:offset
- 544       color:num <- get curr, color:offset
- 545       {
- 546         # damp whites down to grey
- 547         white?:bool <- equal color, 7/white
- 548         break-unless white?
- 549         color <- copy 245/grey
- 550       }
- 551       print screen, c, color
- 552       column <- add column, 1
- 553       i <- add i, 1
- 554       loop
- 555     }
- 556     # print final '.'
- 557     print screen, full-stop, 245/grey
- 558     column <- add column, 1
- 559     {
- 560       # clear rest of current line
- 561       line-done?:bool <- greater-than column, right
- 562       break-if line-done?
- 563       print screen, space
- 564       column <- add column, 1
- 565       loop
- 566     }
- 567     row <- add row, 1
- 568     loop
- 569   }
- 570 ]
- 571 
- 572 scenario run-updates-results [
- 573   local-scope
- 574   trace-until 100/app  # trace too long
- 575   assume-screen 100/width, 12/height
- 576   # define a recipe (no indent for the 'add' line below so column numbers are more obvious)
- 577   assume-resources [
- 578     [lesson/recipes.mu] <- [
- 579       ||
- 580       |recipe foo [|
- 581       |  local-scope|
- 582       |  z:num <- add 2, 2|
- 583       |  reply z|
- 584       |]|
- 585     ]
- 586   ]
- 587   # sandbox editor contains an instruction without storing outputs
- 588   env:&:environment <- new-programming-environment resources, screen, [foo]  # contents of sandbox editor
- 589   render-all screen, env, render
- 590   $clear-trace
- 591   # run the code in the editors
- 592   assume-console [
- 593     press F4
- 594   ]
- 595   event-loop screen, console, env, resources
- 596   screen-should-contain [
- 597     .                                                                                 run (F4)           .
- 598     .                                                  ╎                                                 .
- 599     .recipe foo [                                      ╎─────────────────────────────────────────────────.
- 600     .  local-scope                                     ╎0   edit       copy       to recipe    delete    .
- 601     .  z:num <- add 2, 2                               ╎foo                                              .
- 602     .  reply z                                         ╎4                                                .
- 603     .]                                                 ╎─────────────────────────────────────────────────.
- 604     .                                                  ╎                                                 .
- 605     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
- 606     .                                                  ╎                                                 .
- 607   ]
- 608   # the new sandbox should be saved to disk
- 609   trace-should-contain [
- 610     app: save sandboxes
- 611   ]
- 612   # no need to update editor
- 613   trace-should-not-contain [
- 614     app: render recipes
- 615   ]
- 616   # make a change (incrementing one of the args to 'add'), then rerun
- 617   $clear-trace
- 618   assume-console [
- 619     left-click 4, 28  # one past the value of the second arg
- 620     press backspace
- 621     type [3]
- 622     press F4
- 623   ]
- 624   run [
- 625     event-loop screen, console, env, resources
- 626   ]
- 627   # check that screen updates the result on the right
- 628   screen-should-contain [
- 629     .                                                                                 run (F4)           .
- 630     .                                                  ╎                                                 .
- 631     .recipe foo [                                      ╎─────────────────────────────────────────────────.
- 632     .  local-scope                                     ╎0   edit       copy       to recipe    delete    .
- 633     .  z:num <- add 2, 3                               ╎foo                                              .
- 634     .  reply z                                         ╎5                                                .
- 635     .]                                                 ╎─────────────────────────────────────────────────.
- 636     .                                                  ╎                                                 .
- 637     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
- 638     .                                                  ╎                                                 .
- 639   ]
- 640   # no need to save sandboxes all over again
- 641   trace-should-not-contain [
- 642     app: save sandboxes
- 643   ]
- 644 ]
- 645 
- 646 scenario run-instruction-manages-screen-per-sandbox [
- 647   local-scope
- 648   trace-until 100/app  # trace too long
- 649   assume-screen 100/width, 20/height
- 650   # empty recipes
- 651   assume-resources [
- 652   ]
- 653   # sandbox editor contains an instruction
- 654   env:&:environment <- new-programming-environment resources, screen, [print screen, 4]  # contents of sandbox editor
- 655   render-all screen, env, render
- 656   # run the code in the editor
- 657   assume-console [
- 658     press F4
- 659   ]
- 660   run [
- 661     event-loop screen, console, env, resources
- 662   ]
- 663   # check that it prints a little toy screen
- 664   screen-should-contain [
- 665     .                                                                                 run (F4)           .
- 666     .                                                  ╎                                                 .
- 667     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 668     .                                                  ╎0   edit       copy       to recipe    delete    .
- 669     .                                                  ╎print screen, 4                                  .
- 670     .                                                  ╎screen:                                          .
- 671     .                                                  ╎  .4                             .               .
- 672     .                                                  ╎  .                              .               .
- 673     .                                                  ╎  .                              .               .
- 674     .                                                  ╎  .                              .               .
- 675     .                                                  ╎  .                              .               .
- 676     .                                                  ╎─────────────────────────────────────────────────.
- 677     .                                                  ╎                                                 .
- 678   ]
- 679 ]
- 680 
- 681 def editor-contents editor:&:editor -> result:text [
- 682   local-scope
- 683   load-inputs
- 684   buf:&:buffer:char <- new-buffer 80
- 685   curr:&:duplex-list:char <- get *editor, data:offset
- 686   # skip § sentinel
- 687   assert curr, [editor without data is illegal; must have at least a sentinel]
- 688   curr <- next curr
- 689   return-unless curr, null
- 690   {
- 691     break-unless curr
- 692     c:char <- get *curr, value:offset
- 693     buf <- append buf, c
- 694     curr <- next curr
- 695     loop
- 696   }
- 697   result <- buffer-to-array buf
- 698 ]
- 699 
- 700 scenario editor-provides-edited-contents [
- 701   local-scope
- 702   assume-screen 10/width, 5/height
- 703   e:&:editor <- new-editor [abc], 0/left, 10/right
- 704   assume-console [
- 705     left-click 1, 2
- 706     type [def]
- 707   ]
- 708   run [
- 709     editor-event-loop screen, console, e
- 710     s:text <- editor-contents e
- 711     1:@:char/raw <- copy *s
- 712   ]
- 713   memory-should-contain [
- 714     1:array:character <- [abdefc]
- 715   ]
- 716 ]
- 717 
- 718 # keep the bottom of recipes from scrolling off the screen
- 719 
- 720 scenario scrolling-down-past-bottom-of-recipe-editor [
- 721   local-scope
- 722   trace-until 100/app
- 723   assume-screen 100/width, 10/height
- 724   assume-resources [
- 725   ]
- 726   env:&:environment <- new-programming-environment resources, screen, []
- 727   render-all screen, env, render
- 728   assume-console [
- 729     press enter
- 730     press down-arrow
- 731   ]
- 732   event-loop screen, console, env, resources
- 733   # no scroll
- 734   screen-should-contain [
- 735     .                                                                                 run (F4)           .
- 736     .                                                  ╎                                                 .
- 737     .                                                  ╎─────────────────────────────────────────────────.
- 738     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
- 739     .                                                  ╎                                                 .
- 740   ]
- 741 ]
- 742 
- 743 scenario cursor-down-in-recipe-editor [
- 744   local-scope
- 745   trace-until 100/app
- 746   assume-screen 100/width, 10/height
- 747   assume-resources [
- 748   ]
- 749   env:&:environment <- new-programming-environment resources, screen, []
- 750   render-all screen, env, render
- 751   assume-console [
- 752     press enter
- 753     press up-arrow
- 754     press down-arrow  # while cursor isn't at bottom
- 755   ]
- 756   event-loop screen, console, env, resources
- 757   cursor:char <- copy 9251/␣
- 758   print screen, cursor
- 759   # cursor moves back to bottom
- 760   screen-should-contain [
- 761     .                                                                                 run (F4)           .
- 762     .                                                  ╎                                                 .
- 763     .␣                                                 ╎─────────────────────────────────────────────────.
- 764     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
- 765     .                                                  ╎                                                 .
- 766   ]
- 767 ]
- 768 
- 769 scenario scrolling-down-past-bottom-of-recipe-editor-2 [
- 770   local-scope
- 771   trace-until 100/app
- 772   assume-screen 100/width, 10/height
- 773   assume-resources [
- 774   ]
- 775   env:&:environment <- new-programming-environment resources, screen, []
- 776   render-all screen, env, render
- 777   assume-console [
- 778     # add a line
- 779     press enter
- 780     # cursor back to top line
- 781     press up-arrow
- 782     # try to scroll
- 783     press page-down  # or ctrl-f
- 784   ]
- 785   event-loop screen, console, env, resources
- 786   # no scroll, and cursor remains at top line
- 787   screen-should-contain [
- 788     .                                                                                 run (F4)           .
- 789     .                                                  ╎                                                 .
- 790     .                                                  ╎─────────────────────────────────────────────────.
- 791     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
- 792     .                                                  ╎                                                 .
- 793   ]
- 794 ]
- 795 
- 796 scenario scrolling-down-past-bottom-of-recipe-editor-3 [
- 797   local-scope
- 798   trace-until 100/app
- 799   assume-screen 100/width, 10/height
- 800   assume-resources [
- 801   ]
- 802   env:&:environment <- new-programming-environment resources, screen, [ab
- 803 cd]
- 804   render-all screen, env, render
- 805   assume-console [
- 806     # add a line
- 807     press enter
- 808     # switch to sandbox
- 809     press ctrl-n
- 810     # move cursor
- 811     press down-arrow
- 812   ]
- 813   event-loop screen, console, env, resources
- 814   cursor:char <- copy 9251/␣
- 815   print screen, cursor
- 816   # no scroll on recipe side, cursor moves on sandbox side
- 817   screen-should-contain [
- 818     .                                                                                 run (F4)           .
- 819     .                                                  ╎ab                                               .
- 820     .                                                  ╎␣d                                               .
- 821     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 822     .                                                  ╎                                                 .
- 823   ]
- 824 ]
- 825 
- 826 # scrolling through sandboxes
- 827 
- 828 scenario scrolling-down-past-bottom-of-sandbox-editor [
- 829   local-scope
- 830   trace-until 100/app  # trace too long
- 831   assume-screen 100/width, 10/height
- 832   # initialize
- 833   assume-resources [
- 834   ]
- 835   env:&:environment <- new-programming-environment resources, screen, [add 2, 2]
- 836   render-all screen, env, render
- 837   assume-console [
- 838     # create a sandbox
- 839     press F4
- 840   ]
- 841   event-loop screen, console, env, resources
- 842   screen-should-contain [
- 843     .                                                                                 run (F4)           .
- 844     .                                                  ╎                                                 .
- 845     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 846     .                                                  ╎0   edit       copy       to recipe    delete    .
- 847     .                                                  ╎add 2, 2                                         .
- 848   ]
- 849   # switch to sandbox window and hit 'page-down'
- 850   assume-console [
- 851     press ctrl-n
- 852     press page-down
- 853   ]
- 854   run [
- 855     event-loop screen, console, env, resources
- 856     cursor:char <- copy 9251/␣
- 857     print screen, cursor
- 858   ]
- 859   # sandbox editor hidden; first sandbox displayed
- 860   # cursor moves to first sandbox
- 861   screen-should-contain [
- 862     .                                                                                 run (F4)           .
- 863     .                                                  ╎─────────────────────────────────────────────────.
- 864     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎␣   edit       copy       to recipe    delete    .
- 865     .                                                  ╎add 2, 2                                         .
- 866     .                                                  ╎4                                                .
- 867   ]
- 868   # hit 'page-up'
- 869   assume-console [
- 870     press page-up
- 871   ]
- 872   run [
- 873     event-loop screen, console, env, resources
- 874     cursor:char <- copy 9251/␣
- 875     print screen, cursor
- 876   ]
- 877   # sandbox editor displays again, cursor is in editor
- 878   screen-should-contain [
- 879     .                                                                                 run (F4)           .
- 880     .                                                  ╎␣                                                .
- 881     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 882     .                                                  ╎0   edit       copy       to recipe    delete    .
- 883     .                                                  ╎add 2, 2                                         .
- 884   ]
- 885 ]
- 886 
- 887 # page-down on sandbox side updates render-from to scroll sandboxes
- 888 after <global-keypress> [
- 889   {
- 890     break-unless sandbox-in-focus?
- 891     page-down?:bool <- equal k, 65518/page-down
- 892     break-unless page-down?
- 893     sandbox:&:sandbox <- get *env, sandbox:offset
- 894     break-unless sandbox
- 895     # slide down if possible
- 896     {
- 897       render-from:num <- get *env, render-from:offset
- 898       number-of-sandboxes:num <- get *env, number-of-sandboxes:offset
- 899       max:num <- subtract number-of-sandboxes, 1
- 900       at-end?:bool <- greater-or-equal render-from, max
- 901       loop-if at-end?, +next-event  # render nothing
- 902       render-from <- add render-from, 1
- 903       *env <- put *env, render-from:offset, render-from
- 904     }
- 905     screen <- render-sandbox-side screen, env, render
- 906     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
- 907     loop +next-event
- 908   }
- 909 ]
- 910 
- 911 # update-cursor takes render-from into account
- 912 after <update-cursor-special-cases> [
- 913   {
- 914     break-unless sandbox-in-focus?
- 915     render-from:num <- get *env, render-from:offset
- 916     scrolling?:bool <- greater-or-equal render-from, 0
- 917     break-unless scrolling?
- 918     cursor-column:num <- get *current-sandbox, left:offset
- 919     screen <- move-cursor screen, 2/row, cursor-column  # highlighted sandbox will always start at row 2
- 920     return
- 921   }
- 922 ]
- 923 
- 924 # 'page-up' on sandbox side is like 'page-down': updates render-from when necessary
- 925 after <global-keypress> [
- 926   {
- 927     break-unless sandbox-in-focus?
- 928     page-up?:bool <- equal k, 65519/page-up
- 929     break-unless page-up?
- 930     render-from:num <- get *env, render-from:offset
- 931     at-beginning?:bool <- equal render-from, -1
- 932     break-if at-beginning?
- 933     render-from <- subtract render-from, 1
- 934     *env <- put *env, render-from:offset, render-from
- 935     screen <- render-sandbox-side screen, env, render
- 936     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
- 937     loop +next-event
- 938   }
- 939 ]
- 940 
- 941 # sandbox belonging to 'env' whose next-sandbox is 'in'
- 942 # return null if there's no such sandbox, either because 'in' doesn't exist in 'env', or because it's the first sandbox
- 943 def previous-sandbox env:&:environment, in:&:sandbox -> out:&:sandbox [
- 944   local-scope
- 945   load-inputs
- 946   curr:&:sandbox <- get *env, sandbox:offset
- 947   return-unless curr, null
- 948   next:&:sandbox <- get *curr, next-sandbox:offset
- 949   {
- 950     return-unless next, null
- 951     found?:bool <- equal next, in
- 952     break-if found?
- 953     curr <- copy next
- 954     next <- get *curr, next-sandbox:offset
- 955     loop
- 956   }
- 957   return curr
- 958 ]
- 959 
- 960 scenario scrolling-through-multiple-sandboxes [
- 961   local-scope
- 962   trace-until 100/app  # trace too long
- 963   assume-screen 100/width, 10/height
- 964   # initialize environment
- 965   assume-resources [
- 966   ]
- 967   env:&:environment <- new-programming-environment resources, screen, []
- 968   render-all screen, env, render
- 969   # create 2 sandboxes
- 970   assume-console [
- 971     press ctrl-n
- 972     type [add 2, 2]
- 973     press F4
- 974     type [add 1, 1]
- 975     press F4
- 976   ]
- 977   event-loop screen, console, env, resources
- 978   cursor:char <- copy 9251/␣
- 979   print screen, cursor
- 980   screen-should-contain [
- 981     .                                                                                 run (F4)           .
- 982     .                                                  ╎␣                                                .
- 983     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 984     .                                                  ╎0   edit       copy       to recipe    delete    .
- 985     .                                                  ╎add 1, 1                                         .
- 986     .                                                  ╎2                                                .
- 987     .                                                  ╎─────────────────────────────────────────────────.
- 988     .                                                  ╎1   edit       copy       to recipe    delete    .
- 989     .                                                  ╎add 2, 2                                         .
- 990     .                                                  ╎4                                                .
- 991   ]
- 992   # hit 'page-down'
- 993   assume-console [
- 994     press page-down
- 995   ]
- 996   run [
- 997     event-loop screen, console, env, resources
- 998     cursor:char <- copy 9251/␣
- 999     print screen, cursor
-1000   ]
-1001   # sandbox editor hidden; first sandbox displayed
-1002   # cursor moves to first sandbox
-1003   screen-should-contain [
-1004     .                                                                                 run (F4)           .
-1005     .                                                  ╎─────────────────────────────────────────────────.
-1006     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎␣   edit       copy       to recipe    delete    .
-1007     .                                                  ╎add 1, 1                                         .
-1008     .                                                  ╎2                                                .
-1009     .                                                  ╎─────────────────────────────────────────────────.
-1010     .                                                  ╎1   edit       copy       to recipe    delete    .
-1011     .                                                  ╎add 2, 2                                         .
-1012     .                                                  ╎4                                                .
-1013   ]
-1014   # hit 'page-down' again
-1015   assume-console [
-1016     press page-down
-1017   ]
-1018   run [
-1019     event-loop screen, console, env, resources
-1020   ]
-1021   # just second sandbox displayed
-1022   screen-should-contain [
-1023     .                                                                                 run (F4)           .
-1024     .                                                  ╎─────────────────────────────────────────────────.
-1025     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎1   edit       copy       to recipe    delete    .
-1026     .                                                  ╎add 2, 2                                         .
-1027     .                                                  ╎4                                                .
-1028     .                                                  ╎─────────────────────────────────────────────────.
-1029     .                                                  ╎                                                 .
-1030   ]
-1031   # hit 'page-down' again
-1032   assume-console [
-1033     press page-down
-1034   ]
-1035   run [
-1036     event-loop screen, console, env, resources
-1037   ]
-1038   # no change
-1039   screen-should-contain [
-1040     .                                                                                 run (F4)           .
-1041     .                                                  ╎─────────────────────────────────────────────────.
-1042     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎1   edit       copy       to recipe    delete    .
-1043     .                                                  ╎add 2, 2                                         .
-1044     .                                                  ╎4                                                .
-1045     .                                                  ╎─────────────────────────────────────────────────.
-1046     .                                                  ╎                                                 .
-1047   ]
-1048   # hit 'page-up'
-1049   assume-console [
-1050     press page-up
-1051   ]
-1052   run [
-1053     event-loop screen, console, env, resources
-1054   ]
-1055   # back to displaying both sandboxes without editor
-1056   screen-should-contain [
-1057     .                                                                                 run (F4)           .
-1058     .                                                  ╎─────────────────────────────────────────────────.
-1059     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎0   edit       copy       to recipe    delete    .
-1060     .                                                  ╎add 1, 1                                         .
-1061     .                                                  ╎2                                                .
-1062     .                                                  ╎─────────────────────────────────────────────────.
-1063     .                                                  ╎1   edit       copy       to recipe    delete    .
-1064     .                                                  ╎add 2, 2                                         .
-1065     .                                                  ╎4                                                .
-1066   ]
-1067   # hit 'page-up' again
-1068   assume-console [
-1069     press page-up
-1070   ]
-1071   run [
-1072     event-loop screen, console, env, resources
-1073     cursor:char <- copy 9251/␣
-1074     print screen, cursor
-1075   ]
-1076   # back to displaying both sandboxes as well as editor
-1077   screen-should-contain [
-1078     .                                                                                 run (F4)           .
-1079     .                                                  ╎␣                                                .
-1080     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-1081     .                                                  ╎0   edit       copy       to recipe    delete    .
-1082     .                                                  ╎add 1, 1                                         .
-1083     .                                                  ╎2                                                .
-1084     .                                                  ╎─────────────────────────────────────────────────.
-1085     .                                                  ╎1   edit       copy       to recipe    delete    .
-1086     .                                                  ╎add 2, 2                                         .
-1087     .                                                  ╎4                                                .
-1088   ]
-1089   # hit 'page-up' again
-1090   assume-console [
-1091     press page-up
-1092   ]
-1093   run [
-1094     event-loop screen, console, env, resources
-1095     cursor:char <- copy 9251/␣
-1096     print screen, cursor
-1097   ]
-1098   # no change
-1099   screen-should-contain [
-1100     .                                                                                 run (F4)           .
-1101     .                                                  ╎␣                                                .
-1102     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-1103     .                                                  ╎0   edit       copy       to recipe    delete    .
-1104     .                                                  ╎add 1, 1                                         .
-1105     .                                                  ╎2                                                .
-1106     .                                                  ╎─────────────────────────────────────────────────.
-1107     .                                                  ╎1   edit       copy       to recipe    delete    .
-1108     .                                                  ╎add 2, 2                                         .
-1109     .                                                  ╎4                                                .
-1110   ]
-1111 ]
-1112 
-1113 scenario scrolling-manages-sandbox-index-correctly [
-1114   local-scope
-1115   trace-until 100/app  # trace too long
-1116   assume-screen 100/width, 10/height
-1117   # initialize environment
-1118   assume-resources [
-1119   ]
-1120   env:&:environment <- new-programming-environment resources, screen, []
-1121   render-all screen, env, render
-1122   # create a sandbox
-1123   assume-console [
-1124     press ctrl-n
-1125     type [add 1, 1]
-1126     press F4
-1127   ]
-1128   event-loop screen, console, env, resources
-1129   screen-should-contain [
-1130     .                                                                                 run (F4)           .
-1131     .                                                  ╎                                                 .
-1132     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-1133     .                                                  ╎0   edit       copy       to recipe    delete    .
-1134     .                                                  ╎add 1, 1                                         .
-1135     .                                                  ╎2                                                .
-1136     .                                                  ╎─────────────────────────────────────────────────.
-1137     .                                                  ╎                                                 .
-1138   ]
-1139   # hit 'page-down' and 'page-up' a couple of times. sandbox index should be stable
-1140   assume-console [
-1141     press page-down
-1142   ]
-1143   run [
-1144     event-loop screen, console, env, resources
-1145   ]
-1146   # sandbox editor hidden; first sandbox displayed
-1147   # cursor moves to first sandbox
-1148   screen-should-contain [
-1149     .                                                                                 run (F4)           .
-1150     .                                                  ╎─────────────────────────────────────────────────.
-1151     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎0   edit       copy       to recipe    delete    .
-1152     .                                                  ╎add 1, 1                                         .
-1153     .                                                  ╎2                                                .
-1154     .                                                  ╎─────────────────────────────────────────────────.
-1155     .                                                  ╎                                                 .
-1156   ]
-1157   # hit 'page-up' again
-1158   assume-console [
-1159     press page-up
-1160   ]
-1161   run [
-1162     event-loop screen, console, env, resources
-1163   ]
-1164   # back to displaying both sandboxes as well as editor
-1165   screen-should-contain [
-1166     .                                                                                 run (F4)           .
-1167     .                                                  ╎                                                 .
-1168     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-1169     .                                                  ╎0   edit       copy       to recipe    delete    .
-1170     .                                                  ╎add 1, 1                                         .
-1171     .                                                  ╎2                                                .
-1172     .                                                  ╎─────────────────────────────────────────────────.
-1173     .                                                  ╎                                                 .
-1174   ]
-1175   # hit 'page-down'
-1176   assume-console [
-1177     press page-down
-1178   ]
-1179   run [
-1180     event-loop screen, console, env, resources
-1181   ]
-1182   # sandbox editor hidden; first sandbox displayed
-1183   # cursor moves to first sandbox
-1184   screen-should-contain [
-1185     .                                                                                 run (F4)           .
-1186     .                                                  ╎─────────────────────────────────────────────────.
-1187     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎0   edit       copy       to recipe    delete    .
-1188     .                                                  ╎add 1, 1                                         .
-1189     .                                                  ╎2                                                .
-1190     .                                                  ╎─────────────────────────────────────────────────.
-1191     .                                                  ╎                                                 .
-1192   ]
-1193 ]
-
- - - diff --git a/html/edit/006-sandbox-copy.mu.html b/html/edit/006-sandbox-copy.mu.html deleted file mode 100644 index 9fc7e6f4..00000000 --- a/html/edit/006-sandbox-copy.mu.html +++ /dev/null @@ -1,459 +0,0 @@ - - - - -Mu - edit/006-sandbox-copy.mu - - - - - - - - - - -https://github.com/akkartik/mu/blob/master/edit/006-sandbox-copy.mu -
-  1 ## the 'copy' button makes it easy to duplicate a sandbox, and thence to
-  2 ## see code operate in multiple situations
-  3 
-  4 scenario copy-a-sandbox-to-editor [
-  5   local-scope
-  6   trace-until 100/app  # trace too long
-  7   assume-screen 100/width, 10/height
-  8   # empty recipes
-  9   assume-resources [
- 10   ]
- 11   env:&:environment <- new-programming-environment resources, screen, [add 1, 1]  # contents of sandbox editor
- 12   render-all screen, env, render
- 13   # run it
- 14   assume-console [
- 15     press F4
- 16   ]
- 17   event-loop screen, console, env, resources
- 18   screen-should-contain [
- 19     .                                                                                 run (F4)           .
- 20     .                                                  ╎                                                 .
- 21     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 22     .                                                  ╎0   edit       copy       to recipe    delete    .
- 23     .                                                  ╎add 1, 1                                         .
- 24     .                                                  ╎2                                                .
- 25     .                                                  ╎─────────────────────────────────────────────────.
- 26     .                                                  ╎                                                 .
- 27   ]
- 28   # click at left edge of 'copy' button
- 29   assume-console [
- 30     left-click 3, 69
- 31   ]
- 32   run [
- 33     event-loop screen, console, env, resources
- 34   ]
- 35   # it copies into editor
- 36   screen-should-contain [
- 37     .                                                                                 run (F4)           .
- 38     .                                                  ╎add 1, 1                                         .
- 39     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 40     .                                                  ╎0   edit       copy       to recipe    delete    .
- 41     .                                                  ╎add 1, 1                                         .
- 42     .                                                  ╎2                                                .
- 43     .                                                  ╎─────────────────────────────────────────────────.
- 44     .                                                  ╎                                                 .
- 45   ]
- 46   # cursor should be in the right place
- 47   assume-console [
- 48     type [0]
- 49   ]
- 50   run [
- 51     event-loop screen, console, env, resources
- 52   ]
- 53   screen-should-contain [
- 54     .                                                                                 run (F4)           .
- 55     .                                                  ╎0add 1, 1                                        .
- 56     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 57     .                                                  ╎0   edit       copy       to recipe    delete    .
- 58     .                                                  ╎add 1, 1                                         .
- 59     .                                                  ╎2                                                .
- 60     .                                                  ╎─────────────────────────────────────────────────.
- 61     .                                                  ╎                                                 .
- 62   ]
- 63 ]
- 64 
- 65 scenario copy-a-sandbox-to-editor-2 [
- 66   local-scope
- 67   trace-until 100/app  # trace too long
- 68   assume-screen 100/width, 10/height
- 69   # empty recipes
- 70   assume-resources [
- 71   ]
- 72   env:&:environment <- new-programming-environment resources, screen, [add 1, 1]  # contents of sandbox editor
- 73   render-all screen, env, render
- 74   # run it
- 75   assume-console [
- 76     press F4
- 77   ]
- 78   event-loop screen, console, env, resources
- 79   screen-should-contain [
- 80     .                                                                                 run (F4)           .
- 81     .                                                  ╎                                                 .
- 82     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 83     .                                                  ╎0   edit       copy       to recipe    delete    .
- 84     .                                                  ╎add 1, 1                                         .
- 85     .                                                  ╎2                                                .
- 86     .                                                  ╎─────────────────────────────────────────────────.
- 87     .                                                  ╎                                                 .
- 88   ]
- 89   # click at right edge of 'copy' button (just before 'delete')
- 90   assume-console [
- 91     left-click 3, 76
- 92   ]
- 93   run [
- 94     event-loop screen, console, env, resources
- 95   ]
- 96   # it copies into editor
- 97   screen-should-contain [
- 98     .                                                                                 run (F4)           .
- 99     .                                                  ╎add 1, 1                                         .
-100     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-101     .                                                  ╎0   edit       copy       to recipe    delete    .
-102     .                                                  ╎add 1, 1                                         .
-103     .                                                  ╎2                                                .
-104     .                                                  ╎─────────────────────────────────────────────────.
-105     .                                                  ╎                                                 .
-106   ]
-107   # cursor should be in the right place
-108   assume-console [
-109     type [0]
-110   ]
-111   run [
-112     event-loop screen, console, env, resources
-113   ]
-114   screen-should-contain [
-115     .                                                                                 run (F4)           .
-116     .                                                  ╎0add 1, 1                                        .
-117     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-118     .                                                  ╎0   edit       copy       to recipe    delete    .
-119     .                                                  ╎add 1, 1                                         .
-120     .                                                  ╎2                                                .
-121     .                                                  ╎─────────────────────────────────────────────────.
-122     .                                                  ╎                                                 .
-123   ]
-124 ]
-125 
-126 after <global-touch> [
-127   # support 'copy' button
-128   {
-129     copy?:bool <- should-attempt-copy? click-row, click-column, env
-130     break-unless copy?
-131     copy?, env <- try-copy-sandbox click-row, env
-132     break-unless copy?
-133     screen <- render-sandbox-side screen, env, render
-134     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
-135     loop +next-event
-136   }
-137 ]
-138 
-139 # some preconditions for attempting to copy a sandbox
-140 def should-attempt-copy? click-row:num, click-column:num, env:&:environment -> result:bool [
-141   local-scope
-142   load-inputs
-143   # are we below the sandbox editor?
-144   click-sandbox-area?:bool <- click-on-sandbox-area? click-row, click-column, env
-145   return-unless click-sandbox-area?, false
-146   # narrower, is the click in the columns spanning the 'copy' button?
-147   first-sandbox:&:editor <- get *env, current-sandbox:offset
-148   assert first-sandbox, [!!]
-149   sandbox-left-margin:num <- get *first-sandbox, left:offset
-150   sandbox-right-margin:num <- get *first-sandbox, right:offset
-151   _, _, copy-button-left:num, copy-button-right:num <- sandbox-menu-columns sandbox-left-margin, sandbox-right-margin
-152   copy-button-vertical-area?:bool <- within-range? click-column, copy-button-left, copy-button-right
-153   return-unless copy-button-vertical-area?, false
-154   # finally, is sandbox editor empty?
-155   current-sandbox:&:editor <- get *env, current-sandbox:offset
-156   result <- empty-editor? current-sandbox
-157 ]
-158 
-159 def try-copy-sandbox click-row:num, env:&:environment -> clicked-on-copy-button?:bool, env:&:environment [
-160   local-scope
-161   load-inputs
-162   # identify the sandbox to copy, if the click was actually on the 'copy' button
-163   sandbox:&:sandbox <- find-sandbox env, click-row
-164   return-unless sandbox, false
-165   clicked-on-copy-button? <- copy true
-166   text:text <- get *sandbox, data:offset
-167   current-sandbox:&:editor <- get *env, current-sandbox:offset
-168   current-sandbox <- insert-text current-sandbox, text
-169   # reset scroll
-170   *env <- put *env, render-from:offset, -1
-171   # position cursor in sandbox editor
-172   *env <- put *env, sandbox-in-focus?:offset, true
-173 ]
-174 
-175 def find-sandbox env:&:environment, click-row:num -> result:&:sandbox [
-176   local-scope
-177   load-inputs
-178   curr-sandbox:&:sandbox <- get *env, sandbox:offset
-179   {
-180     break-unless curr-sandbox
-181     start:num <- get *curr-sandbox, starting-row-on-screen:offset
-182     found?:bool <- equal click-row, start
-183     return-if found?, curr-sandbox
-184     curr-sandbox <- get *curr-sandbox, next-sandbox:offset
-185     loop
-186   }
-187   return null/not-found
-188 ]
-189 
-190 def click-on-sandbox-area? click-row:num, click-column:num, env:&:environment -> result:bool [
-191   local-scope
-192   load-inputs
-193   current-sandbox:&:editor <- get *env, current-sandbox:offset
-194   sandbox-left-margin:num <- get *current-sandbox, left:offset
-195   on-sandbox-side?:bool <- greater-or-equal click-column, sandbox-left-margin
-196   return-unless on-sandbox-side?, false
-197   first-sandbox:&:sandbox <- get *env, sandbox:offset
-198   return-unless first-sandbox, false
-199   first-sandbox-begins:num <- get *first-sandbox, starting-row-on-screen:offset
-200   result <- greater-or-equal click-row, first-sandbox-begins
-201 ]
-202 
-203 def empty-editor? editor:&:editor -> result:bool [
-204   local-scope
-205   load-inputs
-206   head:&:duplex-list:char <- get *editor, data:offset
-207   first:&:duplex-list:char <- next head
-208   result <- not first
-209 ]
-210 
-211 def within-range? x:num, low:num, high:num -> result:bool [
-212   local-scope
-213   load-inputs
-214   not-too-far-left?:bool <- greater-or-equal x, low
-215   not-too-far-right?:bool <- lesser-or-equal x, high
-216   result <- and not-too-far-left? not-too-far-right?
-217 ]
-218 
-219 scenario copy-fails-if-sandbox-editor-not-empty [
-220   local-scope
-221   trace-until 100/app  # trace too long
-222   assume-screen 100/width, 10/height
-223   # empty recipes
-224   assume-resources [
-225   ]
-226   env:&:environment <- new-programming-environment resources, screen, [add 1, 1]  # contents of sandbox editor
-227   render-all screen, env, render
-228   # run it
-229   assume-console [
-230     press F4
-231   ]
-232   event-loop screen, console, env, resources
-233   screen-should-contain [
-234     .                                                                                 run (F4)           .
-235     .                                                  ╎                                                 .
-236     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-237     .                                                  ╎0   edit       copy       to recipe    delete    .
-238     .                                                  ╎add 1, 1                                         .
-239     .                                                  ╎2                                                .
-240     .                                                  ╎─────────────────────────────────────────────────.
-241     .                                                  ╎                                                 .
-242   ]
-243   # type something into the sandbox editor, then click on the 'copy' button
-244   assume-console [
-245     left-click 2, 70  # put cursor in sandbox editor
-246     type [0]  # type something
-247     left-click 3, 70  # click 'copy' button
-248   ]
-249   run [
-250     event-loop screen, console, env, resources
-251   ]
-252   # copy doesn't happen
-253   screen-should-contain [
-254     .                                                                                 run (F4)           .
-255     .                                                  ╎0                                                .
-256     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-257     .                                                  ╎0   edit       copy       to recipe    delete    .
-258     .                                                  ╎add 1, 1                                         .
-259     .                                                  ╎2                                                .
-260     .                                                  ╎─────────────────────────────────────────────────.
-261     .                                                  ╎                                                 .
-262   ]
-263   # cursor should be in the right place
-264   assume-console [
-265     type [1]
-266   ]
-267   run [
-268     event-loop screen, console, env, resources
-269   ]
-270   screen-should-contain [
-271     .                                                                                 run (F4)           .
-272     .                                                  ╎01                                               .
-273     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-274     .                                                  ╎0   edit       copy       to recipe    delete    .
-275     .                                                  ╎add 1, 1                                         .
-276     .                                                  ╎2                                                .
-277     .                                                  ╎─────────────────────────────────────────────────.
-278     .                                                  ╎                                                 .
-279   ]
-280 ]
-281 
-282 ## the 'to recipe' button makes it easy to create a function out of a sandbox
-283 
-284 scenario copy-a-sandbox-to-recipe-side [
-285   local-scope
-286   trace-until 100/app  # trace too long
-287   assume-screen 100/width, 10/height
-288   # empty recipes
-289   assume-resources [
-290   ]
-291   env:&:environment <- new-programming-environment resources, screen, [add 1, 1]  # contents of sandbox editor
-292   render-all screen, env, render
-293   # run it
-294   assume-console [
-295     press F4
-296   ]
-297   event-loop screen, console, env, resources
-298   screen-should-contain [
-299     .                                                                                 run (F4)           .
-300     .                                                  ╎                                                 .
-301     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-302     .                                                  ╎0   edit       copy       to recipe    delete    .
-303     .                                                  ╎add 1, 1                                         .
-304     .                                                  ╎2                                                .
-305     .                                                  ╎─────────────────────────────────────────────────.
-306     .                                                  ╎                                                 .
-307   ]
-308   # click at left edge of 'copy' button
-309   assume-console [
-310     left-click 3, 78
-311   ]
-312   run [
-313     event-loop screen, console, env, resources
-314   ]
-315   # it copies into recipe side
-316   screen-should-contain [
-317     .                                                                                 run (F4)           .
-318     .add 1, 1                                          ╎                                                 .
-319     .                                                  ╎─────────────────────────────────────────────────.
-320     .                                                  ╎0   edit       copy       to recipe    delete    .
-321     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎add 1, 1                                         .
-322     .                                                  ╎2                                                .
-323     .                                                  ╎─────────────────────────────────────────────────.
-324     .                                                  ╎                                                 .
-325   ]
-326   # cursor should be at the top left of the recipe side
-327   assume-console [
-328     type [0]
-329   ]
-330   run [
-331     event-loop screen, console, env, resources
-332   ]
-333   screen-should-contain [
-334     .                                                                                 run (F4)           .
-335     .0add 1, 1                                         ╎                                                 .
-336     .                                                  ╎─────────────────────────────────────────────────.
-337     .                                                  ╎0   edit       copy       to recipe    delete    .
-338     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎add 1, 1                                         .
-339     .                                                  ╎2                                                .
-340     .                                                  ╎─────────────────────────────────────────────────.
-341     .                                                  ╎                                                 .
-342   ]
-343 ]
-344 
-345 after <global-touch> [
-346   # support 'copy to recipe' button
-347   {
-348     copy?:bool <- should-copy-to-recipe? click-row, click-column, env
-349     break-unless copy?
-350     modified?:bool <- prepend-sandbox-into-recipe-side click-row, env
-351     break-unless modified?
-352     *env <- put *env, sandbox-in-focus?:offset, false
-353     screen <- render-recipes screen, env, render
-354     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
-355     loop +next-event
-356   }
-357 ]
-358 
-359 # some preconditions for attempting to copy a sandbox into the recipe side
-360 def should-copy-to-recipe? click-row:num, click-column:num, env:&:environment -> result:bool [
-361   local-scope
-362   load-inputs
-363   # are we below the sandbox editor?
-364   click-sandbox-area?:bool <- click-on-sandbox-area? click-row, click-column, env
-365   return-unless click-sandbox-area?, false
-366   # narrower, is the click in the columns spanning the 'copy' button?
-367   first-sandbox:&:editor <- get *env, current-sandbox:offset
-368   assert first-sandbox, [!!]
-369   sandbox-left-margin:num <- get *first-sandbox, left:offset
-370   sandbox-right-margin:num <- get *first-sandbox, right:offset
-371   _, _, _, _, recipe-button-left:num, recipe-button-right:num <- sandbox-menu-columns sandbox-left-margin, sandbox-right-margin
-372   result <- within-range? click-column, recipe-button-left, recipe-button-right
-373 ]
-374 
-375 def prepend-sandbox-into-recipe-side click-row:num, env:&:environment -> clicked-on-copy-to-recipe-button?:bool, env:&:environment [
-376   local-scope
-377   load-inputs
-378   sandbox:&:sandbox <- find-sandbox env, click-row
-379   return-unless sandbox, false
-380   recipe-editor:&:editor <- get *env, recipes:offset
-381   recipe-data:&:duplex-list:char <- get *recipe-editor, data:offset
-382   # make the newly inserted code easy to delineate
-383   newline:char <- copy 10
-384   insert newline, recipe-data
-385   insert newline, recipe-data
-386   # insert code from the selected sandbox
-387   sandbox-data:text <- get *sandbox, data:offset
-388   insert recipe-data, sandbox-data
-389   # reset cursor
-390   *recipe-editor <- put *recipe-editor, top-of-screen:offset, recipe-data
-391   *recipe-editor <- put *recipe-editor, before-cursor:offset, recipe-data
-392   *recipe-editor <- put *recipe-editor, cursor-row:offset, 1
-393   *recipe-editor <- put *recipe-editor, cursor-column:offset, 0
-394   return true
-395 ]
-
- - - diff --git a/html/edit/007-sandbox-delete.mu.html b/html/edit/007-sandbox-delete.mu.html deleted file mode 100644 index 26f46332..00000000 --- a/html/edit/007-sandbox-delete.mu.html +++ /dev/null @@ -1,406 +0,0 @@ - - - - -Mu - edit/007-sandbox-delete.mu - - - - - - - - - - -https://github.com/akkartik/mu/blob/master/edit/007-sandbox-delete.mu -
-  1 ## deleting sandboxes
-  2 
-  3 scenario deleting-sandboxes [
-  4   local-scope
-  5   trace-until 100/app  # trace too long
-  6   assume-screen 100/width, 15/height
-  7   assume-resources [
-  8   ]
-  9   env:&:environment <- new-programming-environment resources, screen, []
- 10   render-all screen, env, render
- 11   # run a few commands
- 12   assume-console [
- 13     left-click 1, 75
- 14     type [divide-with-remainder 11, 3]
- 15     press F4
- 16     type [add 2, 2]
- 17     press F4
- 18   ]
- 19   event-loop screen, console, env, resources
- 20   screen-should-contain [
- 21     .                                                                                 run (F4)           .
- 22     .                                                  ╎                                                 .
- 23     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 24     .                                                  ╎0   edit       copy       to recipe    delete    .
- 25     .                                                  ╎add 2, 2                                         .
- 26     .                                                  ╎4                                                .
- 27     .                                                  ╎─────────────────────────────────────────────────.
- 28     .                                                  ╎1   edit       copy       to recipe    delete    .
- 29     .                                                  ╎divide-with-remainder 11, 3                      .
- 30     .                                                  ╎3                                                .
- 31     .                                                  ╎2                                                .
- 32     .                                                  ╎─────────────────────────────────────────────────.
- 33     .                                                  ╎                                                 .
- 34   ]
- 35   # delete second sandbox by clicking on left edge of 'delete' button
- 36   assume-console [
- 37     left-click 7, 90
- 38   ]
- 39   run [
- 40     event-loop screen, console, env, resources
- 41   ]
- 42   screen-should-contain [
- 43     .                                                                                 run (F4)           .
- 44     .                                                  ╎                                                 .
- 45     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 46     .                                                  ╎0   edit       copy       to recipe    delete    .
- 47     .                                                  ╎add 2, 2                                         .
- 48     .                                                  ╎4                                                .
- 49     .                                                  ╎─────────────────────────────────────────────────.
- 50     .                                                  ╎                                                 .
- 51     .                                                  ╎                                                 .
- 52   ]
- 53   # delete first sandbox by clicking at right edge of 'delete' button
- 54   assume-console [
- 55     left-click 3, 99
- 56   ]
- 57   run [
- 58     event-loop screen, console, env, resources
- 59   ]
- 60   screen-should-contain [
- 61     .                                                                                 run (F4)           .
- 62     .                                                  ╎                                                 .
- 63     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 64     .                                                  ╎                                                 .
- 65     .                                                  ╎                                                 .
- 66   ]
- 67 ]
- 68 
- 69 after <global-touch> [
- 70   # support 'delete' button
- 71   {
- 72     delete?:bool <- should-attempt-delete? click-row, click-column, env
- 73     break-unless delete?
- 74     delete?, env <- try-delete-sandbox click-row, env
- 75     break-unless delete?
- 76     screen <- render-sandbox-side screen, env, render
- 77     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
- 78     loop +next-event
- 79   }
- 80 ]
- 81 
- 82 # some preconditions for attempting to delete a sandbox
- 83 def should-attempt-delete? click-row:num, click-column:num, env:&:environment -> result:bool [
- 84   local-scope
- 85   load-inputs
- 86   # are we below the sandbox editor?
- 87   click-sandbox-area?:bool <- click-on-sandbox-area? click-row, click-column, env
- 88   return-unless click-sandbox-area?, false
- 89   # narrower, is the click in the columns spanning the 'copy' button?
- 90   first-sandbox:&:editor <- get *env, current-sandbox:offset
- 91   assert first-sandbox, [!!]
- 92   sandbox-left-margin:num <- get *first-sandbox, left:offset
- 93   sandbox-right-margin:num <- get *first-sandbox, right:offset
- 94   _, _, _, _, _, _, delete-button-left:num <- sandbox-menu-columns sandbox-left-margin, sandbox-right-margin
- 95   result <- within-range? click-column, delete-button-left, sandbox-right-margin
- 96 ]
- 97 
- 98 def try-delete-sandbox click-row:num, env:&:environment -> clicked-on-delete-button?:bool, env:&:environment [
- 99   local-scope
-100   load-inputs
-101   # identify the sandbox to delete, if the click was actually on the 'delete' button
-102   sandbox:&:sandbox <- find-sandbox env, click-row
-103   return-unless sandbox, false
-104   clicked-on-delete-button? <- copy true
-105   env <- delete-sandbox env, sandbox
-106 ]
-107 
-108 def delete-sandbox env:&:environment, sandbox:&:sandbox -> env:&:environment [
-109   local-scope
-110   load-inputs
-111   curr-sandbox:&:sandbox <- get *env, sandbox:offset
-112   first-sandbox?:bool <- equal curr-sandbox, sandbox
-113   {
-114     # first sandbox? pop
-115     break-unless first-sandbox?
-116     next-sandbox:&:sandbox <- get *curr-sandbox, next-sandbox:offset
-117     *env <- put *env, sandbox:offset, next-sandbox
-118   }
-119   {
-120     # not first sandbox?
-121     break-if first-sandbox?
-122     prev-sandbox:&:sandbox <- copy curr-sandbox
-123     curr-sandbox <- get *curr-sandbox, next-sandbox:offset
-124     {
-125       assert curr-sandbox, [sandbox not found! something is wrong.]
-126       found?:bool <- equal curr-sandbox, sandbox
-127       break-if found?
-128       prev-sandbox <- copy curr-sandbox
-129       curr-sandbox <- get *curr-sandbox, next-sandbox:offset
-130       loop
-131     }
-132     # snip sandbox out of its list
-133     next-sandbox:&:sandbox <- get *curr-sandbox, next-sandbox:offset
-134     *prev-sandbox <- put *prev-sandbox, next-sandbox:offset, next-sandbox
-135   }
-136   # update sandbox count
-137   sandbox-count:num <- get *env, number-of-sandboxes:offset
-138   sandbox-count <- subtract sandbox-count, 1
-139   *env <- put *env, number-of-sandboxes:offset, sandbox-count
-140   # reset scroll if deleted sandbox was last
-141   {
-142     break-if next-sandbox
-143     render-from:num <- get *env, render-from:offset
-144     reset-scroll?:bool <- equal render-from, sandbox-count
-145     break-unless reset-scroll?
-146     *env <- put *env, render-from:offset, -1
-147   }
-148 ]
-149 
-150 scenario deleting-sandbox-after-scroll [
-151   local-scope
-152   trace-until 100/app  # trace too long
-153   assume-screen 100/width, 10/height
-154   # initialize environment
-155   assume-resources [
-156   ]
-157   env:&:environment <- new-programming-environment resources, screen, []
-158   render-all screen, env, render
-159   # create 2 sandboxes and scroll to second
-160   assume-console [
-161     press ctrl-n
-162     type [add 2, 2]
-163     press F4
-164     type [add 1, 1]
-165     press F4
-166     press page-down
-167   ]
-168   event-loop screen, console, env, resources
-169   screen-should-contain [
-170     .                                                                                 run (F4)           .
-171     .                                                  ╎─────────────────────────────────────────────────.
-172     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎0   edit       copy       to recipe    delete    .
-173     .                                                  ╎add 1, 1                                         .
-174     .                                                  ╎2                                                .
-175     .                                                  ╎─────────────────────────────────────────────────.
-176     .                                                  ╎1   edit       copy       to recipe    delete    .
-177   ]
-178   # delete the second sandbox
-179   assume-console [
-180     left-click 6, 99
-181   ]
-182   run [
-183     event-loop screen, console, env, resources
-184   ]
-185   # second sandbox shows in editor; scroll resets to display first sandbox
-186   screen-should-contain [
-187     .                                                                                 run (F4)           .
-188     .                                                  ╎─────────────────────────────────────────────────.
-189     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎0   edit       copy       to recipe    delete    .
-190     .                                                  ╎add 1, 1                                         .
-191     .                                                  ╎2                                                .
-192     .                                                  ╎─────────────────────────────────────────────────.
-193     .                                                  ╎                                                 .
-194   ]
-195 ]
-196 
-197 scenario deleting-top-sandbox-after-scroll [
-198   local-scope
-199   trace-until 100/app  # trace too long
-200   assume-screen 100/width, 10/height
-201   # initialize environment
-202   assume-resources [
-203   ]
-204   env:&:environment <- new-programming-environment resources, screen, []
-205   render-all screen, env, render
-206   # create 2 sandboxes and scroll to second
-207   assume-console [
-208     press ctrl-n
-209     type [add 2, 2]
-210     press F4
-211     type [add 1, 1]
-212     press F4
-213     press page-down
-214   ]
-215   event-loop screen, console, env, resources
-216   screen-should-contain [
-217     .                                                                                 run (F4)           .
-218     .                                                  ╎─────────────────────────────────────────────────.
-219     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎0   edit       copy       to recipe    delete    .
-220     .                                                  ╎add 1, 1                                         .
-221     .                                                  ╎2                                                .
-222     .                                                  ╎─────────────────────────────────────────────────.
-223     .                                                  ╎1   edit       copy       to recipe    delete    .
-224   ]
-225   # delete the second sandbox
-226   assume-console [
-227     left-click 2, 99
-228   ]
-229   run [
-230     event-loop screen, console, env, resources
-231   ]
-232   # second sandbox shows in editor; scroll resets to display first sandbox
-233   screen-should-contain [
-234     .                                                                                 run (F4)           .
-235     .                                                  ╎─────────────────────────────────────────────────.
-236     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎0   edit       copy       to recipe    delete    .
-237     .                                                  ╎add 2, 2                                         .
-238     .                                                  ╎4                                                .
-239     .                                                  ╎─────────────────────────────────────────────────.
-240     .                                                  ╎                                                 .
-241   ]
-242 ]
-243 
-244 scenario deleting-final-sandbox-after-scroll [
-245   local-scope
-246   trace-until 100/app  # trace too long
-247   assume-screen 100/width, 10/height
-248   # initialize environment
-249   assume-resources [
-250   ]
-251   env:&:environment <- new-programming-environment resources, screen, []
-252   render-all screen, env, render
-253   # create 2 sandboxes and scroll to second
-254   assume-console [
-255     press ctrl-n
-256     type [add 2, 2]
-257     press F4
-258     type [add 1, 1]
-259     press F4
-260     press page-down
-261     press page-down
-262   ]
-263   event-loop screen, console, env, resources
-264   screen-should-contain [
-265     .                                                                                 run (F4)           .
-266     .                                                  ╎─────────────────────────────────────────────────.
-267     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎1   edit       copy       to recipe    delete    .
-268     .                                                  ╎add 2, 2                                         .
-269     .                                                  ╎4                                                .
-270     .                                                  ╎─────────────────────────────────────────────────.
-271     .                                                  ╎                                                 .
-272   ]
-273   # delete the second sandbox
-274   assume-console [
-275     left-click 2, 99
-276   ]
-277   run [
-278     event-loop screen, console, env, resources
-279   ]
-280   # implicitly scroll up to first sandbox
-281   screen-should-contain [
-282     .                                                                                 run (F4)           .
-283     .                                                  ╎                                                 .
-284     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-285     .                                                  ╎0   edit       copy       to recipe    delete    .
-286     .                                                  ╎add 1, 1                                         .
-287     .                                                  ╎2                                                .
-288     .                                                  ╎─────────────────────────────────────────────────.
-289     .                                                  ╎                                                 .
-290   ]
-291 ]
-292 
-293 scenario deleting-updates-sandbox-count [
-294   local-scope
-295   trace-until 100/app  # trace too long
-296   assume-screen 100/width, 10/height
-297   # initialize environment
-298   assume-resources [
-299   ]
-300   env:&:environment <- new-programming-environment resources, screen, []
-301   render-all screen, env, render
-302   # create 2 sandboxes
-303   assume-console [
-304     press ctrl-n
-305     type [add 2, 2]
-306     press F4
-307     type [add 1, 1]
-308     press F4
-309   ]
-310   event-loop screen, console, env, resources
-311   screen-should-contain [
-312     .                                                                                 run (F4)           .
-313     .                                                  ╎                                                 .
-314     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-315     .                                                  ╎0   edit       copy       to recipe    delete    .
-316     .                                                  ╎add 1, 1                                         .
-317     .                                                  ╎2                                                .
-318     .                                                  ╎─────────────────────────────────────────────────.
-319     .                                                  ╎1   edit       copy       to recipe    delete    .
-320     .                                                  ╎add 2, 2                                         .
-321     .                                                  ╎4                                                .
-322   ]
-323   # delete the second sandbox, then try to scroll down twice
-324   assume-console [
-325     left-click 3, 99
-326     press page-down
-327     press page-down
-328   ]
-329   run [
-330     event-loop screen, console, env, resources
-331   ]
-332   # shouldn't go past last sandbox
-333   screen-should-contain [
-334     .                                                                                 run (F4)           .
-335     .                                                  ╎─────────────────────────────────────────────────.
-336     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎0   edit       copy       to recipe    delete    .
-337     .                                                  ╎add 2, 2                                         .
-338     .                                                  ╎4                                                .
-339     .                                                  ╎─────────────────────────────────────────────────.
-340     .                                                  ╎                                                 .
-341   ]
-342 ]
-
- - - diff --git a/html/edit/008-sandbox-edit.mu.html b/html/edit/008-sandbox-edit.mu.html deleted file mode 100644 index ab672c78..00000000 --- a/html/edit/008-sandbox-edit.mu.html +++ /dev/null @@ -1,389 +0,0 @@ - - - - -Mu - edit/008-sandbox-edit.mu - - - - - - - - - - -https://github.com/akkartik/mu/blob/master/edit/008-sandbox-edit.mu -
-  1 ## editing sandboxes after they've been created
-  2 
-  3 scenario clicking-on-sandbox-edit-button-moves-it-to-editor [
-  4   local-scope
-  5   trace-until 100/app  # trace too long
-  6   assume-screen 100/width, 10/height
-  7   # empty recipes
-  8   assume-resources [
-  9   ]
- 10   env:&:environment <- new-programming-environment resources, screen, [add 2, 2]
- 11   render-all screen, env, render
- 12   # run it
- 13   assume-console [
- 14     press F4
- 15   ]
- 16   event-loop screen, console, env, resources
- 17   screen-should-contain [
- 18     .                                                                                 run (F4)           .
- 19     .                                                  ╎                                                 .
- 20     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 21     .                                                  ╎0   edit       copy       to recipe    delete    .
- 22     .                                                  ╎add 2, 2                                         .
- 23     .                                                  ╎4                                                .
- 24     .                                                  ╎─────────────────────────────────────────────────.
- 25     .                                                  ╎                                                 .
- 26   ]
- 27   # click at left edge of 'edit' button
- 28   assume-console [
- 29     left-click 3, 55
- 30   ]
- 31   run [
- 32     event-loop screen, console, env, resources
- 33   ]
- 34   # it pops back into editor
- 35   screen-should-contain [
- 36     .                                                                                 run (F4)           .
- 37     .                                                  ╎add 2, 2                                         .
- 38     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 39     .                                                  ╎                                                 .
- 40   ]
- 41   # cursor should be in the right place
- 42   assume-console [
- 43     type [0]
- 44   ]
- 45   run [
- 46     event-loop screen, console, env, resources
- 47   ]
- 48   screen-should-contain [
- 49     .                                                                                 run (F4)           .
- 50     .                                                  ╎0add 2, 2                                        .
- 51     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 52     .                                                  ╎                                                 .
- 53   ]
- 54 ]
- 55 
- 56 scenario clicking-on-sandbox-edit-button-moves-it-to-editor-2 [
- 57   local-scope
- 58   trace-until 100/app  # trace too long
- 59   assume-screen 100/width, 10/height
- 60   # empty recipes
- 61   assume-resources [
- 62   ]
- 63   env:&:environment <- new-programming-environment resources, screen, [add 2, 2]
- 64   render-all screen, env, render
- 65   # run it
- 66   assume-console [
- 67     press F4
- 68   ]
- 69   event-loop screen, console, env, resources
- 70   screen-should-contain [
- 71     .                                                                                 run (F4)           .
- 72     .                                                  ╎                                                 .
- 73     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 74     .                                                  ╎0   edit       copy       to recipe    delete    .
- 75     .                                                  ╎add 2, 2                                         .
- 76     .                                                  ╎4                                                .
- 77     .                                                  ╎─────────────────────────────────────────────────.
- 78     .                                                  ╎                                                 .
- 79   ]
- 80   # click at right edge of 'edit' button (just before 'copy')
- 81   assume-console [
- 82     left-click 3, 65
- 83   ]
- 84   run [
- 85     event-loop screen, console, env, resources
- 86   ]
- 87   # it pops back into editor
- 88   screen-should-contain [
- 89     .                                                                                 run (F4)           .
- 90     .                                                  ╎add 2, 2                                         .
- 91     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 92     .                                                  ╎                                                 .
- 93   ]
- 94   # cursor should be in the right place
- 95   assume-console [
- 96     type [0]
- 97   ]
- 98   run [
- 99     event-loop screen, console, env, resources
-100   ]
-101   screen-should-contain [
-102     .                                                                                 run (F4)           .
-103     .                                                  ╎0add 2, 2                                        .
-104     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-105     .                                                  ╎                                                 .
-106   ]
-107 ]
-108 
-109 after <global-touch> [
-110   # support 'edit' button
-111   {
-112     edit?:bool <- should-attempt-edit? click-row, click-column, env
-113     break-unless edit?
-114     edit?, env <- try-edit-sandbox click-row, env
-115     break-unless edit?
-116     screen <- render-sandbox-side screen, env, render
-117     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
-118     loop +next-event
-119   }
-120 ]
-121 
-122 # some preconditions for attempting to edit a sandbox
-123 def should-attempt-edit? click-row:num, click-column:num, env:&:environment -> result:bool [
-124   local-scope
-125   load-inputs
-126   # are we below the sandbox editor?
-127   click-sandbox-area?:bool <- click-on-sandbox-area? click-row, click-column, env
-128   return-unless click-sandbox-area?, false
-129   # narrower, is the click in the columns spanning the 'edit' button?
-130   first-sandbox:&:editor <- get *env, current-sandbox:offset
-131   assert first-sandbox, [!!]
-132   sandbox-left-margin:num <- get *first-sandbox, left:offset
-133   sandbox-right-margin:num <- get *first-sandbox, right:offset
-134   edit-button-left:num, edit-button-right:num, _ <- sandbox-menu-columns sandbox-left-margin, sandbox-right-margin
-135   edit-button-vertical-area?:bool <- within-range? click-column, edit-button-left, edit-button-right
-136   return-unless edit-button-vertical-area?, false
-137   # finally, is sandbox editor empty?
-138   current-sandbox:&:editor <- get *env, current-sandbox:offset
-139   result <- empty-editor? current-sandbox
-140 ]
-141 
-142 def try-edit-sandbox click-row:num, env:&:environment -> clicked-on-edit-button?:bool, env:&:environment [
-143   local-scope
-144   load-inputs
-145   # identify the sandbox to edit, if the click was actually on the 'edit' button
-146   sandbox:&:sandbox <- find-sandbox env, click-row
-147   return-unless sandbox, false
-148   clicked-on-edit-button? <- copy true
-149   # 'edit' button = 'copy' button + 'delete' button
-150   text:text <- get *sandbox, data:offset
-151   current-sandbox:&:editor <- get *env, current-sandbox:offset
-152   current-sandbox <- insert-text current-sandbox, text
-153   env <- delete-sandbox env, sandbox
-154   # reset scroll
-155   *env <- put *env, render-from:offset, -1
-156   # position cursor in sandbox editor
-157   *env <- put *env, sandbox-in-focus?:offset, true
-158 ]
-159 
-160 scenario sandbox-with-print-can-be-edited [
-161   local-scope
-162   trace-until 100/app  # trace too long
-163   assume-screen 100/width, 20/height
-164   # left editor is empty
-165   assume-resources [
-166   ]
-167   # right editor contains a print instruction
-168   env:&:environment <- new-programming-environment resources, screen, [print screen, 4]
-169   render-all screen, env, render
-170   # run the sandbox
-171   assume-console [
-172     press F4
-173   ]
-174   event-loop screen, console, env, resources
-175   screen-should-contain [
-176     .                                                                                 run (F4)           .
-177     .                                                  ╎                                                 .
-178     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-179     .                                                  ╎0   edit       copy       to recipe    delete    .
-180     .                                                  ╎print screen, 4                                  .
-181     .                                                  ╎screen:                                          .
-182     .                                                  ╎  .4                             .               .
-183     .                                                  ╎  .                              .               .
-184     .                                                  ╎  .                              .               .
-185     .                                                  ╎  .                              .               .
-186     .                                                  ╎  .                              .               .
-187     .                                                  ╎─────────────────────────────────────────────────.
-188     .                                                  ╎                                                 .
-189   ]
-190   # edit the sandbox
-191   assume-console [
-192     left-click 3, 65
-193   ]
-194   run [
-195     event-loop screen, console, env, resources
-196   ]
-197   screen-should-contain [
-198     .                                                                                 run (F4)           .
-199     .                                                  ╎print screen, 4                                  .
-200     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-201     .                                                  ╎                                                 .
-202     .                                                  ╎                                                 .
-203   ]
-204 ]
-205 
-206 scenario editing-sandbox-after-scrolling-resets-scroll [
-207   local-scope
-208   trace-until 100/app  # trace too long
-209   assume-screen 100/width, 10/height
-210   # initialize environment
-211   assume-resources [
-212   ]
-213   env:&:environment <- new-programming-environment resources, screen, []
-214   render-all screen, env, render
-215   # create 2 sandboxes and scroll to second
-216   assume-console [
-217     press ctrl-n
-218     type [add 2, 2]
-219     press F4
-220     type [add 1, 1]
-221     press F4
-222     press page-down
-223     press page-down
-224   ]
-225   event-loop screen, console, env, resources
-226   screen-should-contain [
-227     .                                                                                 run (F4)           .
-228     .                                                  ╎─────────────────────────────────────────────────.
-229     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎1   edit       copy       to recipe    delete    .
-230     .                                                  ╎add 2, 2                                         .
-231     .                                                  ╎4                                                .
-232     .                                                  ╎─────────────────────────────────────────────────.
-233     .                                                  ╎                                                 .
-234   ]
-235   # edit the second sandbox
-236   assume-console [
-237     left-click 2, 55
-238   ]
-239   run [
-240     event-loop screen, console, env, resources
-241   ]
-242   # second sandbox shows in editor; scroll resets to display first sandbox
-243   screen-should-contain [
-244     .                                                                                 run (F4)           .
-245     .                                                  ╎add 2, 2                                         .
-246     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-247     .                                                  ╎0   edit       copy       to recipe    delete    .
-248     .                                                  ╎add 1, 1                                         .
-249     .                                                  ╎2                                                .
-250     .                                                  ╎─────────────────────────────────────────────────.
-251     .                                                  ╎                                                 .
-252   ]
-253 ]
-254 
-255 scenario editing-sandbox-updates-sandbox-count [
-256   local-scope
-257   trace-until 100/app  # trace too long
-258   assume-screen 100/width, 10/height
-259   # initialize environment
-260   assume-resources [
-261   ]
-262   env:&:environment <- new-programming-environment resources, screen, []
-263   render-all screen, env, render
-264   # create 2 sandboxes
-265   assume-console [
-266     press ctrl-n
-267     type [add 2, 2]
-268     press F4
-269     type [add 1, 1]
-270     press F4
-271   ]
-272   event-loop screen, console, env, resources
-273   screen-should-contain [
-274     .                                                                                 run (F4)           .
-275     .                                                  ╎                                                 .
-276     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-277     .                                                  ╎0   edit       copy       to recipe    delete    .
-278     .                                                  ╎add 1, 1                                         .
-279     .                                                  ╎2                                                .
-280     .                                                  ╎─────────────────────────────────────────────────.
-281     .                                                  ╎1   edit       copy       to recipe    delete    .
-282     .                                                  ╎add 2, 2                                         .
-283     .                                                  ╎4                                                .
-284   ]
-285   # edit the second sandbox, then resave
-286   assume-console [
-287     left-click 3, 60
-288     press F4
-289   ]
-290   run [
-291     event-loop screen, console, env, resources
-292   ]
-293   # no change in contents
-294   screen-should-contain [
-295     .                                                                                 run (F4)           .
-296     .                                                  ╎                                                 .
-297     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-298     .                                                  ╎0   edit       copy       to recipe    delete    .
-299     .                                                  ╎add 1, 1                                         .
-300     .                                                  ╎2                                                .
-301     .                                                  ╎─────────────────────────────────────────────────.
-302     .                                                  ╎1   edit       copy       to recipe    delete    .
-303     .                                                  ╎add 2, 2                                         .
-304     .                                                  ╎4                                                .
-305   ]
-306   # now try to scroll past end
-307   assume-console [
-308     press page-down
-309     press page-down
-310     press page-down
-311   ]
-312   run [
-313     event-loop screen, console, env, resources
-314   ]
-315   # screen should show just final sandbox with the right index (1)
-316   screen-should-contain [
-317     .                                                                                 run (F4)           .
-318     .                                                  ╎─────────────────────────────────────────────────.
-319     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎1   edit       copy       to recipe    delete    .
-320     .                                                  ╎add 2, 2                                         .
-321     .                                                  ╎4                                                .
-322     .                                                  ╎─────────────────────────────────────────────────.
-323     .                                                  ╎                                                 .
-324   ]
-325 ]
-
- - - diff --git a/html/edit/009-sandbox-test.mu.html b/html/edit/009-sandbox-test.mu.html deleted file mode 100644 index 199d554f..00000000 --- a/html/edit/009-sandbox-test.mu.html +++ /dev/null @@ -1,296 +0,0 @@ - - - - -Mu - edit/009-sandbox-test.mu - - - - - - - - - - -https://github.com/akkartik/mu/blob/master/edit/009-sandbox-test.mu -
-  1 ## clicking on sandbox results to 'fix' them and turn sandboxes into tests
-  2 
-  3 scenario sandbox-click-on-result-toggles-color-to-green [
-  4   local-scope
-  5   trace-until 100/app  # trace too long
-  6   assume-screen 100/width, 20/height
-  7   # basic recipe
-  8   assume-resources [
-  9     [lesson/recipes.mu] <- [
- 10       |recipe foo [|
- 11       |  reply 4|
- 12       |]|
- 13     ]
- 14   ]
- 15   env:&:environment <- new-programming-environment resources, screen, [foo]
- 16   render-all screen, env, render
- 17   # run it
- 18   assume-console [
- 19     press F4
- 20   ]
- 21   event-loop screen, console, env, resources
- 22   screen-should-contain [
- 23     .                                                                                 run (F4)           .
- 24     .recipe foo [                                      ╎                                                 .
- 25     .  reply 4                                         ╎─────────────────────────────────────────────────.
- 26     .]                                                 ╎0   edit       copy       to recipe    delete    .
- 27     .                                                  ╎foo                                              .
- 28     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎4                                                .
- 29     .                                                  ╎─────────────────────────────────────────────────.
- 30     .                                                  ╎                                                 .
- 31   ]
- 32   # click on the '4' in the result
- 33   $clear-trace
- 34   assume-console [
- 35     left-click 5, 51
- 36   ]
- 37   run [
- 38     event-loop screen, console, env, resources
- 39   ]
- 40   # color toggles to green
- 41   screen-should-contain-in-color 2/green, [
- 42     .                                                                                                    .
- 43     .                                                                                                    .
- 44     .                                                                                                    .
- 45     .                                                                                                    .
- 46     .                                                                                                    .
- 47     .                                                   4                                                .
- 48     .                                                                                                    .
- 49     .                                                                                                    .
- 50   ]
- 51   # don't render entire sandbox side
- 52   check-trace-count-for-label-lesser-than 250, [print-character]  # say 5 sandbox lines
- 53   # cursor should remain unmoved
- 54   run [
- 55     cursor:char <- copy 9251/␣
- 56     print screen, cursor
- 57   ]
- 58   screen-should-contain [
- 59     .                                                                                 run (F4)           .
- 60     .␣ecipe foo [                                      ╎                                                 .
- 61     .  reply 4                                         ╎─────────────────────────────────────────────────.
- 62     .]                                                 ╎0   edit       copy       to recipe    delete    .
- 63     .                                                  ╎foo                                              .
- 64     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎4                                                .
- 65     .                                                  ╎─────────────────────────────────────────────────.
- 66     .                                                  ╎                                                 .
- 67   ]
- 68   # now change the result
- 69   # then rerun
- 70   assume-console [
- 71     left-click 2, 11  # cursor to end of line
- 72     press backspace
- 73     type [3]
- 74     press F4
- 75   ]
- 76   run [
- 77     event-loop screen, console, env, resources
- 78   ]
- 79   # result turns red
- 80   screen-should-contain-in-color 1/red, [
- 81     .                                                                                                    .
- 82     .                                                                                                    .
- 83     .                                                                                                    .
- 84     .                                                                                                    .
- 85     .                                                                                                    .
- 86     .                                                   3                                                .
- 87     .                                                                                                    .
- 88     .                                                                                                    .
- 89   ]
- 90 ]
- 91 
- 92 # this requires tracking a couple more things
- 93 container sandbox [
- 94   response-starting-row-on-screen:num
- 95   expected-response:text
- 96 ]
- 97 
- 98 # include expected response when saving or restoring a sandbox
- 99 before <end-save-sandbox> [
-100   {
-101     expected-response:text <- get *sandbox, expected-response:offset
-102     break-unless expected-response
-103     filename <- append filename, [.out]
-104     resources <- dump resources, filename, expected-response
-105   }
-106 ]
-107 
-108 before <end-restore-sandbox> [
-109   {
-110     filename <- append filename, [.out]
-111     contents <- slurp resources, filename
-112     break-unless contents
-113     *curr <- put *curr, expected-response:offset, contents
-114   }
-115 ]
-116 
-117 # clicks on sandbox responses save it as 'expected'
-118 after <global-touch> [
-119   # check if it's inside the output of any sandbox
-120   {
-121     sandbox-left-margin:num <- get *current-sandbox, left:offset
-122     click-column:num <- get t, column:offset
-123     on-sandbox-side?:bool <- greater-or-equal click-column, sandbox-left-margin
-124     break-unless on-sandbox-side?
-125     first-sandbox:&:sandbox <- get *env, sandbox:offset
-126     break-unless first-sandbox
-127     first-sandbox-begins:num <- get *first-sandbox, starting-row-on-screen:offset
-128     click-row:num <- get t, row:offset
-129     below-sandbox-editor?:bool <- greater-or-equal click-row, first-sandbox-begins
-130     break-unless below-sandbox-editor?
-131     # identify the sandbox whose output is being clicked on
-132     sandbox:&:sandbox, sandbox-index:num <- find-click-in-sandbox-output env, click-row
-133     break-unless sandbox
-134     # update it
-135     sandbox <- toggle-expected-response sandbox
-136     # minimal update to disk
-137     save-sandbox resources, sandbox, sandbox-index
-138     # minimal update to screen
-139     sandbox-right-margin:num <- get *current-sandbox, right:offset
-140     row:num <- render-sandbox-response screen, sandbox, sandbox-left-margin, sandbox-right-margin
-141     {
-142       height:num <- screen-height screen
-143       at-bottom?:bool <- greater-or-equal row, height
-144       break-if at-bottom?
-145       draw-horizontal screen, row, sandbox-left-margin, sandbox-right-margin
-146     }
-147     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
-148     loop +next-event
-149   }
-150 ]
-151 
-152 def find-click-in-sandbox-output env:&:environment, click-row:num -> sandbox:&:sandbox, sandbox-index:num [
-153   local-scope
-154   load-inputs
-155   # assert click-row >= sandbox.starting-row-on-screen
-156   sandbox:&:sandbox <- get *env, sandbox:offset
-157   start:num <- get *sandbox, starting-row-on-screen:offset
-158   clicked-on-sandboxes?:bool <- greater-or-equal click-row, start
-159   assert clicked-on-sandboxes?, [extract-sandbox called on click to sandbox editor]
-160   # while click-row < sandbox.next-sandbox.starting-row-on-screen
-161   sandbox-index <- copy 0
-162   {
-163     next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset
-164     break-unless next-sandbox
-165     next-start:num <- get *next-sandbox, starting-row-on-screen:offset
-166     found?:bool <- lesser-than click-row, next-start
-167     break-if found?
-168     sandbox <- copy next-sandbox
-169     sandbox-index <- add sandbox-index, 1
-170     loop
-171   }
-172   # return sandbox if click is in its output region
-173   response-starting-row:num <- get *sandbox, response-starting-row-on-screen:offset
-174   return-unless response-starting-row, null/no-click-in-sandbox-output, 0/sandbox-index
-175   click-in-response?:bool <- greater-or-equal click-row, response-starting-row
-176   return-unless click-in-response?, null/no-click-in-sandbox-output, 0/sandbox-index
-177   return sandbox, sandbox-index
-178 ]
-179 
-180 def toggle-expected-response sandbox:&:sandbox -> sandbox:&:sandbox [
-181   local-scope
-182   load-inputs
-183   expected-response:text <- get *sandbox, expected-response:offset
-184   {
-185     # if expected-response is set, reset
-186     break-unless expected-response
-187     *sandbox <- put *sandbox, expected-response:offset, null
-188   }
-189   {
-190     # if not, set expected response to the current response
-191     break-if expected-response
-192     response:text <- get *sandbox, response:offset
-193     *sandbox <- put *sandbox, expected-response:offset, response
-194   }
-195 ]
-196 
-197 # when rendering a sandbox, color it in red/green if expected response exists
-198 after <render-sandbox-response> [
-199   {
-200     break-unless sandbox-response
-201     *sandbox <- put *sandbox, response-starting-row-on-screen:offset, row
-202     row <- render-sandbox-response screen, sandbox, left, right
-203     jump +render-sandbox-end
-204   }
-205 ]
-206 
-207 def render-sandbox-response screen:&:screen, sandbox:&:sandbox, left:num, right:num -> row:num, screen:&:screen [
-208   local-scope
-209   load-inputs
-210   sandbox-response:text <- get *sandbox, response:offset
-211   expected-response:text <- get *sandbox, expected-response:offset
-212   row:num <- get *sandbox response-starting-row-on-screen:offset
-213   {
-214     break-if expected-response
-215     row <- render-text screen, sandbox-response, left, right, 245/grey, row
-216     return
-217   }
-218   response-is-expected?:bool <- equal expected-response, sandbox-response
-219   {
-220     break-if response-is-expected?
-221     row <- render-text screen, sandbox-response, left, right, 1/red, row
-222   }
-223   {
-224     break-unless response-is-expected?:bool
-225     row <- render-text screen, sandbox-response, left, right, 2/green, row
-226   }
-227 ]
-228 
-229 before <end-render-sandbox-reset-hidden> [
-230   *sandbox <- put *sandbox, response-starting-row-on-screen:offset, 0
-231 ]
-
- - - diff --git a/html/edit/010-sandbox-trace.mu.html b/html/edit/010-sandbox-trace.mu.html deleted file mode 100644 index 62144117..00000000 --- a/html/edit/010-sandbox-trace.mu.html +++ /dev/null @@ -1,318 +0,0 @@ - - - - -Mu - edit/010-sandbox-trace.mu - - - - - - - - - - -https://github.com/akkartik/mu/blob/master/edit/010-sandbox-trace.mu -
-  1 ## clicking on the code typed into a sandbox toggles its trace
-  2 
-  3 scenario sandbox-click-on-code-toggles-app-trace [
-  4   local-scope
-  5   trace-until 100/app  # trace too long
-  6   assume-screen 100/width, 10/height
-  7   # basic recipe
-  8   assume-resources [
-  9     [lesson/recipes.mu] <- [
- 10       |recipe foo [|
- 11       |  stash [abc]|
- 12       |]|
- 13     ]
- 14   ]
- 15   env:&:environment <- new-programming-environment resources, screen, [foo]
- 16   render-all screen, env, render
- 17   # run it
- 18   assume-console [
- 19     press F4
- 20   ]
- 21   event-loop screen, console, env, resources
- 22   screen-should-contain [
- 23     .                                                                                 run (F4)           .
- 24     .recipe foo [                                      ╎                                                 .
- 25     .  stash [abc]                                     ╎─────────────────────────────────────────────────.
- 26     .]                                                 ╎0   edit       copy       to recipe    delete    .
- 27     .                                                  ╎foo                                              .
- 28     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 29     .                                                  ╎                                                 .
- 30   ]
- 31   # click on the code in the sandbox
- 32   assume-console [
- 33     left-click 4, 51
- 34   ]
- 35   run [
- 36     event-loop screen, console, env, resources
- 37     cursor:char <- copy 9251/␣
- 38     print screen, cursor
- 39   ]
- 40   # trace now printed and cursor shouldn't have budged
- 41   screen-should-contain [
- 42     .                                                                                 run (F4)           .
- 43     .␣ecipe foo [                                      ╎                                                 .
- 44     .  stash [abc]                                     ╎─────────────────────────────────────────────────.
- 45     .]                                                 ╎0   edit       copy       to recipe    delete    .
- 46     .                                                  ╎foo                                              .
- 47     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎abc                                              .
- 48   ]
- 49   screen-should-contain-in-color 245/grey, [
- 50     .                                                                                                    .
- 51     .                                                  ╎                                                 .
- 52     .                                                  ╎─────────────────────────────────────────────────.
- 53     .                                                  ╎                                                 .
- 54     .                                                  ╎                                                 .
- 55     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎abc                                              .
- 56   ]
- 57   # click again on the same region
- 58   assume-console [
- 59     left-click 4, 55
- 60   ]
- 61   run [
- 62     event-loop screen, console, env, resources
- 63     print screen, cursor
- 64   ]
- 65   # trace hidden again
- 66   screen-should-contain [
- 67     .                                                                                 run (F4)           .
- 68     .␣ecipe foo [                                      ╎                                                 .
- 69     .  stash [abc]                                     ╎─────────────────────────────────────────────────.
- 70     .]                                                 ╎0   edit       copy       to recipe    delete    .
- 71     .                                                  ╎foo                                              .
- 72     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
- 73     .                                                  ╎                                                 .
- 74   ]
- 75 ]
- 76 
- 77 scenario sandbox-shows-app-trace-and-result [
- 78   local-scope
- 79   trace-until 100/app  # trace too long
- 80   assume-screen 100/width, 10/height
- 81   # basic recipe
- 82   assume-resources [
- 83     [lesson/recipes.mu] <- [
- 84       |recipe foo [|
- 85       |  stash [abc]|
- 86       |  reply 4|
- 87       |]|
- 88     ]
- 89   ]
- 90   env:&:environment <- new-programming-environment resources, screen, [foo]
- 91   render-all screen, env, render
- 92   # run it
- 93   assume-console [
- 94     press F4
- 95   ]
- 96   event-loop screen, console, env, resources
- 97   screen-should-contain [
- 98     .                                                                                 run (F4)           .
- 99     .recipe foo [                                      ╎                                                 .
-100     .  stash [abc]                                     ╎─────────────────────────────────────────────────.
-101     .  reply 4                                         ╎0   edit       copy       to recipe    delete    .
-102     .]                                                 ╎foo                                              .
-103     .                                                  ╎4                                                .
-104     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-105     .                                                  ╎                                                 .
-106   ]
-107   # click on the code in the sandbox
-108   assume-console [
-109     left-click 4, 51
-110   ]
-111   run [
-112     event-loop screen, console, env, resources
-113   ]
-114   # trace now printed above result
-115   screen-should-contain [
-116     .                                                                                 run (F4)           .
-117     .recipe foo [                                      ╎                                                 .
-118     .  stash [abc]                                     ╎─────────────────────────────────────────────────.
-119     .  reply 4                                         ╎0   edit       copy       to recipe    delete    .
-120     .]                                                 ╎foo                                              .
-121     .                                                  ╎abc                                              .
-122     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎8 instructions run                               .
-123     .                                                  ╎4                                                .
-124     .                                                  ╎─────────────────────────────────────────────────.
-125     .                                                  ╎                                                 .
-126   ]
-127 ]
-128 
-129 scenario clicking-on-app-trace-does-nothing [
-130   local-scope
-131   trace-until 100/app  # trace too long
-132   assume-screen 100/width, 10/height
-133   assume-resources [
-134   ]
-135   env:&:environment <- new-programming-environment resources, screen, [stash 123456789]
-136   render-all screen, env, render
-137   # create and expand the trace
-138   assume-console [
-139     press F4
-140     left-click 4, 51
-141   ]
-142   event-loop screen, console, env, resources
-143   screen-should-contain [
-144     .                                                                                 run (F4)           .
-145     .                                                  ╎                                                 .
-146     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-147     .                                                  ╎0   edit       copy       to recipe    delete    .
-148     .                                                  ╎stash 123456789                                  .
-149     .                                                  ╎123456789                                        .
-150   ]
-151   # click on the stash under the edit-button region (or any of the other buttons, really)
-152   assume-console [
-153     left-click 5, 57
-154   ]
-155   run [
-156     event-loop screen, console, env, resources
-157   ]
-158   # no change; doesn't die
-159   screen-should-contain [
-160     .                                                                                 run (F4)           .
-161     .                                                  ╎                                                 .
-162     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-163     .                                                  ╎0   edit       copy       to recipe    delete    .
-164     .                                                  ╎stash 123456789                                  .
-165     .                                                  ╎123456789                                        .
-166   ]
-167 ]
-168 
-169 container sandbox [
-170   trace:text
-171   display-trace?:bool
-172 ]
-173 
-174 # replaced in a later layer
-175 def! update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [
-176   local-scope
-177   load-inputs
-178   data:text <- get *sandbox, data:offset
-179   response:text, _, fake-screen:&:screen, trace:text <- run-sandboxed data
-180   *sandbox <- put *sandbox, response:offset, response
-181   *sandbox <- put *sandbox, screen:offset, fake-screen
-182   *sandbox <- put *sandbox, trace:offset, trace
-183 ]
-184 
-185 # clicks on sandbox code toggle its display-trace? flag
-186 after <global-touch> [
-187   # check if it's inside the code of any sandbox
-188   {
-189     sandbox-left-margin:num <- get *current-sandbox, left:offset
-190     click-column:num <- get t, column:offset
-191     on-sandbox-side?:bool <- greater-or-equal click-column, sandbox-left-margin
-192     break-unless on-sandbox-side?
-193     first-sandbox:&:sandbox <- get *env, sandbox:offset
-194     break-unless first-sandbox
-195     first-sandbox-begins:num <- get *first-sandbox, starting-row-on-screen:offset
-196     click-row:num <- get t, row:offset
-197     below-sandbox-editor?:bool <- greater-or-equal click-row, first-sandbox-begins
-198     break-unless below-sandbox-editor?
-199     # identify the sandbox whose code is being clicked on
-200     sandbox:&:sandbox <- find-click-in-sandbox-code env, click-row
-201     break-unless sandbox
-202     # toggle its display-trace? property
-203     x:bool <- get *sandbox, display-trace?:offset
-204     x <- not x
-205     *sandbox <- put *sandbox, display-trace?:offset, x
-206     screen <- render-sandbox-side screen, env, render
-207     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
-208     loop +next-event
-209   }
-210 ]
-211 
-212 def find-click-in-sandbox-code env:&:environment, click-row:num -> sandbox:&:sandbox [
-213   local-scope
-214   load-inputs
-215   # assert click-row >= sandbox.starting-row-on-screen
-216   sandbox <- get *env, sandbox:offset
-217   start:num <- get *sandbox, starting-row-on-screen:offset
-218   clicked-on-sandboxes?:bool <- greater-or-equal click-row, start
-219   assert clicked-on-sandboxes?, [extract-sandbox called on click to sandbox editor]
-220   # while click-row < sandbox.next-sandbox.starting-row-on-screen
-221   {
-222     next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset
-223     break-unless next-sandbox
-224     next-start:num <- get *next-sandbox, starting-row-on-screen:offset
-225     found?:bool <- lesser-than click-row, next-start
-226     break-if found?
-227     sandbox <- copy next-sandbox
-228     loop
-229   }
-230   # return sandbox if click is in its code region
-231   code-ending-row:num <- get *sandbox, code-ending-row-on-screen:offset
-232   click-above-response?:bool <- lesser-than click-row, code-ending-row
-233   start:num <- get *sandbox, starting-row-on-screen:offset
-234   click-below-menu?:bool <- greater-than click-row, start
-235   click-on-sandbox-code?:bool <- and click-above-response?, click-below-menu?
-236   {
-237     break-if click-on-sandbox-code?
-238     return null/no-click-in-sandbox-output
-239   }
-240   return sandbox
-241 ]
-242 
-243 # when rendering a sandbox, dump its trace before response/warning if display-trace? property is set
-244 after <render-sandbox-results> [
-245   {
-246     display-trace?:bool <- get *sandbox, display-trace?:offset
-247     break-unless display-trace?
-248     sandbox-trace:text <- get *sandbox, trace:offset
-249     break-unless sandbox-trace  # nothing to print; move on
-250     row, screen <- render-text screen, sandbox-trace, left, right, 245/grey, row
-251   }
-252   <render-sandbox-trace-done>
-253 ]
-
- - - diff --git a/html/edit/011-errors.mu.html b/html/edit/011-errors.mu.html deleted file mode 100644 index d840f7f9..00000000 --- a/html/edit/011-errors.mu.html +++ /dev/null @@ -1,951 +0,0 @@ - - - - -Mu - edit/011-errors.mu - - - - - - - - - - -https://github.com/akkartik/mu/blob/master/edit/011-errors.mu -
-  1 ## handling malformed programs
-  2 
-  3 container environment [
-  4   recipe-errors:text
-  5 ]
-  6 
-  7 # copy code from recipe editor, persist to disk, load, save any errors
-  8 def! update-recipes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, resources:&:resources, screen:&:screen [
-  9   local-scope
- 10   load-inputs
- 11   recipes:&:editor <- get *env, recipes:offset
- 12   in:text <- editor-contents recipes
- 13   resources <- dump resources, [lesson/recipes.mu], in
- 14   recipe-errors:text <- reload in
- 15   *env <- put *env, recipe-errors:offset, recipe-errors
- 16   # if recipe editor has errors, stop
- 17   {
- 18     break-unless recipe-errors
- 19     update-status screen, [errors found     ], 1/red
- 20     errors-found? <- copy true
- 21     return
- 22   }
- 23   errors-found? <- copy false
- 24 ]
- 25 
- 26 after <begin-run-sandboxes-on-F4> [
- 27   old-recipe-errors:text <- get *env, recipe-errors:offset
- 28 ]
- 29 before <end-run-sandboxes-on-F4> [
- 30   # if there were recipe errors before, check if we can clear them
- 31   {
- 32     break-unless old-recipe-errors
- 33     screen <- render-recipes screen, env, render
- 34   }
- 35   render-recipe-errors env, screen
- 36 ]
- 37 
- 38 before <end-render-recipe-components> [
- 39   row <- render-recipe-errors env, screen
- 40 ]
- 41 
- 42 def render-recipe-errors env:&:environment, screen:&:screen -> row:num, screen:&:screen [
- 43   local-scope
- 44   load-inputs
- 45   recipe-errors:text <- get *env, recipe-errors:offset
- 46   recipes:&:editor <- get *env, recipes:offset
- 47   row:num <- get *recipes, bottom:offset
- 48   row <- add row, 1
- 49   return-unless recipe-errors
- 50   left:num <- get *recipes, left:offset
- 51   right:num <- get *recipes, right:offset
- 52   row, screen <- render-text screen, recipe-errors, left, right, 1/red, row
- 53   # draw dotted line after recipes
- 54   draw-horizontal screen, row, left, right, 9480/horizontal-dotted
- 55   row <- add row, 1
- 56   clear-screen-from screen, row, left, left, right
- 57 ]
- 58 
- 59 container environment [
- 60   error-index:num  # index of first sandbox with an error (or -1 if none)
- 61 ]
- 62 
- 63 after <programming-environment-initialization> [
- 64   *result <- put *result, error-index:offset, -1
- 65 ]
- 66 
- 67 after <begin-run-sandboxes> [
- 68   *env <- put *env, error-index:offset, -1
- 69 ]
- 70 
- 71 before <end-run-sandboxes> [
- 72   {
- 73     error-index:num <- get *env, error-index:offset
- 74     sandboxes-completed-successfully?:bool <- equal error-index, -1
- 75     break-if sandboxes-completed-successfully?
- 76     errors-found? <- copy true
- 77   }
- 78 ]
- 79 
- 80 before <end-run-sandboxes-on-F4> [
- 81   {
- 82     break-unless error?
- 83     recipe-errors:text <- get *env, recipe-errors:offset
- 84     break-if recipe-errors
- 85     error-index:num <- get *env, error-index:offset
- 86     sandboxes-completed-successfully?:bool <- equal error-index, -1
- 87     break-if sandboxes-completed-successfully?
- 88     error-index-text:text <- to-text error-index
- 89     status:text <- interpolate [errors found (_)    ], error-index-text
- 90     update-status screen, status, 1/red
- 91   }
- 92 ]
- 93 
- 94 container sandbox [
- 95   errors:text
- 96 ]
- 97 
- 98 def! update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [
- 99   local-scope
-100   load-inputs
-101   data:text <- get *sandbox, data:offset
-102   response:text, errors:text, fake-screen:&:screen, trace:text, completed?:bool <- run-sandboxed data
-103   *sandbox <- put *sandbox, response:offset, response
-104   *sandbox <- put *sandbox, errors:offset, errors
-105   *sandbox <- put *sandbox, screen:offset, fake-screen
-106   *sandbox <- put *sandbox, trace:offset, trace
-107   {
-108     break-if errors
-109     break-if completed?
-110     errors <- new [took too long!
-111 ]
-112     *sandbox <- put *sandbox, errors:offset, errors
-113   }
-114   {
-115     break-unless errors
-116     error-index:num <- get *env, error-index:offset
-117     error-not-set?:bool <- equal error-index, -1
-118     break-unless error-not-set?
-119     *env <- put *env, error-index:offset, idx
-120   }
-121 ]
-122 
-123 # make sure we render any trace
-124 after <render-sandbox-trace-done> [
-125   {
-126     sandbox-errors:text <- get *sandbox, errors:offset
-127     break-unless sandbox-errors
-128     *sandbox <- put *sandbox, response-starting-row-on-screen:offset, 0  # no response
-129     row, screen <- render-text screen, sandbox-errors, left, right, 1/red, row
-130     # don't try to print anything more for this sandbox
-131     jump +render-sandbox-end
-132   }
-133 ]
-134 
-135 scenario run-shows-errors-in-get [
-136   local-scope
-137   trace-until 100/app  # trace too long
-138   assume-screen 100/width, 15/height
-139   assume-resources [
-140     [lesson/recipes.mu] <- [
-141       |recipe foo [|
-142       |  get 123:num, foo:offset|
-143       |]|
-144     ]
-145   ]
-146   env:&:environment <- new-programming-environment resources, screen, [foo]
-147   render-all screen, env, render
-148   screen-should-contain [
-149     .                                                                                 run (F4)           .
-150     .recipe foo [                                      ╎foo                                              .
-151     .  get 123:num, foo:offset                         ╎─────────────────────────────────────────────────.
-152     .]                                                 ╎                                                 .
-153     .                                                  ╎                                                 .
-154     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-155     .                                                  ╎                                                 .
-156   ]
-157   assume-console [
-158     press F4
-159   ]
-160   run [
-161     event-loop screen, console, env, resources
-162   ]
-163   screen-should-contain [
-164     .  errors found                                                                   run (F4)           .
-165     .recipe foo [                                      ╎foo                                              .
-166     .  get 123:num, foo:offset                         ╎─────────────────────────────────────────────────.
-167     .]                                                 ╎                                                 .
-168     .                                                  ╎                                                 .
-169     .foo: unknown element 'foo' in container 'number'  ╎                                                 .
-170     .foo: first ingredient of 'get' should be a contai↩╎                                                 .
-171     .ner, but got '123:num'                            ╎                                                 .
-172     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-173     .                                                  ╎                                                 .
-174   ]
-175   screen-should-contain-in-color 1/red, [
-176     .  errors found                                                                                      .
-177     .                                                                                                    .
-178     .                                                                                                    .
-179     .                                                                                                    .
-180     .                                                                                                    .
-181     .foo: unknown element 'foo' in container 'number'                                                    .
-182     .foo: first ingredient of 'get' should be a contai                                                   .
-183     .ner, but got '123:num'                                                                              .
-184     .                                                                                                    .
-185   ]
-186 ]
-187 
-188 scenario run-shows-errors-without-final-newline-in-recipe-side [
-189   local-scope
-190   trace-until 100/app  # trace too long
-191   assume-screen 100/width, 15/height
-192   assume-resources [
-193   ]
-194   env:&:environment <- new-programming-environment resources, screen
-195   render-all screen, env, render
-196   assume-console [
-197     type [recipe foo x [
-198 ]]
-199     press F4
-200   ]
-201   run [
-202     event-loop screen, console, env, resources
-203   ]
-204   screen-should-contain [
-205     .  errors found                                                                   run (F4)           .
-206     .recipe foo x [                                    ╎                                                 .
-207     .]                                                 ╎─────────────────────────────────────────────────.
-208     .foo: ingredient 'x' has no type                   ╎                                                 .
-209     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-210     .                                                  ╎                                                 .
-211   ]
-212 ]
-213 
-214 scenario run-updates-status-with-first-erroneous-sandbox [
-215   local-scope
-216   trace-until 100/app  # trace too long
-217   assume-screen 100/width, 15/height
-218   assume-resources [
-219   ]
-220   env:&:environment <- new-programming-environment resources, screen, []
-221   render-all screen, env, render
-222   assume-console [
-223     left-click 3, 80
-224     # create invalid sandbox 1
-225     type [get foo, x:offset]
-226     press F4
-227     # create invalid sandbox 0
-228     type [get foo, x:offset]
-229     press F4
-230   ]
-231   run [
-232     event-loop screen, console, env, resources
-233   ]
-234   # status line shows that error is in first sandbox
-235   screen-should-contain [
-236     .  errors found (0)                                                               run (F4)           .
-237   ]
-238 ]
-239 
-240 scenario run-updates-status-with-first-erroneous-sandbox-2 [
-241   local-scope
-242   trace-until 100/app  # trace too long
-243   assume-screen 100/width, 15/height
-244   assume-resources [
-245   ]
-246   env:&:environment <- new-programming-environment resources, screen, []
-247   render-all screen, env, render
-248   assume-console [
-249     left-click 3, 80
-250     # create invalid sandbox 2
-251     type [get foo, x:offset]
-252     press F4
-253     # create invalid sandbox 1
-254     type [get foo, x:offset]
-255     press F4
-256     # create valid sandbox 0
-257     type [add 2, 2]
-258     press F4
-259   ]
-260   run [
-261     event-loop screen, console, env, resources
-262   ]
-263   # status line shows that error is in second sandbox
-264   screen-should-contain [
-265     .  errors found (1)                                                               run (F4)           .
-266   ]
-267 ]
-268 
-269 scenario run-hides-errors-from-past-sandboxes [
-270   local-scope
-271   trace-until 100/app  # trace too long
-272   assume-screen 100/width, 15/height
-273   assume-resources [
-274   ]
-275   env:&:environment <- new-programming-environment resources, screen, [get foo, x:offset]  # invalid
-276   render-all screen, env, render
-277   assume-console [
-278     press F4  # generate error
-279   ]
-280   event-loop screen, console, env, resources
-281   assume-console [
-282     left-click 3, 58
-283     press ctrl-k
-284     type [add 2, 2]  # valid code
-285     press F4  # update sandbox
-286   ]
-287   run [
-288     event-loop screen, console, env, resources
-289   ]
-290   # error should disappear
-291   screen-should-contain [
-292     .                                                                                 run (F4)           .
-293     .                                                  ╎                                                 .
-294     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-295     .                                                  ╎0   edit       copy       to recipe    delete    .
-296     .                                                  ╎add 2, 2                                         .
-297     .                                                  ╎4                                                .
-298     .                                                  ╎─────────────────────────────────────────────────.
-299     .                                                  ╎                                                 .
-300   ]
-301 ]
-302 
-303 scenario run-updates-errors-for-shape-shifting-recipes [
-304   local-scope
-305   trace-until 100/app  # trace too long
-306   assume-screen 100/width, 15/height
-307   # define a shape-shifting recipe with an error
-308   assume-resources [
-309     [lesson/recipes.mu] <- [
-310       |recipe foo x:_elem -> z:_elem [|
-311       |  local-scope|
-312       |  load-ingredients|
-313       |  y:&:num <- copy null|
-314       |  z <- add x, y|
-315       |]|
-316     ]
-317   ]
-318   env:&:environment <- new-programming-environment resources, screen, [foo 2]
-319   render-all screen, env, render
-320   assume-console [
-321     press F4
-322   ]
-323   event-loop screen, console, env, resources
-324   screen-should-contain [
-325     .  errors found (0)                                                               run (F4)           .
-326     .recipe foo x:_elem -> z:_elem [                   ╎                                                 .
-327     .  local-scope                                     ╎─────────────────────────────────────────────────.
-328     .  load-ingredients                                ╎0   edit       copy       to recipe    delete    .
-329     .  y:&:num <- copy null                            ╎foo 2                                            .
-330     .  z <- add x, y                                   ╎foo_2: 'add' requires number ingredients, but go↩.
-331     .]                                                 ╎t 'y'                                            .
-332     .                                                  ╎─────────────────────────────────────────────────.
-333     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-334     .                                                  ╎                                                 .
-335   ]
-336   # now rerun everything
-337   assume-console [
-338     press F4
-339   ]
-340   run [
-341     event-loop screen, console, env, resources
-342   ]
-343   # error should remain unchanged
-344   screen-should-contain [
-345     .  errors found (0)                                                               run (F4)           .
-346     .recipe foo x:_elem -> z:_elem [                   ╎                                                 .
-347     .  local-scope                                     ╎─────────────────────────────────────────────────.
-348     .  load-ingredients                                ╎0   edit       copy       to recipe    delete    .
-349     .  y:&:num <- copy null                            ╎foo 2                                            .
-350     .  z <- add x, y                                   ╎foo_3: 'add' requires number ingredients, but go↩.
-351     .]                                                 ╎t 'y'                                            .
-352     .                                                  ╎─────────────────────────────────────────────────.
-353     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-354     .                                                  ╎                                                 .
-355   ]
-356 ]
-357 
-358 scenario run-avoids-spurious-errors-on-reloading-shape-shifting-recipes [
-359   local-scope
-360   trace-until 100/app  # trace too long
-361   assume-screen 100/width, 15/height
-362   # overload a well-known shape-shifting recipe
-363   assume-resources [
-364     [lesson/recipes.mu] <- [
-365       |recipe length l:&:list:_elem -> n:num [|
-366       |]|
-367     ]
-368   ]
-369   # call code that uses other variants of it, but not it itself
-370   test-sandbox:text <- new [x:&:list:num <- copy null
-371 to-text x]
-372   env:&:environment <- new-programming-environment resources, screen, test-sandbox
-373   render-all screen, env, render
-374   # run it once
-375   assume-console [
-376     press F4
-377   ]
-378   event-loop screen, console, env, resources
-379   # no errors anywhere on screen (can't check anything else, since to-text will return an address)
-380   screen-should-contain-in-color 1/red, [
-381     .                                                                                                    .
-382     .                                                                                                    .
-383     .                                                                                                    .
-384     .                                                                                                    .
-385     .                                                                <-                                  .
-386     .                                                                                                    .
-387     .                                                                                                    .
-388     .                                                                                                    .
-389     .                                                                                                    .
-390     .                                                                                                    .
-391     .                                                                                                    .
-392     .                                                                                                    .
-393     .                                                                                                    .
-394     .                                                                                                    .
-395     .                                                                                                    .
-396   ]
-397   # rerun everything
-398   assume-console [
-399     press F4
-400   ]
-401   run [
-402     event-loop screen, console, env, resources
-403   ]
-404   # still no errors
-405   screen-should-contain-in-color 1/red, [
-406     .                                                                                                    .
-407     .                                                                                                    .
-408     .                                                                                                    .
-409     .                                                                                                    .
-410     .                                                                <-                                  .
-411     .                                                                                                    .
-412     .                                                                                                    .
-413     .                                                                                                    .
-414     .                                                                                                    .
-415     .                                                                                                    .
-416     .                                                                                                    .
-417     .                                                                                                    .
-418     .                                                                                                    .
-419     .                                                                                                    .
-420     .                                                                                                    .
-421   ]
-422 ]
-423 
-424 scenario run-shows-missing-type-errors [
-425   local-scope
-426   trace-until 100/app  # trace too long
-427   assume-screen 100/width, 15/height
-428   assume-resources [
-429     [lesson/recipes.mu] <- [
-430       |recipe foo [|
-431       |  x <- copy 0|
-432       |]|
-433     ]
-434   ]
-435   env:&:environment <- new-programming-environment resources, screen, [foo]
-436   render-all screen, env, render
-437   assume-console [
-438     press F4
-439   ]
-440   run [
-441     event-loop screen, console, env, resources
-442   ]
-443   screen-should-contain [
-444     .  errors found                                                                   run (F4)           .
-445     .recipe foo [                                      ╎foo                                              .
-446     .  x <- copy 0                                     ╎─────────────────────────────────────────────────.
-447     .]                                                 ╎                                                 .
-448     .                                                  ╎                                                 .
-449     .foo: missing type for 'x' in 'x <- copy 0'        ╎                                                 .
-450     .foo: can't copy '0' to 'x'; types don't match     ╎                                                 .
-451     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-452     .                                                  ╎                                                 .
-453   ]
-454 ]
-455 
-456 scenario run-shows-unbalanced-bracket-errors [
-457   local-scope
-458   trace-until 100/app  # trace too long
-459   assume-screen 100/width, 15/height
-460   # recipe is incomplete (unbalanced '[')
-461   assume-resources [
-462     [lesson/recipes.mu] <- [
-463       |recipe foo \\\[|
-464       |  x <- copy 0|
-465     ]
-466   ]
-467   env:&:environment <- new-programming-environment resources, screen, [foo]
-468   render-all screen, env, render
-469   assume-console [
-470     press F4
-471   ]
-472   run [
-473     event-loop screen, console, env, resources
-474   ]
-475   screen-should-contain [
-476     .  errors found                                                                   run (F4)           .
-477     .recipe foo \\[                                      ╎foo                                              .
-478     .  x <- copy 0                                     ╎─────────────────────────────────────────────────.
-479     .                                                  ╎                                                 .
-480     .9: unbalanced '\\[' for recipe                      ╎                                                 .
-481     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-482     .                                                  ╎                                                 .
-483   ]
-484 ]
-485 
-486 scenario run-shows-get-on-non-container-errors [
-487   local-scope
-488   trace-until 100/app  # trace too long
-489   assume-screen 100/width, 15/height
-490   assume-resources [
-491     [lesson/recipes.mu] <- [
-492       |recipe foo [|
-493       |  local-scope|
-494       |  x:&:point <- new point:type|
-495       |  get x:&:point, 1:offset|
-496       |]|
-497     ]
-498   ]
-499   env:&:environment <- new-programming-environment resources, screen, [foo]
-500   render-all screen, env, render
-501   assume-console [
-502     press F4
-503   ]
-504   run [
-505     event-loop screen, console, env, resources
-506   ]
-507   screen-should-contain [
-508     .  errors found                                                                   run (F4)           .
-509     .recipe foo [                                      ╎foo                                              .
-510     .  local-scope                                     ╎─────────────────────────────────────────────────.
-511     .  x:&:point <- new point:type                     ╎                                                 .
-512     .  get x:&:point, 1:offset                         ╎                                                 .
-513     .]                                                 ╎                                                 .
-514     .                                                  ╎                                                 .
-515     .foo: first ingredient of 'get' should be a contai↩╎                                                 .
-516     .ner, but got 'x:&:point'                          ╎                                                 .
-517     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-518     .                                                  ╎                                                 .
-519   ]
-520 ]
-521 
-522 scenario run-shows-non-literal-get-argument-errors [
-523   local-scope
-524   trace-until 100/app  # trace too long
-525   assume-screen 100/width, 15/height
-526   assume-resources [
-527     [lesson/recipes.mu] <- [
-528       |recipe foo [|
-529       |  local-scope|
-530       |  x:num <- copy 0|
-531       |  y:&:point <- new point:type|
-532       |  get *y:&:point, x:num|
-533       |]|
-534     ]
-535   ]
-536   env:&:environment <- new-programming-environment resources, screen, [foo]
-537   render-all screen, env, render
-538   assume-console [
-539     press F4
-540   ]
-541   run [
-542     event-loop screen, console, env, resources
-543   ]
-544   screen-should-contain [
-545     .  errors found                                                                   run (F4)           .
-546     .recipe foo [                                      ╎foo                                              .
-547     .  local-scope                                     ╎─────────────────────────────────────────────────.
-548     .  x:num <- copy 0                                 ╎                                                 .
-549     .  y:&:point <- new point:type                     ╎                                                 .
-550     .  get *y:&:point, x:num                           ╎                                                 .
-551     .]                                                 ╎                                                 .
-552     .                                                  ╎                                                 .
-553     .foo: second ingredient of 'get' should have type ↩╎                                                 .
-554     .'offset', but got 'x:num'                         ╎                                                 .
-555     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-556     .                                                  ╎                                                 .
-557   ]
-558 ]
-559 
-560 scenario run-shows-errors-every-time [
-561   local-scope
-562   trace-until 100/app  # trace too long
-563   assume-screen 100/width, 15/height
-564   # try to run a file with an error
-565   assume-resources [
-566     [lesson/recipes.mu] <- [
-567       |recipe foo [|
-568       |  local-scope|
-569       |  x:num <- copy y:num|
-570       |]|
-571     ]
-572   ]
-573   env:&:environment <- new-programming-environment resources, screen, [foo]
-574   render-all screen, env, render
-575   assume-console [
-576     press F4
-577   ]
-578   event-loop screen, console, env, resources
-579   screen-should-contain [
-580     .  errors found                                                                   run (F4)           .
-581     .recipe foo [                                      ╎foo                                              .
-582     .  local-scope                                     ╎─────────────────────────────────────────────────.
-583     .  x:num <- copy y:num                             ╎                                                 .
-584     .]                                                 ╎                                                 .
-585     .                                                  ╎                                                 .
-586     .foo: tried to read ingredient 'y' in 'x:num <- co↩╎                                                 .
-587     .py y:num' but it hasn't been written to yet       ╎                                                 .
-588     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-589     .                                                  ╎                                                 .
-590   ]
-591   # rerun the file, check for the same error
-592   assume-console [
-593     press F4
-594   ]
-595   run [
-596     event-loop screen, console, env, resources
-597   ]
-598   screen-should-contain [
-599     .  errors found                                                                   run (F4)           .
-600     .recipe foo [                                      ╎foo                                              .
-601     .  local-scope                                     ╎─────────────────────────────────────────────────.
-602     .  x:num <- copy y:num                             ╎                                                 .
-603     .]                                                 ╎                                                 .
-604     .                                                  ╎                                                 .
-605     .foo: tried to read ingredient 'y' in 'x:num <- co↩╎                                                 .
-606     .py y:num' but it hasn't been written to yet       ╎                                                 .
-607     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-608     .                                                  ╎                                                 .
-609   ]
-610 ]
-611 
-612 scenario run-hides-errors [
-613   local-scope
-614   trace-until 100/app  # trace too long
-615   assume-screen 100/width, 15/height
-616   # try to run a file with an error
-617   assume-resources [
-618     [lesson/recipes.mu] <- [
-619       |recipe foo [|
-620       |  local-scope|
-621       |  x:num <- copy y:num|
-622       |]|
-623     ]
-624   ]
-625   env:&:environment <- new-programming-environment resources, screen, [foo]
-626   render-all screen, env, render
-627   assume-console [
-628     press F4
-629   ]
-630   event-loop screen, console, env, resources
-631   screen-should-contain [
-632     .  errors found                                                                   run (F4)           .
-633     .recipe foo [                                      ╎foo                                              .
-634     .  local-scope                                     ╎─────────────────────────────────────────────────.
-635     .  x:num <- copy y:num                             ╎                                                 .
-636     .]                                                 ╎                                                 .
-637     .                                                  ╎                                                 .
-638     .foo: tried to read ingredient 'y' in 'x:num <- co↩╎                                                 .
-639     .py y:num' but it hasn't been written to yet       ╎                                                 .
-640     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-641     .                                                  ╎                                                 .
-642   ]
-643   # fix the error, hit F4
-644   assume-console [
-645     left-click 3, 16
-646     press ctrl-k
-647     type [0]
-648     press F4
-649   ]
-650   event-loop screen, console, env, resources
-651   # no error anymore
-652   screen-should-contain [
-653     .                                                                                 run (F4)           .
-654     .recipe foo [                                      ╎                                                 .
-655     .  local-scope                                     ╎─────────────────────────────────────────────────.
-656     .  x:num <- copy 0                                 ╎0   edit       copy       to recipe    delete    .
-657     .]                                                 ╎foo                                              .
-658     .                                                  ╎─────────────────────────────────────────────────.
-659     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-660     .                                                  ╎                                                 .
-661   ]
-662 ]
-663 
-664 scenario scrolling-recipe-side-reveals-errors [
-665   local-scope
-666   trace-until 100/app  # trace too long
-667   assume-screen 100/width, 5/height
-668   # recipe overflows recipe side
-669   assume-resources [
-670     [lesson/recipes.mu] <- [
-671       |recipe foo [|
-672       |  a:num <- copy 0|  # padding to overflow recipe side
-673       |  b:num <- copy 0|  # padding to overflow recipe side
-674       |  get 123:num, foo:offset|  # line containing error
-675       |]|
-676     ]
-677   ]
-678   env:&:environment <- new-programming-environment resources, screen, [foo]
-679   render-all screen, env, render
-680   # hit F4, generating errors, then scroll down
-681   assume-console [
-682     press F4
-683     press page-down
-684   ]
-685   run [
-686     event-loop screen, console, env, resources
-687   ]
-688   # errors should be displayed
-689   screen-should-contain [
-690     .  errors found                                                                   run (F4)           .
-691     .  get 123:num, foo:offset                         ╎foo                                              .
-692     .\\]                                                 ╎─────────────────────────────────────────────────.
-693     .                                                  ╎                                                 .
-694     .foo: unknown element 'foo' in container 'number'  ╎                                                 .
-695   ]
-696 ]
-697 
-698 scenario run-instruction-and-print-errors [
-699   local-scope
-700   trace-until 100/app  # trace too long
-701   assume-screen 100/width, 10/height
-702   assume-resources [
-703   ]
-704   # sandbox editor contains an illegal instruction
-705   env:&:environment <- new-programming-environment resources, screen, [get 1234:num, foo:offset]
-706   render-all screen, env, render
-707   assume-console [
-708     press F4
-709   ]
-710   run [
-711     event-loop screen, console, env, resources
-712   ]
-713   # check that screen prints error message in red
-714   screen-should-contain [
-715     .  errors found (0)                                                               run (F4)           .
-716     .                                                  ╎                                                 .
-717     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-718     .                                                  ╎0   edit       copy       to recipe    delete    .
-719     .                                                  ╎get 1234:num, foo:offset                         .
-720     .                                                  ╎unknown element 'foo' in container 'number'      .
-721     .                                                  ╎first ingredient of 'get' should be a container,↩.
-722     .                                                  ╎ but got '1234:num'                              .
-723     .                                                  ╎─────────────────────────────────────────────────.
-724     .                                                  ╎                                                 .
-725   ]
-726   screen-should-contain-in-color 7/white, [
-727     .                                                                                                    .
-728     .                                                                                                    .
-729     .                                                                                                    .
-730     .                                                                                                    .
-731     .                                                   get 1234:num, foo:offset                         .
-732     .                                                                                                    .
-733     .                                                                                                    .
-734     .                                                                                                    .
-735   ]
-736   screen-should-contain-in-color 1/red, [
-737     .  errors found (0)                                                                                  .
-738     .                                                                                                    .
-739     .                                                                                                    .
-740     .                                                                                                    .
-741     .                                                                                                    .
-742     .                                                   unknown element 'foo' in container 'number'      .
-743     .                                                   first ingredient of 'get' should be a container, .
-744     .                                                    but got '1234:num'                              .
-745     .                                                                                                    .
-746   ]
-747   screen-should-contain-in-color 245/grey, [
-748     .                                                                                                    .
-749     .                                                  ╎                                                 .
-750     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-751     .                                                  ╎                                                 .
-752     .                                                  ╎                                                 .
-753     .                                                  ╎                                                 .
-754     .                                                  ╎                                                ↩.
-755     .                                                  ╎                                                 .
-756     .                                                  ╎─────────────────────────────────────────────────.
-757     .                                                  ╎                                                 .
-758   ]
-759 ]
-760 
-761 scenario run-instruction-and-print-errors-only-once [
-762   local-scope
-763   trace-until 100/app  # trace too long
-764   assume-screen 100/width, 10/height
-765   assume-resources [
-766   ]
-767   # sandbox editor contains an illegal instruction
-768   env:&:environment <- new-programming-environment resources, screen, [get 1234:num, foo:offset]
-769   render-all screen, env, render
-770   # run the code in the editors multiple times
-771   assume-console [
-772     press F4
-773     press F4
-774   ]
-775   run [
-776     event-loop screen, console, env, resources
-777   ]
-778   # check that screen prints error message just once
-779   screen-should-contain [
-780     .  errors found (0)                                                               run (F4)           .
-781     .                                                  ╎                                                 .
-782     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
-783     .                                                  ╎0   edit       copy       to recipe    delete    .
-784     .                                                  ╎get 1234:num, foo:offset                         .
-785     .                                                  ╎unknown element 'foo' in container 'number'      .
-786     .                                                  ╎first ingredient of 'get' should be a container,↩.
-787     .                                                  ╎ but got '1234:num'                              .
-788     .                                                  ╎─────────────────────────────────────────────────.
-789     .                                                  ╎                                                 .
-790   ]
-791 ]
-792 
-793 scenario sandbox-can-handle-infinite-loop [
-794   local-scope
-795   trace-until 100/app  # trace too long
-796   assume-screen 100/width, 20/height
-797   # sandbox editor will trigger an infinite loop
-798   assume-resources [
-799     [lesson/recipes.mu] <- [
-800       |recipe foo [|
-801       |  {|
-802       |    loop|
-803       |  }|
-804       |]|
-805     ]
-806   ]
-807   env:&:environment <- new-programming-environment resources, screen, [foo]
-808   render-all screen, env, render
-809   # run the sandbox
-810   assume-console [
-811     press F4
-812   ]
-813   run [
-814     event-loop screen, console, env, resources
-815   ]
-816   screen-should-contain [
-817     .  errors found (0)                                                               run (F4)           .
-818     .recipe foo [                                      ╎                                                 .
-819     .  {                                               ╎─────────────────────────────────────────────────.
-820     .    loop                                          ╎0   edit       copy       to recipe    delete    .
-821     .  }                                               ╎foo                                              .
-822     .]                                                 ╎took too long!                                   .
-823     .                                                  ╎─────────────────────────────────────────────────.
-824     .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
-825     .                                                  ╎                                                 .
-826   ]
-827 ]
-828 
-829 scenario sandbox-with-errors-shows-trace [
-830   local-scope
-831   trace-until 100/app  # trace too long
-832   assume-screen 100/width, 10/height
-833   # generate a stash and a error
-834   assume-resources [
-835     [lesson/recipes.mu] <- [
-836       |recipe foo [|
-837       |  local-scope|
-838       |  a:num <- next-ingredient|
-839       |  b:num <- next-ingredient|
-840       |  stash [dividing by], b|
-841       |  _, c:num <- divide-with-remainder a, b|
-842       |  reply b|
-843       |]|
-844     ]
-845   ]
-846   env:&:environment <- new-programming-environment resources, screen, [foo 4, 0]
-847   render-all screen, env, render
-848   # run
-849   assume-console [
-850     press F4
-851   ]
-852   event-loop screen, console, env, resources
-853   # screen prints error message
-854   screen-should-contain [
-855     .  errors found (0)                                                               run (F4)           .
-856     .recipe foo [                                      ╎                                                 .
-857     .  local-scope                                     ╎─────────────────────────────────────────────────.
-858     .  a:num <- next-ingredient                        ╎0   edit       copy       to recipe    delete    .
-859     .  b:num <- next-ingredient                        ╎foo 4, 0                                         .
-860     .  stash [dividing by], b                          ╎foo: divide by zero in '_, c:num <- divide-with-↩.
-861     .  _, c:num <- divide-with-remainder a, b          ╎remainder a, b'                                  .
-862     .  reply b                                         ╎─────────────────────────────────────────────────.
-863     .]                                                 ╎                                                 .
-864     .                                                  ╎                                                 .
-865   ]
-866   # click on the call in the sandbox
-867   assume-console [
-868     left-click 4, 55
-869   ]
-870   run [
-871     event-loop screen, console, env, resources
-872   ]
-873   # screen should expand trace
-874   screen-should-contain [
-875     .  errors found (0)                                                               run (F4)           .
-876     .recipe foo [                                      ╎                                                 .
-877     .  local-scope                                     ╎─────────────────────────────────────────────────.
-878     .  a:num <- next-ingredient                        ╎0   edit       copy       to recipe    delete    .
-879     .  b:num <- next-ingredient                        ╎foo 4, 0                                         .
-880     .  stash [dividing by], b                          ╎dividing by 0                                    .
-881     .  _, c:num <- divide-with-remainder a, b          ╎14 instructions run                              .
-882     .  reply b                                         ╎foo: divide by zero in '_, c:num <- divide-with-↩.
-883     .]                                                 ╎remainder a, b'                                  .
-884     .                                                  ╎─────────────────────────────────────────────────.
-885   ]
-886 ]
-
- - - diff --git a/html/edit/012-editor-undo.mu.html b/html/edit/012-editor-undo.mu.html deleted file mode 100644 index 3b663e19..00000000 --- a/html/edit/012-editor-undo.mu.html +++ /dev/null @@ -1,2176 +0,0 @@ - - - - -Mu - edit/012-editor-undo.mu - - - - - - - - - - -https://github.com/akkartik/mu/blob/master/edit/012-editor-undo.mu -
-   1 ## undo/redo
-   2 
-   3 # for every undoable event, create a type of *operation* that contains all the
-   4 # information needed to reverse it
-   5 exclusive-container operation [
-   6   typing:insert-operation
-   7   move:move-operation
-   8   delete:delete-operation
-   9 ]
-  10 
-  11 container insert-operation [
-  12   before-row:num
-  13   before-column:num
-  14   before-top-of-screen:&:duplex-list:char
-  15   after-row:num
-  16   after-column:num
-  17   after-top-of-screen:&:duplex-list:char
-  18   # inserted text is from 'insert-from' until 'insert-until'; list doesn't have to terminate
-  19   insert-from:&:duplex-list:char
-  20   insert-until:&:duplex-list:char
-  21   tag:num  # event causing this operation; might be used to coalesce runs of similar events
-  22     # 0: no coalesce (enter+indent)
-  23     # 1: regular alphanumeric characters
-  24 ]
-  25 
-  26 container move-operation [
-  27   before-row:num
-  28   before-column:num
-  29   before-top-of-screen:&:duplex-list:char
-  30   after-row:num
-  31   after-column:num
-  32   after-top-of-screen:&:duplex-list:char
-  33   tag:num  # event causing this operation; might be used to coalesce runs of similar events
-  34     # 0: no coalesce (touch events, etc)
-  35     # 1: left arrow
-  36     # 2: right arrow
-  37     # 3: up arrow
-  38     # 4: down arrow
-  39     # 5: line up
-  40     # 6: line down
-  41 ]
-  42 
-  43 container delete-operation [
-  44   before-row:num
-  45   before-column:num
-  46   before-top-of-screen:&:duplex-list:char
-  47   after-row:num
-  48   after-column:num
-  49   after-top-of-screen:&:duplex-list:char
-  50   deleted-text:&:duplex-list:char
-  51   delete-from:&:duplex-list:char
-  52   delete-until:&:duplex-list:char
-  53   tag:num  # event causing this operation; might be used to coalesce runs of similar events
-  54     # 0: no coalesce (ctrl-k, ctrl-u)
-  55     # 1: backspace
-  56     # 2: delete
-  57 ]
-  58 
-  59 # every editor accumulates a list of operations to undo/redo
-  60 container editor [
-  61   undo:&:list:&:operation
-  62   redo:&:list:&:operation
-  63 ]
-  64 
-  65 # ctrl-z - undo operation
-  66 after <handle-special-character> [
-  67   {
-  68     undo?:bool <- equal c, 26/ctrl-z
-  69     break-unless undo?
-  70     undo:&:list:&:operation <- get *editor, undo:offset
-  71     break-unless undo
-  72     op:&:operation <- first undo
-  73     undo <- rest undo
-  74     *editor <- put *editor, undo:offset, undo
-  75     redo:&:list:&:operation <- get *editor, redo:offset
-  76     redo <- push op, redo
-  77     *editor <- put *editor, redo:offset, redo
-  78     <handle-undo>
-  79     return true/go-render
-  80   }
-  81 ]
-  82 
-  83 # ctrl-y - redo operation
-  84 after <handle-special-character> [
-  85   {
-  86     redo?:bool <- equal c, 25/ctrl-y
-  87     break-unless redo?
-  88     redo:&:list:&:operation <- get *editor, redo:offset
-  89     break-unless redo
-  90     op:&:operation <- first redo
-  91     redo <- rest redo
-  92     *editor <- put *editor, redo:offset, redo
-  93     undo:&:list:&:operation <- get *editor, undo:offset
-  94     undo <- push op, undo
-  95     *editor <- put *editor, undo:offset, undo
-  96     <handle-redo>
-  97     return true/go-render
-  98   }
-  99 ]
- 100 
- 101 # undo typing
- 102 
- 103 scenario editor-can-undo-typing [
- 104   local-scope
- 105   # create an editor and type a character
- 106   assume-screen 10/width, 5/height
- 107   e:&:editor <- new-editor [], 0/left, 10/right
- 108   editor-render screen, e
- 109   assume-console [
- 110     type [0]
- 111   ]
- 112   editor-event-loop screen, console, e
- 113   # undo
- 114   assume-console [
- 115     press ctrl-z
- 116   ]
- 117   run [
- 118     editor-event-loop screen, console, e
- 119   ]
- 120   # character should be gone
- 121   screen-should-contain [
- 122     .          .
- 123     .          .
- 124     .╌╌╌╌╌╌╌╌╌╌.
- 125     .          .
- 126   ]
- 127   # cursor should be in the right place
- 128   assume-console [
- 129     type [1]
- 130   ]
- 131   run [
- 132     editor-event-loop screen, console, e
- 133   ]
- 134   screen-should-contain [
- 135     .          .
- 136     .1         .
- 137     .╌╌╌╌╌╌╌╌╌╌.
- 138     .          .
- 139   ]
- 140 ]
- 141 
- 142 # save operation to undo
- 143 after <begin-insert-character> [
- 144   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
- 145   cursor-before:&:duplex-list:char <- get *editor, before-cursor:offset
- 146 ]
- 147 before <end-insert-character> [
- 148   top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
- 149   cursor-row:num <- get *editor, cursor-row:offset
- 150   cursor-column:num <- get *editor, cursor-column:offset
- 151   undo:&:list:&:operation <- get *editor, undo:offset
- 152   {
- 153     # if previous operation was an insert, coalesce this operation with it
- 154     break-unless undo
- 155     op:&:operation <- first undo
- 156     typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant
- 157     break-unless is-insert?
- 158     previous-coalesce-tag:num <- get typing, tag:offset
- 159     break-unless previous-coalesce-tag
- 160     before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
- 161     insert-until:&:duplex-list:char <- next before-cursor
- 162     typing <- put typing, insert-until:offset, insert-until
- 163     typing <- put typing, after-row:offset, cursor-row
- 164     typing <- put typing, after-column:offset, cursor-column
- 165     typing <- put typing, after-top-of-screen:offset, top-after
- 166     *op <- merge 0/insert-operation, typing
- 167     break +done-adding-insert-operation
- 168   }
- 169   # if not, create a new operation
- 170   insert-from:&:duplex-list:char <- next cursor-before
- 171   insert-to:&:duplex-list:char <- next insert-from
- 172   op:&:operation <- new operation:type
- 173   *op <- merge 0/insert-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, insert-from, insert-to, 1/coalesce
- 174   editor <- add-operation editor, op
- 175   +done-adding-insert-operation
- 176 ]
- 177 
- 178 # enter operations never coalesce with typing before or after
- 179 after <begin-insert-enter> [
- 180   cursor-row-before:num <- copy cursor-row
- 181   cursor-column-before:num <- copy cursor-column
- 182   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
- 183   cursor-before:&:duplex-list:char <- get *editor, before-cursor:offset
- 184 ]
- 185 before <end-insert-enter> [
- 186   top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
- 187   cursor-row:num <- get *editor, cursor-row:offset
- 188   cursor-column:num <- get *editor, cursor-row:offset
- 189   # never coalesce
- 190   insert-from:&:duplex-list:char <- next cursor-before
- 191   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
- 192   insert-to:&:duplex-list:char <- next before-cursor
- 193   op:&:operation <- new operation:type
- 194   *op <- merge 0/insert-operation, cursor-row-before, cursor-column-before, top-before, cursor-row/after, cursor-column/after, top-after, insert-from, insert-to, 0/never-coalesce
- 195   editor <- add-operation editor, op
- 196 ]
- 197 
- 198 # Everytime you add a new operation to the undo stack, be sure to clear the
- 199 # redo stack, because it's now obsolete.
- 200 # Beware: since we're counting cursor moves as operations, this means just
- 201 # moving the cursor can lose work on the undo stack.
- 202 def add-operation editor:&:editor, op:&:operation -> editor:&:editor [
- 203   local-scope
- 204   load-inputs
- 205   undo:&:list:&:operation <- get *editor, undo:offset
- 206   undo <- push op undo
- 207   *editor <- put *editor, undo:offset, undo
- 208   redo:&:list:&:operation <- get *editor, redo:offset
- 209   redo <- copy null
- 210   *editor <- put *editor, redo:offset, redo
- 211 ]
- 212 
- 213 after <handle-undo> [
- 214   {
- 215     typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant
- 216     break-unless is-insert?
- 217     start:&:duplex-list:char <- get typing, insert-from:offset
- 218     end:&:duplex-list:char <- get typing, insert-until:offset
- 219     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
- 220     before-cursor:&:duplex-list:char <- prev start
- 221     *editor <- put *editor, before-cursor:offset, before-cursor
- 222     remove-between before-cursor, end
- 223     cursor-row <- get typing, before-row:offset
- 224     *editor <- put *editor, cursor-row:offset, cursor-row
- 225     cursor-column <- get typing, before-column:offset
- 226     *editor <- put *editor, cursor-column:offset, cursor-column
- 227     top:&:duplex-list:char <- get typing, before-top-of-screen:offset
- 228     *editor <- put *editor, top-of-screen:offset, top
- 229   }
- 230 ]
- 231 
- 232 scenario editor-can-undo-typing-multiple [
- 233   local-scope
- 234   # create an editor and type multiple characters
- 235   assume-screen 10/width, 5/height
- 236   e:&:editor <- new-editor [], 0/left, 10/right
- 237   editor-render screen, e
- 238   assume-console [
- 239     type [012]
- 240   ]
- 241   editor-event-loop screen, console, e
- 242   # undo
- 243   assume-console [
- 244     press ctrl-z
- 245   ]
- 246   run [
- 247     editor-event-loop screen, console, e
- 248   ]
- 249   # all characters must be gone
- 250   screen-should-contain [
- 251     .          .
- 252     .          .
- 253     .╌╌╌╌╌╌╌╌╌╌.
- 254     .          .
- 255   ]
- 256 ]
- 257 
- 258 scenario editor-can-undo-typing-multiple-2 [
- 259   local-scope
- 260   # create an editor with some text
- 261   assume-screen 10/width, 5/height
- 262   e:&:editor <- new-editor [a], 0/left, 10/right
- 263   editor-render screen, e
- 264   # type some characters
- 265   assume-console [
- 266     type [012]
- 267   ]
- 268   editor-event-loop screen, console, e
- 269   screen-should-contain [
- 270     .          .
- 271     .012a      .
- 272     .╌╌╌╌╌╌╌╌╌╌.
- 273     .          .
- 274   ]
- 275   # undo
- 276   assume-console [
- 277     press ctrl-z
- 278   ]
- 279   run [
- 280     editor-event-loop screen, console, e
- 281   ]
- 282   # back to original text
- 283   screen-should-contain [
- 284     .          .
- 285     .a         .
- 286     .╌╌╌╌╌╌╌╌╌╌.
- 287     .          .
- 288   ]
- 289   # cursor should be in the right place
- 290   assume-console [
- 291     type [3]
- 292   ]
- 293   run [
- 294     editor-event-loop screen, console, e
- 295   ]
- 296   screen-should-contain [
- 297     .          .
- 298     .3a        .
- 299     .╌╌╌╌╌╌╌╌╌╌.
- 300     .          .
- 301   ]
- 302 ]
- 303 
- 304 scenario editor-can-undo-typing-enter [
- 305   local-scope
- 306   # create an editor with some text
- 307   assume-screen 10/width, 5/height
- 308   e:&:editor <- new-editor [  abc], 0/left, 10/right
- 309   editor-render screen, e
- 310   # new line
- 311   assume-console [
- 312     left-click 1, 8
- 313     press enter
- 314   ]
- 315   editor-event-loop screen, console, e
- 316   screen-should-contain [
- 317     .          .
- 318     .  abc     .
- 319     .          .
- 320     .╌╌╌╌╌╌╌╌╌╌.
- 321     .          .
- 322   ]
- 323   # line is indented
- 324   3:num/raw <- get *e, cursor-row:offset
- 325   4:num/raw <- get *e, cursor-column:offset
- 326   memory-should-contain [
- 327     3 <- 2
- 328     4 <- 2
- 329   ]
- 330   # undo
- 331   assume-console [
- 332     press ctrl-z
- 333   ]
- 334   run [
- 335     editor-event-loop screen, console, e
- 336   ]
- 337   3:num/raw <- get *e, cursor-row:offset
- 338   4:num/raw <- get *e, cursor-column:offset
- 339   memory-should-contain [
- 340     3 <- 1
- 341     4 <- 5
- 342   ]
- 343   # back to original text
- 344   screen-should-contain [
- 345     .          .
- 346     .  abc     .
- 347     .╌╌╌╌╌╌╌╌╌╌.
- 348     .          .
- 349   ]
- 350   # cursor should be at end of line
- 351   assume-console [
- 352     type [1]
- 353   ]
- 354   run [
- 355     editor-event-loop screen, console, e
- 356   ]
- 357   screen-should-contain [
- 358     .          .
- 359     .  abc1    .
- 360     .╌╌╌╌╌╌╌╌╌╌.
- 361     .          .
- 362   ]
- 363 ]
- 364 
- 365 # redo typing
- 366 
- 367 scenario editor-redo-typing [
- 368   local-scope
- 369   # create an editor, type something, undo
- 370   assume-screen 10/width, 5/height
- 371   e:&:editor <- new-editor [a], 0/left, 10/right
- 372   editor-render screen, e
- 373   assume-console [
- 374     type [012]
- 375     press ctrl-z
- 376   ]
- 377   editor-event-loop screen, console, e
- 378   screen-should-contain [
- 379     .          .
- 380     .a         .
- 381     .╌╌╌╌╌╌╌╌╌╌.
- 382     .          .
- 383   ]
- 384   # redo
- 385   assume-console [
- 386     press ctrl-y
- 387   ]
- 388   run [
- 389     editor-event-loop screen, console, e
- 390   ]
- 391   # all characters must be back
- 392   screen-should-contain [
- 393     .          .
- 394     .012a      .
- 395     .╌╌╌╌╌╌╌╌╌╌.
- 396     .          .
- 397   ]
- 398   # cursor should be in the right place
- 399   assume-console [
- 400     type [3]
- 401   ]
- 402   run [
- 403     editor-event-loop screen, console, e
- 404   ]
- 405   screen-should-contain [
- 406     .          .
- 407     .0123a     .
- 408     .╌╌╌╌╌╌╌╌╌╌.
- 409     .          .
- 410   ]
- 411 ]
- 412 
- 413 after <handle-redo> [
- 414   {
- 415     typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant
- 416     break-unless is-insert?
- 417     before-cursor <- get *editor, before-cursor:offset
- 418     insert-from:&:duplex-list:char <- get typing, insert-from:offset  # ignore insert-to because it's already been spliced away
- 419     # assert insert-to matches next(before-cursor)
- 420     splice before-cursor, insert-from
- 421     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
- 422     cursor-row <- get typing, after-row:offset
- 423     *editor <- put *editor, cursor-row:offset, cursor-row
- 424     cursor-column <- get typing, after-column:offset
- 425     *editor <- put *editor, cursor-column:offset, cursor-column
- 426     top:&:duplex-list:char <- get typing, after-top-of-screen:offset
- 427     *editor <- put *editor, top-of-screen:offset, top
- 428   }
- 429 ]
- 430 
- 431 scenario editor-redo-typing-empty [
- 432   local-scope
- 433   # create an editor, type something, undo
- 434   assume-screen 10/width, 5/height
- 435   e:&:editor <- new-editor [], 0/left, 10/right
- 436   editor-render screen, e
- 437   assume-console [
- 438     type [012]
- 439     press ctrl-z
- 440   ]
- 441   editor-event-loop screen, console, e
- 442   screen-should-contain [
- 443     .          .
- 444     .          .
- 445     .╌╌╌╌╌╌╌╌╌╌.
- 446     .          .
- 447   ]
- 448   # redo
- 449   assume-console [
- 450     press ctrl-y
- 451   ]
- 452   run [
- 453     editor-event-loop screen, console, e
- 454   ]
- 455   # all characters must be back
- 456   screen-should-contain [
- 457     .          .
- 458     .012       .
- 459     .╌╌╌╌╌╌╌╌╌╌.
- 460     .          .
- 461   ]
- 462   # cursor should be in the right place
- 463   assume-console [
- 464     type [3]
- 465   ]
- 466   run [
- 467     editor-event-loop screen, console, e
- 468   ]
- 469   screen-should-contain [
- 470     .          .
- 471     .0123      .
- 472     .╌╌╌╌╌╌╌╌╌╌.
- 473     .          .
- 474   ]
- 475 ]
- 476 
- 477 scenario editor-work-clears-redo-stack [
- 478   local-scope
- 479   # create an editor with some text, do some work, undo
- 480   assume-screen 10/width, 5/height
- 481   contents:text <- new [abc
- 482 def
- 483 ghi]
- 484   e:&:editor <- new-editor contents, 0/left, 10/right
- 485   editor-render screen, e
- 486   assume-console [
- 487     type [1]
- 488     press ctrl-z
- 489   ]
- 490   editor-event-loop screen, console, e
- 491   # do some more work
- 492   assume-console [
- 493     type [0]
- 494   ]
- 495   editor-event-loop screen, console, e
- 496   screen-should-contain [
- 497     .          .
- 498     .0abc      .
- 499     .def       .
- 500     .ghi       .
- 501     .╌╌╌╌╌╌╌╌╌╌.
- 502   ]
- 503   # redo
- 504   assume-console [
- 505     press ctrl-y
- 506   ]
- 507   run [
- 508     editor-event-loop screen, console, e
- 509   ]
- 510   # nothing should happen
- 511   screen-should-contain [
- 512     .          .
- 513     .0abc      .
- 514     .def       .
- 515     .ghi       .
- 516     .╌╌╌╌╌╌╌╌╌╌.
- 517   ]
- 518 ]
- 519 
- 520 scenario editor-can-redo-typing-and-enter-and-tab [
- 521   local-scope
- 522   # create an editor
- 523   assume-screen 10/width, 5/height
- 524   e:&:editor <- new-editor [], 0/left, 10/right
- 525   editor-render screen, e
- 526   # insert some text and tabs, hit enter, some more text and tabs
- 527   assume-console [
- 528     press tab
- 529     type [ab]
- 530     press tab
- 531     type [cd]
- 532     press enter
- 533     press tab
- 534     type [efg]
- 535   ]
- 536   editor-event-loop screen, console, e
- 537   screen-should-contain [
- 538     .          .
- 539     .  ab  cd  .
- 540     .    efg   .
- 541     .╌╌╌╌╌╌╌╌╌╌.
- 542     .          .
- 543   ]
- 544   3:num/raw <- get *e, cursor-row:offset
- 545   4:num/raw <- get *e, cursor-column:offset
- 546   memory-should-contain [
- 547     3 <- 2
- 548     4 <- 7
- 549   ]
- 550   # undo
- 551   assume-console [
- 552     press ctrl-z
- 553   ]
- 554   run [
- 555     editor-event-loop screen, console, e
- 556   ]
- 557   # typing in second line deleted, but not indent
- 558   3:num/raw <- get *e, cursor-row:offset
- 559   4:num/raw <- get *e, cursor-column:offset
- 560   memory-should-contain [
- 561     3 <- 2
- 562     4 <- 2
- 563   ]
- 564   screen-should-contain [
- 565     .          .
- 566     .  ab  cd  .
- 567     .          .
- 568     .╌╌╌╌╌╌╌╌╌╌.
- 569     .          .
- 570   ]
- 571   # undo again
- 572   assume-console [
- 573     press ctrl-z
- 574   ]
- 575   run [
- 576     editor-event-loop screen, console, e
- 577   ]
- 578   # indent and newline deleted
- 579   3:num/raw <- get *e, cursor-row:offset
- 580   4:num/raw <- get *e, cursor-column:offset
- 581   memory-should-contain [
- 582     3 <- 1
- 583     4 <- 8
- 584   ]
- 585   screen-should-contain [
- 586     .          .
- 587     .  ab  cd  .
- 588     .╌╌╌╌╌╌╌╌╌╌.
- 589     .          .
- 590   ]
- 591   # undo again
- 592   assume-console [
- 593     press ctrl-z
- 594   ]
- 595   run [
- 596     editor-event-loop screen, console, e
- 597   ]
- 598   # empty screen
- 599   3:num/raw <- get *e, cursor-row:offset
- 600   4:num/raw <- get *e, cursor-column:offset
- 601   memory-should-contain [
- 602     3 <- 1
- 603     4 <- 0
- 604   ]
- 605   screen-should-contain [
- 606     .          .
- 607     .          .
- 608     .╌╌╌╌╌╌╌╌╌╌.
- 609     .          .
- 610   ]
- 611   # redo
- 612   assume-console [
- 613     press ctrl-y
- 614   ]
- 615   run [
- 616     editor-event-loop screen, console, e
- 617   ]
- 618   # first line inserted
- 619   3:num/raw <- get *e, cursor-row:offset
- 620   4:num/raw <- get *e, cursor-column:offset
- 621   memory-should-contain [
- 622     3 <- 1
- 623     4 <- 8
- 624   ]
- 625   screen-should-contain [
- 626     .          .
- 627     .  ab  cd  .
- 628     .╌╌╌╌╌╌╌╌╌╌.
- 629     .          .
- 630   ]
- 631   # redo again
- 632   assume-console [
- 633     press ctrl-y
- 634   ]
- 635   run [
- 636     editor-event-loop screen, console, e
- 637   ]
- 638   # newline and indent inserted
- 639   3:num/raw <- get *e, cursor-row:offset
- 640   4:num/raw <- get *e, cursor-column:offset
- 641   memory-should-contain [
- 642     3 <- 2
- 643     4 <- 2
- 644   ]
- 645   screen-should-contain [
- 646     .          .
- 647     .  ab  cd  .
- 648     .          .
- 649     .╌╌╌╌╌╌╌╌╌╌.
- 650     .          .
- 651   ]
- 652   # redo again
- 653   assume-console [
- 654     press ctrl-y
- 655   ]
- 656   run [
- 657     editor-event-loop screen, console, e
- 658   ]
- 659   # indent and newline deleted
- 660   3:num/raw <- get *e, cursor-row:offset
- 661   4:num/raw <- get *e, cursor-column:offset
- 662   memory-should-contain [
- 663     3 <- 2
- 664     4 <- 7
- 665   ]
- 666   screen-should-contain [
- 667     .          .
- 668     .  ab  cd  .
- 669     .    efg   .
- 670     .╌╌╌╌╌╌╌╌╌╌.
- 671     .          .
- 672   ]
- 673 ]
- 674 
- 675 # undo cursor movement and scroll
- 676 
- 677 scenario editor-can-undo-touch [
- 678   local-scope
- 679   # create an editor with some text
- 680   assume-screen 10/width, 5/height
- 681   contents:text <- new [abc
- 682 def
- 683 ghi]
- 684   e:&:editor <- new-editor contents, 0/left, 10/right
- 685   editor-render screen, e
- 686   # move the cursor
- 687   assume-console [
- 688     left-click 3, 1
- 689   ]
- 690   editor-event-loop screen, console, e
- 691   # undo
- 692   assume-console [
- 693     press ctrl-z
- 694   ]
- 695   run [
- 696     editor-event-loop screen, console, e
- 697   ]
- 698   # click undone
- 699   3:num/raw <- get *e, cursor-row:offset
- 700   4:num/raw <- get *e, cursor-column:offset
- 701   memory-should-contain [
- 702     3 <- 1
- 703     4 <- 0
- 704   ]
- 705   # cursor should be in the right place
- 706   assume-console [
- 707     type [1]
- 708   ]
- 709   run [
- 710     editor-event-loop screen, console, e
- 711   ]
- 712   screen-should-contain [
- 713     .          .
- 714     .1abc      .
- 715     .def       .
- 716     .ghi       .
- 717     .╌╌╌╌╌╌╌╌╌╌.
- 718   ]
- 719 ]
- 720 
- 721 after <begin-move-cursor> [
- 722   cursor-row-before:num <- get *editor, cursor-row:offset
- 723   cursor-column-before:num <- get *editor, cursor-column:offset
- 724   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
- 725 ]
- 726 before <end-move-cursor> [
- 727   top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
- 728   cursor-row:num <- get *editor, cursor-row:offset
- 729   cursor-column:num <- get *editor, cursor-column:offset
- 730   {
- 731     break-unless undo-coalesce-tag
- 732     # if previous operation was also a move, and also had the same coalesce
- 733     # tag, coalesce with it
- 734     undo:&:list:&:operation <- get *editor, undo:offset
- 735     break-unless undo
- 736     op:&:operation <- first undo
- 737     move:move-operation, is-move?:bool <- maybe-convert *op, move:variant
- 738     break-unless is-move?
- 739     previous-coalesce-tag:num <- get move, tag:offset
- 740     coalesce?:bool <- equal undo-coalesce-tag, previous-coalesce-tag
- 741     break-unless coalesce?
- 742     move <- put move, after-row:offset, cursor-row
- 743     move <- put move, after-column:offset, cursor-column
- 744     move <- put move, after-top-of-screen:offset, top-after
- 745     *op <- merge 1/move-operation, move
- 746     break +done-adding-move-operation
- 747   }
- 748   op:&:operation <- new operation:type
- 749   *op <- merge 1/move-operation, cursor-row-before, cursor-column-before, top-before, cursor-row/after, cursor-column/after, top-after, undo-coalesce-tag
- 750   editor <- add-operation editor, op
- 751   +done-adding-move-operation
- 752 ]
- 753 
- 754 after <handle-undo> [
- 755   {
- 756     move:move-operation, is-move?:bool <- maybe-convert *op, move:variant
- 757     break-unless is-move?
- 758     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
- 759     cursor-row <- get move, before-row:offset
- 760     *editor <- put *editor, cursor-row:offset, cursor-row
- 761     cursor-column <- get move, before-column:offset
- 762     *editor <- put *editor, cursor-column:offset, cursor-column
- 763     top:&:duplex-list:char <- get move, before-top-of-screen:offset
- 764     *editor <- put *editor, top-of-screen:offset, top
- 765   }
- 766 ]
- 767 
- 768 scenario editor-can-undo-scroll [
- 769   local-scope
- 770   # screen has 1 line for menu + 3 lines
- 771   assume-screen 5/width, 4/height
- 772   # editor contains a wrapped line
- 773   contents:text <- new [a
- 774 b
- 775 cdefgh]
- 776   e:&:editor <- new-editor contents, 0/left, 5/right
- 777   # position cursor at end of screen and try to move right
- 778   assume-console [
- 779     left-click 3, 3
- 780     press right-arrow
- 781   ]
- 782   editor-event-loop screen, console, e
- 783   3:num/raw <- get *e, cursor-row:offset
- 784   4:num/raw <- get *e, cursor-column:offset
- 785   # screen scrolls
- 786   screen-should-contain [
- 787     .     .
- 788     .b    .
- 789     .cdef↩.
- 790     .gh   .
- 791   ]
- 792   memory-should-contain [
- 793     3 <- 3
- 794     4 <- 0
- 795   ]
- 796   # undo
- 797   assume-console [
- 798     press ctrl-z
- 799   ]
- 800   run [
- 801     editor-event-loop screen, console, e
- 802   ]
- 803   # cursor moved back
- 804   3:num/raw <- get *e, cursor-row:offset
- 805   4:num/raw <- get *e, cursor-column:offset
- 806   memory-should-contain [
- 807     3 <- 3
- 808     4 <- 3
- 809   ]
- 810   # scroll undone
- 811   screen-should-contain [
- 812     .     .
- 813     .a    .
- 814     .b    .
- 815     .cdef↩.
- 816   ]
- 817   # cursor should be in the right place
- 818   assume-console [
- 819     type [1]
- 820   ]
- 821   run [
- 822     editor-event-loop screen, console, e
- 823   ]
- 824   screen-should-contain [
- 825     .     .
- 826     .b    .
- 827     .cde1↩.
- 828     .fgh  .
- 829   ]
- 830 ]
- 831 
- 832 scenario editor-can-undo-left-arrow [
- 833   local-scope
- 834   # create an editor with some text
- 835   assume-screen 10/width, 5/height
- 836   contents:text <- new [abc
- 837 def
- 838 ghi]
- 839   e:&:editor <- new-editor contents, 0/left, 10/right
- 840   editor-render screen, e
- 841   # move the cursor
- 842   assume-console [
- 843     left-click 3, 1
- 844     press left-arrow
- 845   ]
- 846   editor-event-loop screen, console, e
- 847   # undo
- 848   assume-console [
- 849     press ctrl-z
- 850   ]
- 851   run [
- 852     editor-event-loop screen, console, e
- 853   ]
- 854   # cursor moves back
- 855   3:num/raw <- get *e, cursor-row:offset
- 856   4:num/raw <- get *e, cursor-column:offset
- 857   memory-should-contain [
- 858     3 <- 3
- 859     4 <- 1
- 860   ]
- 861   # cursor should be in the right place
- 862   assume-console [
- 863     type [1]
- 864   ]
- 865   run [
- 866     editor-event-loop screen, console, e
- 867   ]
- 868   screen-should-contain [
- 869     .          .
- 870     .abc       .
- 871     .def       .
- 872     .g1hi      .
- 873     .╌╌╌╌╌╌╌╌╌╌.
- 874   ]
- 875 ]
- 876 
- 877 scenario editor-can-undo-up-arrow [
- 878   local-scope
- 879   # create an editor with some text
- 880   assume-screen 10/width, 5/height
- 881   contents:text <- new [abc
- 882 def
- 883 ghi]
- 884   e:&:editor <- new-editor contents, 0/left, 10/right
- 885   editor-render screen, e
- 886   # move the cursor
- 887   assume-console [
- 888     left-click 3, 1
- 889     press up-arrow
- 890   ]
- 891   editor-event-loop screen, console, e
- 892   3:num/raw <- get *e, cursor-row:offset
- 893   4:num/raw <- get *e, cursor-column:offset
- 894   memory-should-contain [
- 895     3 <- 2
- 896     4 <- 1
- 897   ]
- 898   # undo
- 899   assume-console [
- 900     press ctrl-z
- 901   ]
- 902   run [
- 903     editor-event-loop screen, console, e
- 904   ]
- 905   # cursor moves back
- 906   3:num/raw <- get *e, cursor-row:offset
- 907   4:num/raw <- get *e, cursor-column:offset
- 908   memory-should-contain [
- 909     3 <- 3
- 910     4 <- 1
- 911   ]
- 912   # cursor should be in the right place
- 913   assume-console [
- 914     type [1]
- 915   ]
- 916   run [
- 917     editor-event-loop screen, console, e
- 918   ]
- 919   screen-should-contain [
- 920     .          .
- 921     .abc       .
- 922     .def       .
- 923     .g1hi      .
- 924     .╌╌╌╌╌╌╌╌╌╌.
- 925   ]
- 926 ]
- 927 
- 928 scenario editor-can-undo-down-arrow [
- 929   local-scope
- 930   # create an editor with some text
- 931   assume-screen 10/width, 5/height
- 932   contents:text <- new [abc
- 933 def
- 934 ghi]
- 935   e:&:editor <- new-editor contents, 0/left, 10/right
- 936   editor-render screen, e
- 937   # move the cursor
- 938   assume-console [
- 939     left-click 2, 1
- 940     press down-arrow
- 941   ]
- 942   editor-event-loop screen, console, e
- 943   # undo
- 944   assume-console [
- 945     press ctrl-z
- 946   ]
- 947   run [
- 948     editor-event-loop screen, console, e
- 949   ]
- 950   # cursor moves back
- 951   3:num/raw <- get *e, cursor-row:offset
- 952   4:num/raw <- get *e, cursor-column:offset
- 953   memory-should-contain [
- 954     3 <- 2
- 955     4 <- 1
- 956   ]
- 957   # cursor should be in the right place
- 958   assume-console [
- 959     type [1]
- 960   ]
- 961   run [
- 962     editor-event-loop screen, console, e
- 963   ]
- 964   screen-should-contain [
- 965     .          .
- 966     .abc       .
- 967     .d1ef      .
- 968     .ghi       .
- 969     .╌╌╌╌╌╌╌╌╌╌.
- 970   ]
- 971 ]
- 972 
- 973 scenario editor-can-undo-ctrl-f [
- 974   local-scope
- 975   # create an editor with multiple pages of text
- 976   assume-screen 10/width, 5/height
- 977   contents:text <- new [a
- 978 b
- 979 c
- 980 d
- 981 e
- 982 f]
- 983   e:&:editor <- new-editor contents, 0/left, 10/right
- 984   editor-render screen, e
- 985   # scroll the page
- 986   assume-console [
- 987     press ctrl-f
- 988   ]
- 989   editor-event-loop screen, console, e
- 990   # undo
- 991   assume-console [
- 992     press ctrl-z
- 993   ]
- 994   run [
- 995     editor-event-loop screen, console, e
- 996   ]
- 997   # screen should again show page 1
- 998   screen-should-contain [
- 999     .          .
-1000     .a         .
-1001     .b         .
-1002     .c         .
-1003     .d         .
-1004   ]
-1005 ]
-1006 
-1007 scenario editor-can-undo-page-down [
-1008   local-scope
-1009   # create an editor with multiple pages of text
-1010   assume-screen 10/width, 5/height
-1011   contents:text <- new [a
-1012 b
-1013 c
-1014 d
-1015 e
-1016 f]
-1017   e:&:editor <- new-editor contents, 0/left, 10/right
-1018   editor-render screen, e
-1019   # scroll the page
-1020   assume-console [
-1021     press page-down
-1022   ]
-1023   editor-event-loop screen, console, e
-1024   # undo
-1025   assume-console [
-1026     press ctrl-z
-1027   ]
-1028   run [
-1029     editor-event-loop screen, console, e
-1030   ]
-1031   # screen should again show page 1
-1032   screen-should-contain [
-1033     .          .
-1034     .a         .
-1035     .b         .
-1036     .c         .
-1037     .d         .
-1038   ]
-1039 ]
-1040 
-1041 scenario editor-can-undo-ctrl-b [
-1042   local-scope
-1043   # create an editor with multiple pages of text
-1044   assume-screen 10/width, 5/height
-1045   contents:text <- new [a
-1046 b
-1047 c
-1048 d
-1049 e
-1050 f]
-1051   e:&:editor <- new-editor contents, 0/left, 10/right
-1052   editor-render screen, e
-1053   # scroll the page down and up
-1054   assume-console [
-1055     press page-down
-1056     press ctrl-b
-1057   ]
-1058   editor-event-loop screen, console, e
-1059   # undo
-1060   assume-console [
-1061     press ctrl-z
-1062   ]
-1063   run [
-1064     editor-event-loop screen, console, e
-1065   ]
-1066   # screen should again show page 2
-1067   screen-should-contain [
-1068     .          .
-1069     .d         .
-1070     .e         .
-1071     .f         .
-1072     .╌╌╌╌╌╌╌╌╌╌.
-1073   ]
-1074 ]
-1075 
-1076 scenario editor-can-undo-page-up [
-1077   local-scope
-1078   # create an editor with multiple pages of text
-1079   assume-screen 10/width, 5/height
-1080   contents:text <- new [a
-1081 b
-1082 c
-1083 d
-1084 e
-1085 f]
-1086   e:&:editor <- new-editor contents, 0/left, 10/right
-1087   editor-render screen, e
-1088   # scroll the page down and up
-1089   assume-console [
-1090     press page-down
-1091     press page-up
-1092   ]
-1093   editor-event-loop screen, console, e
-1094   # undo
-1095   assume-console [
-1096     press ctrl-z
-1097   ]
-1098   run [
-1099     editor-event-loop screen, console, e
-1100   ]
-1101   # screen should again show page 2
-1102   screen-should-contain [
-1103     .          .
-1104     .d         .
-1105     .e         .
-1106     .f         .
-1107     .╌╌╌╌╌╌╌╌╌╌.
-1108   ]
-1109 ]
-1110 
-1111 scenario editor-can-undo-ctrl-a [
-1112   local-scope
-1113   # create an editor with some text
-1114   assume-screen 10/width, 5/height
-1115   contents:text <- new [abc
-1116 def
-1117 ghi]
-1118   e:&:editor <- new-editor contents, 0/left, 10/right
-1119   editor-render screen, e
-1120   # move the cursor, then to start of line
-1121   assume-console [
-1122     left-click 2, 1
-1123     press ctrl-a
-1124   ]
-1125   editor-event-loop screen, console, e
-1126   # undo
-1127   assume-console [
-1128     press ctrl-z
-1129   ]
-1130   run [
-1131     editor-event-loop screen, console, e
-1132   ]
-1133   # cursor moves back
-1134   3:num/raw <- get *e, cursor-row:offset
-1135   4:num/raw <- get *e, cursor-column:offset
-1136   memory-should-contain [
-1137     3 <- 2
-1138     4 <- 1
-1139   ]
-1140   # cursor should be in the right place
-1141   assume-console [
-1142     type [1]
-1143   ]
-1144   run [
-1145     editor-event-loop screen, console, e
-1146   ]
-1147   screen-should-contain [
-1148     .          .
-1149     .abc       .
-1150     .d1ef      .
-1151     .ghi       .
-1152     .╌╌╌╌╌╌╌╌╌╌.
-1153   ]
-1154 ]
-1155 
-1156 scenario editor-can-undo-home [
-1157   local-scope
-1158   # create an editor with some text
-1159   assume-screen 10/width, 5/height
-1160   contents:text <- new [abc
-1161 def
-1162 ghi]
-1163   e:&:editor <- new-editor contents, 0/left, 10/right
-1164   editor-render screen, e
-1165   # move the cursor, then to start of line
-1166   assume-console [
-1167     left-click 2, 1
-1168     press home
-1169   ]
-1170   editor-event-loop screen, console, e
-1171   # undo
-1172   assume-console [
-1173     press ctrl-z
-1174   ]
-1175   run [
-1176     editor-event-loop screen, console, e
-1177   ]
-1178   # cursor moves back
-1179   3:num/raw <- get *e, cursor-row:offset
-1180   4:num/raw <- get *e, cursor-column:offset
-1181   memory-should-contain [
-1182     3 <- 2
-1183     4 <- 1
-1184   ]
-1185   # cursor should be in the right place
-1186   assume-console [
-1187     type [1]
-1188   ]
-1189   run [
-1190     editor-event-loop screen, console, e
-1191   ]
-1192   screen-should-contain [
-1193     .          .
-1194     .abc       .
-1195     .d1ef      .
-1196     .ghi       .
-1197     .╌╌╌╌╌╌╌╌╌╌.
-1198   ]
-1199 ]
-1200 
-1201 scenario editor-can-undo-ctrl-e [
-1202   local-scope
-1203   # create an editor with some text
-1204   assume-screen 10/width, 5/height
-1205   contents:text <- new [abc
-1206 def
-1207 ghi]
-1208   e:&:editor <- new-editor contents, 0/left, 10/right
-1209   editor-render screen, e
-1210   # move the cursor, then to start of line
-1211   assume-console [
-1212     left-click 2, 1
-1213     press ctrl-e
-1214   ]
-1215   editor-event-loop screen, console, e
-1216   # undo
-1217   assume-console [
-1218     press ctrl-z
-1219   ]
-1220   run [
-1221     editor-event-loop screen, console, e
-1222   ]
-1223   # cursor moves back
-1224   3:num/raw <- get *e, cursor-row:offset
-1225   4:num/raw <- get *e, cursor-column:offset
-1226   memory-should-contain [
-1227     3 <- 2
-1228     4 <- 1
-1229   ]
-1230   # cursor should be in the right place
-1231   assume-console [
-1232     type [1]
-1233   ]
-1234   run [
-1235     editor-event-loop screen, console, e
-1236   ]
-1237   screen-should-contain [
-1238     .          .
-1239     .abc       .
-1240     .d1ef      .
-1241     .ghi       .
-1242     .╌╌╌╌╌╌╌╌╌╌.
-1243   ]
-1244 ]
-1245 
-1246 scenario editor-can-undo-end [
-1247   local-scope
-1248   # create an editor with some text
-1249   assume-screen 10/width, 5/height
-1250   contents:text <- new [abc
-1251 def
-1252 ghi]
-1253   e:&:editor <- new-editor contents, 0/left, 10/right
-1254   editor-render screen, e
-1255   # move the cursor, then to start of line
-1256   assume-console [
-1257     left-click 2, 1
-1258     press end
-1259   ]
-1260   editor-event-loop screen, console, e
-1261   # undo
-1262   assume-console [
-1263     press ctrl-z
-1264   ]
-1265   run [
-1266     editor-event-loop screen, console, e
-1267   ]
-1268   # cursor moves back
-1269   3:num/raw <- get *e, cursor-row:offset
-1270   4:num/raw <- get *e, cursor-column:offset
-1271   memory-should-contain [
-1272     3 <- 2
-1273     4 <- 1
-1274   ]
-1275   # cursor should be in the right place
-1276   assume-console [
-1277     type [1]
-1278   ]
-1279   run [
-1280     editor-event-loop screen, console, e
-1281   ]
-1282   screen-should-contain [
-1283     .          .
-1284     .abc       .
-1285     .d1ef      .
-1286     .ghi       .
-1287     .╌╌╌╌╌╌╌╌╌╌.
-1288   ]
-1289 ]
-1290 
-1291 scenario editor-can-undo-multiple-arrows-in-the-same-direction [
-1292   local-scope
-1293   # create an editor with some text
-1294   assume-screen 10/width, 5/height
-1295   contents:text <- new [abc
-1296 def
-1297 ghi]
-1298   e:&:editor <- new-editor contents, 0/left, 10/right
-1299   editor-render screen, e
-1300   # move the cursor
-1301   assume-console [
-1302     left-click 2, 1
-1303     press right-arrow
-1304     press right-arrow
-1305     press up-arrow
-1306   ]
-1307   editor-event-loop screen, console, e
-1308   3:num/raw <- get *e, cursor-row:offset
-1309   4:num/raw <- get *e, cursor-column:offset
-1310   memory-should-contain [
-1311     3 <- 1
-1312     4 <- 3
-1313   ]
-1314   # undo
-1315   assume-console [
-1316     press ctrl-z
-1317   ]
-1318   run [
-1319     editor-event-loop screen, console, e
-1320   ]
-1321   # up-arrow is undone
-1322   3:num/raw <- get *e, cursor-row:offset
-1323   4:num/raw <- get *e, cursor-column:offset
-1324   memory-should-contain [
-1325     3 <- 2
-1326     4 <- 3
-1327   ]
-1328   # undo again
-1329   assume-console [
-1330     press ctrl-z
-1331   ]
-1332   run [
-1333     editor-event-loop screen, console, e
-1334   ]
-1335   # both right-arrows are undone
-1336   3:num/raw <- get *e, cursor-row:offset
-1337   4:num/raw <- get *e, cursor-column:offset
-1338   memory-should-contain [
-1339     3 <- 2
-1340     4 <- 1
-1341   ]
-1342 ]
-1343 
-1344 # redo cursor movement and scroll
-1345 
-1346 scenario editor-redo-touch [
-1347   local-scope
-1348   # create an editor with some text, click on a character, undo
-1349   assume-screen 10/width, 5/height
-1350   contents:text <- new [abc
-1351 def
-1352 ghi]
-1353   e:&:editor <- new-editor contents, 0/left, 10/right
-1354   editor-render screen, e
-1355   assume-console [
-1356     left-click 3, 1
-1357     press ctrl-z
-1358   ]
-1359   editor-event-loop screen, console, e
-1360   # redo
-1361   assume-console [
-1362     press ctrl-y
-1363   ]
-1364   run [
-1365     editor-event-loop screen, console, e
-1366   ]
-1367   # cursor moves to left-click
-1368   3:num/raw <- get *e, cursor-row:offset
-1369   4:num/raw <- get *e, cursor-column:offset
-1370   memory-should-contain [
-1371     3 <- 3
-1372     4 <- 1
-1373   ]
-1374   # cursor should be in the right place
-1375   assume-console [
-1376     type [1]
-1377   ]
-1378   run [
-1379     editor-event-loop screen, console, e
-1380   ]
-1381   screen-should-contain [
-1382     .          .
-1383     .abc       .
-1384     .def       .
-1385     .g1hi      .
-1386     .╌╌╌╌╌╌╌╌╌╌.
-1387   ]
-1388 ]
-1389 
-1390 after <handle-redo> [
-1391   {
-1392     move:move-operation, is-move?:bool <- maybe-convert *op, move:variant
-1393     break-unless is-move?
-1394     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-1395     cursor-row <- get move, after-row:offset
-1396     *editor <- put *editor, cursor-row:offset, cursor-row
-1397     cursor-column <- get move, after-column:offset
-1398     *editor <- put *editor, cursor-column:offset, cursor-column
-1399     top:&:duplex-list:char <- get move, after-top-of-screen:offset
-1400     *editor <- put *editor, top-of-screen:offset, top
-1401   }
-1402 ]
-1403 
-1404 scenario editor-separates-undo-insert-from-undo-cursor-move [
-1405   local-scope
-1406   # create an editor, type some text, move the cursor, type some more text
-1407   assume-screen 10/width, 5/height
-1408   e:&:editor <- new-editor [], 0/left, 10/right
-1409   editor-render screen, e
-1410   assume-console [
-1411     type [abc]
-1412     left-click 1, 1
-1413     type [d]
-1414   ]
-1415   editor-event-loop screen, console, e
-1416   3:num/raw <- get *e, cursor-row:offset
-1417   4:num/raw <- get *e, cursor-column:offset
-1418   screen-should-contain [
-1419     .          .
-1420     .adbc      .
-1421     .╌╌╌╌╌╌╌╌╌╌.
-1422     .          .
-1423   ]
-1424   memory-should-contain [
-1425     3 <- 1
-1426     4 <- 2
-1427   ]
-1428   # undo
-1429   assume-console [
-1430     press ctrl-z
-1431   ]
-1432   run [
-1433     editor-event-loop screen, console, e
-1434     3:num/raw <- get *e, cursor-row:offset
-1435     4:num/raw <- get *e, cursor-column:offset
-1436   ]
-1437   # last letter typed is deleted
-1438   screen-should-contain [
-1439     .          .
-1440     .abc       .
-1441     .╌╌╌╌╌╌╌╌╌╌.
-1442     .          .
-1443   ]
-1444   memory-should-contain [
-1445     3 <- 1
-1446     4 <- 1
-1447   ]
-1448   # undo again
-1449   assume-console [
-1450     press ctrl-z
-1451   ]
-1452   run [
-1453     editor-event-loop screen, console, e
-1454     3:num/raw <- get *e, cursor-row:offset
-1455     4:num/raw <- get *e, cursor-column:offset
-1456   ]
-1457   # no change to screen; cursor moves
-1458   screen-should-contain [
-1459     .          .
-1460     .abc       .
-1461     .╌╌╌╌╌╌╌╌╌╌.
-1462     .          .
-1463   ]
-1464   memory-should-contain [
-1465     3 <- 1
-1466     4 <- 3
-1467   ]
-1468   # undo again
-1469   assume-console [
-1470     press ctrl-z
-1471   ]
-1472   run [
-1473     editor-event-loop screen, console, e
-1474     3:num/raw <- get *e, cursor-row:offset
-1475     4:num/raw <- get *e, cursor-column:offset
-1476   ]
-1477   # screen empty
-1478   screen-should-contain [
-1479     .          .
-1480     .          .
-1481     .╌╌╌╌╌╌╌╌╌╌.
-1482     .          .
-1483   ]
-1484   memory-should-contain [
-1485     3 <- 1
-1486     4 <- 0
-1487   ]
-1488   # redo
-1489   assume-console [
-1490     press ctrl-y
-1491   ]
-1492   run [
-1493     editor-event-loop screen, console, e
-1494     3:num/raw <- get *e, cursor-row:offset
-1495     4:num/raw <- get *e, cursor-column:offset
-1496   ]
-1497   # first insert
-1498   screen-should-contain [
-1499     .          .
-1500     .abc       .
-1501     .╌╌╌╌╌╌╌╌╌╌.
-1502     .          .
-1503   ]
-1504   memory-should-contain [
-1505     3 <- 1
-1506     4 <- 3
-1507   ]
-1508   # redo again
-1509   assume-console [
-1510     press ctrl-y
-1511   ]
-1512   run [
-1513     editor-event-loop screen, console, e
-1514     3:num/raw <- get *e, cursor-row:offset
-1515     4:num/raw <- get *e, cursor-column:offset
-1516   ]
-1517   # cursor moves
-1518   screen-should-contain [
-1519     .          .
-1520     .abc       .
-1521     .╌╌╌╌╌╌╌╌╌╌.
-1522     .          .
-1523   ]
-1524   # cursor moves
-1525   memory-should-contain [
-1526     3 <- 1
-1527     4 <- 1
-1528   ]
-1529   # redo again
-1530   assume-console [
-1531     press ctrl-y
-1532   ]
-1533   run [
-1534     editor-event-loop screen, console, e
-1535     3:num/raw <- get *e, cursor-row:offset
-1536     4:num/raw <- get *e, cursor-column:offset
-1537   ]
-1538   # second insert
-1539   screen-should-contain [
-1540     .          .
-1541     .adbc      .
-1542     .╌╌╌╌╌╌╌╌╌╌.
-1543     .          .
-1544   ]
-1545   memory-should-contain [
-1546     3 <- 1
-1547     4 <- 2
-1548   ]
-1549 ]
-1550 
-1551 # undo backspace
-1552 
-1553 scenario editor-can-undo-and-redo-backspace [
-1554   local-scope
-1555   # create an editor
-1556   assume-screen 10/width, 5/height
-1557   e:&:editor <- new-editor [], 0/left, 10/right
-1558   editor-render screen, e
-1559   # insert some text and hit backspace
-1560   assume-console [
-1561     type [abc]
-1562     press backspace
-1563     press backspace
-1564   ]
-1565   editor-event-loop screen, console, e
-1566   screen-should-contain [
-1567     .          .
-1568     .a         .
-1569     .╌╌╌╌╌╌╌╌╌╌.
-1570     .          .
-1571   ]
-1572   3:num/raw <- get *e, cursor-row:offset
-1573   4:num/raw <- get *e, cursor-column:offset
-1574   memory-should-contain [
-1575     3 <- 1
-1576     4 <- 1
-1577   ]
-1578   # undo
-1579   assume-console [
-1580     press ctrl-z
-1581   ]
-1582   run [
-1583     editor-event-loop screen, console, e
-1584   ]
-1585   3:num/raw <- get *e, cursor-row:offset
-1586   4:num/raw <- get *e, cursor-column:offset
-1587   memory-should-contain [
-1588     3 <- 1
-1589     4 <- 3
-1590   ]
-1591   screen-should-contain [
-1592     .          .
-1593     .abc       .
-1594     .╌╌╌╌╌╌╌╌╌╌.
-1595     .          .
-1596   ]
-1597   # redo
-1598   assume-console [
-1599     press ctrl-y
-1600   ]
-1601   run [
-1602     editor-event-loop screen, console, e
-1603   ]
-1604   3:num/raw <- get *e, cursor-row:offset
-1605   4:num/raw <- get *e, cursor-column:offset
-1606   memory-should-contain [
-1607     3 <- 1
-1608     4 <- 1
-1609   ]
-1610   screen-should-contain [
-1611     .          .
-1612     .a         .
-1613     .╌╌╌╌╌╌╌╌╌╌.
-1614     .          .
-1615   ]
-1616 ]
-1617 
-1618 # save operation to undo
-1619 after <begin-backspace-character> [
-1620   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
-1621 ]
-1622 before <end-backspace-character> [
-1623   {
-1624     break-unless backspaced-cell  # backspace failed; don't add an undo operation
-1625     top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
-1626     cursor-row:num <- get *editor, cursor-row:offset
-1627     cursor-column:num <- get *editor, cursor-row:offset
-1628     before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-1629     undo:&:list:&:operation <- get *editor, undo:offset
-1630     {
-1631       # if previous operation was an insert, coalesce this operation with it
-1632       break-unless undo
-1633       op:&:operation <- first undo
-1634       deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant
-1635       break-unless is-delete?
-1636       previous-coalesce-tag:num <- get deletion, tag:offset
-1637       coalesce?:bool <- equal previous-coalesce-tag, 1/coalesce-backspace
-1638       break-unless coalesce?
-1639       deletion <- put deletion, delete-from:offset, before-cursor
-1640       backspaced-so-far:&:duplex-list:char <- get deletion, deleted-text:offset
-1641       splice backspaced-cell, backspaced-so-far
-1642       deletion <- put deletion, deleted-text:offset, backspaced-cell
-1643       deletion <- put deletion, after-row:offset, cursor-row
-1644       deletion <- put deletion, after-column:offset, cursor-column
-1645       deletion <- put deletion, after-top-of-screen:offset, top-after
-1646       *op <- merge 2/delete-operation, deletion
-1647       break +done-adding-backspace-operation
-1648     }
-1649     # if not, create a new operation
-1650     op:&:operation <- new operation:type
-1651     deleted-until:&:duplex-list:char <- next before-cursor
-1652     *op <- merge 2/delete-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, backspaced-cell/deleted, before-cursor/delete-from, deleted-until, 1/coalesce-backspace
-1653     editor <- add-operation editor, op
-1654     +done-adding-backspace-operation
-1655   }
-1656 ]
-1657 
-1658 after <handle-undo> [
-1659   {
-1660     deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant
-1661     break-unless is-delete?
-1662     anchor:&:duplex-list:char <- get deletion, delete-from:offset
-1663     break-unless anchor
-1664     deleted:&:duplex-list:char <- get deletion, deleted-text:offset
-1665     old-cursor:&:duplex-list:char <- last deleted
-1666     splice anchor, deleted
-1667     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-1668     before-cursor <- copy old-cursor
-1669     cursor-row <- get deletion, before-row:offset
-1670     *editor <- put *editor, cursor-row:offset, cursor-row
-1671     cursor-column <- get deletion, before-column:offset
-1672     *editor <- put *editor, cursor-column:offset, cursor-column
-1673     top:&:duplex-list:char <- get deletion, before-top-of-screen:offset
-1674     *editor <- put *editor, top-of-screen:offset, top
-1675   }
-1676 ]
-1677 
-1678 after <handle-redo> [
-1679   {
-1680     deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant
-1681     break-unless is-delete?
-1682     start:&:duplex-list:char <- get deletion, delete-from:offset
-1683     end:&:duplex-list:char <- get deletion, delete-until:offset
-1684     data:&:duplex-list:char <- get *editor, data:offset
-1685     remove-between start, end
-1686     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-1687     cursor-row <- get deletion, after-row:offset
-1688     *editor <- put *editor, cursor-row:offset, cursor-row
-1689     cursor-column <- get deletion, after-column:offset
-1690     *editor <- put *editor, cursor-column:offset, cursor-column
-1691     top:&:duplex-list:char <- get deletion, before-top-of-screen:offset
-1692     *editor <- put *editor, top-of-screen:offset, top
-1693   }
-1694 ]
-1695 
-1696 # undo delete
-1697 
-1698 scenario editor-can-undo-and-redo-delete [
-1699   local-scope
-1700   # create an editor
-1701   assume-screen 10/width, 5/height
-1702   e:&:editor <- new-editor [], 0/left, 10/right
-1703   editor-render screen, e
-1704   # insert some text and hit delete and backspace a few times
-1705   assume-console [
-1706     type [abcdef]
-1707     left-click 1, 2
-1708     press delete
-1709     press backspace
-1710     press delete
-1711     press delete
-1712   ]
-1713   editor-event-loop screen, console, e
-1714   screen-should-contain [
-1715     .          .
-1716     .af        .
-1717     .╌╌╌╌╌╌╌╌╌╌.
-1718     .          .
-1719   ]
-1720   3:num/raw <- get *e, cursor-row:offset
-1721   4:num/raw <- get *e, cursor-column:offset
-1722   memory-should-contain [
-1723     3 <- 1
-1724     4 <- 1
-1725   ]
-1726   # undo deletes
-1727   assume-console [
-1728     press ctrl-z
-1729   ]
-1730   run [
-1731     editor-event-loop screen, console, e
-1732   ]
-1733   3:num/raw <- get *e, cursor-row:offset
-1734   4:num/raw <- get *e, cursor-column:offset
-1735   memory-should-contain [
-1736     3 <- 1
-1737     4 <- 1
-1738   ]
-1739   screen-should-contain [
-1740     .          .
-1741     .adef      .
-1742     .╌╌╌╌╌╌╌╌╌╌.
-1743     .          .
-1744   ]
-1745   # undo backspace
-1746   assume-console [
-1747     press ctrl-z
-1748   ]
-1749   run [
-1750     editor-event-loop screen, console, e
-1751   ]
-1752   3:num/raw <- get *e, cursor-row:offset
-1753   4:num/raw <- get *e, cursor-column:offset
-1754   memory-should-contain [
-1755     3 <- 1
-1756     4 <- 2
-1757   ]
-1758   screen-should-contain [
-1759     .          .
-1760     .abdef     .
-1761     .╌╌╌╌╌╌╌╌╌╌.
-1762     .          .
-1763   ]
-1764   # undo first delete
-1765   assume-console [
-1766     press ctrl-z
-1767   ]
-1768   run [
-1769     editor-event-loop screen, console, e
-1770   ]
-1771   3:num/raw <- get *e, cursor-row:offset
-1772   4:num/raw <- get *e, cursor-column:offset
-1773   memory-should-contain [
-1774     3 <- 1
-1775     4 <- 2
-1776   ]
-1777   screen-should-contain [
-1778     .          .
-1779     .abcdef    .
-1780     .╌╌╌╌╌╌╌╌╌╌.
-1781     .          .
-1782   ]
-1783   # redo first delete
-1784   assume-console [
-1785     press ctrl-y
-1786   ]
-1787   run [
-1788     editor-event-loop screen, console, e
-1789   ]
-1790   # first line inserted
-1791   3:num/raw <- get *e, cursor-row:offset
-1792   4:num/raw <- get *e, cursor-column:offset
-1793   memory-should-contain [
-1794     3 <- 1
-1795     4 <- 2
-1796   ]
-1797   screen-should-contain [
-1798     .          .
-1799     .abdef     .
-1800     .╌╌╌╌╌╌╌╌╌╌.
-1801     .          .
-1802   ]
-1803   # redo backspace
-1804   assume-console [
-1805     press ctrl-y
-1806   ]
-1807   run [
-1808     editor-event-loop screen, console, e
-1809   ]
-1810   # first line inserted
-1811   3:num/raw <- get *e, cursor-row:offset
-1812   4:num/raw <- get *e, cursor-column:offset
-1813   memory-should-contain [
-1814     3 <- 1
-1815     4 <- 1
-1816   ]
-1817   screen-should-contain [
-1818     .          .
-1819     .adef      .
-1820     .╌╌╌╌╌╌╌╌╌╌.
-1821     .          .
-1822   ]
-1823   # redo deletes
-1824   assume-console [
-1825     press ctrl-y
-1826   ]
-1827   run [
-1828     editor-event-loop screen, console, e
-1829   ]
-1830   # first line inserted
-1831   3:num/raw <- get *e, cursor-row:offset
-1832   4:num/raw <- get *e, cursor-column:offset
-1833   memory-should-contain [
-1834     3 <- 1
-1835     4 <- 1
-1836   ]
-1837   screen-should-contain [
-1838     .          .
-1839     .af        .
-1840     .╌╌╌╌╌╌╌╌╌╌.
-1841     .          .
-1842   ]
-1843 ]
-1844 
-1845 after <begin-delete-character> [
-1846   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
-1847 ]
-1848 before <end-delete-character> [
-1849   {
-1850     break-unless deleted-cell  # delete failed; don't add an undo operation
-1851     top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
-1852     cursor-row:num <- get *editor, cursor-row:offset
-1853     cursor-column:num <- get *editor, cursor-column:offset
-1854     before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-1855     undo:&:list:&:operation <- get *editor, undo:offset
-1856     {
-1857       # if previous operation was an insert, coalesce this operation with it
-1858       break-unless undo
-1859       op:&:operation <- first undo
-1860       deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant
-1861       break-unless is-delete?
-1862       previous-coalesce-tag:num <- get deletion, tag:offset
-1863       coalesce?:bool <- equal previous-coalesce-tag, 2/coalesce-delete
-1864       break-unless coalesce?
-1865       delete-until:&:duplex-list:char <- next before-cursor
-1866       deletion <- put deletion, delete-until:offset, delete-until
-1867       deleted-so-far:&:duplex-list:char <- get deletion, deleted-text:offset
-1868       deleted-so-far <- append deleted-so-far, deleted-cell
-1869       deletion <- put deletion, deleted-text:offset, deleted-so-far
-1870       deletion <- put deletion, after-row:offset, cursor-row
-1871       deletion <- put deletion, after-column:offset, cursor-column
-1872       deletion <- put deletion, after-top-of-screen:offset, top-after
-1873       *op <- merge 2/delete-operation, deletion
-1874       break +done-adding-delete-operation
-1875     }
-1876     # if not, create a new operation
-1877     op:&:operation <- new operation:type
-1878     deleted-until:&:duplex-list:char <- next before-cursor
-1879     *op <- merge 2/delete-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, deleted-cell/deleted, before-cursor/delete-from, deleted-until, 2/coalesce-delete
-1880     editor <- add-operation editor, op
-1881     +done-adding-delete-operation
-1882   }
-1883 ]
-1884 
-1885 # undo ctrl-k
-1886 
-1887 scenario editor-can-undo-and-redo-ctrl-k [
-1888   local-scope
-1889   # create an editor
-1890   assume-screen 10/width, 5/height
-1891   contents:text <- new [abc
-1892 def]
-1893   e:&:editor <- new-editor contents, 0/left, 10/right
-1894   editor-render screen, e
-1895   # insert some text and hit delete and backspace a few times
-1896   assume-console [
-1897     left-click 1, 1
-1898     press ctrl-k
-1899   ]
-1900   editor-event-loop screen, console, e
-1901   screen-should-contain [
-1902     .          .
-1903     .a         .
-1904     .def       .
-1905     .╌╌╌╌╌╌╌╌╌╌.
-1906     .          .
-1907   ]
-1908   3:num/raw <- get *e, cursor-row:offset
-1909   4:num/raw <- get *e, cursor-column:offset
-1910   memory-should-contain [
-1911     3 <- 1
-1912     4 <- 1
-1913   ]
-1914   # undo
-1915   assume-console [
-1916     press ctrl-z
-1917   ]
-1918   run [
-1919     editor-event-loop screen, console, e
-1920   ]
-1921   screen-should-contain [
-1922     .          .
-1923     .abc       .
-1924     .def       .
-1925     .╌╌╌╌╌╌╌╌╌╌.
-1926     .          .
-1927   ]
-1928   3:num/raw <- get *e, cursor-row:offset
-1929   4:num/raw <- get *e, cursor-column:offset
-1930   memory-should-contain [
-1931     3 <- 1
-1932     4 <- 1
-1933   ]
-1934   # redo
-1935   assume-console [
-1936     press ctrl-y
-1937   ]
-1938   run [
-1939     editor-event-loop screen, console, e
-1940   ]
-1941   # first line inserted
-1942   screen-should-contain [
-1943     .          .
-1944     .a         .
-1945     .def       .
-1946     .╌╌╌╌╌╌╌╌╌╌.
-1947     .          .
-1948   ]
-1949   3:num/raw <- get *e, cursor-row:offset
-1950   4:num/raw <- get *e, cursor-column:offset
-1951   memory-should-contain [
-1952     3 <- 1
-1953     4 <- 1
-1954   ]
-1955   # cursor should be in the right place
-1956   assume-console [
-1957     type [1]
-1958   ]
-1959   run [
-1960     editor-event-loop screen, console, e
-1961   ]
-1962   screen-should-contain [
-1963     .          .
-1964     .a1        .
-1965     .def       .
-1966     .╌╌╌╌╌╌╌╌╌╌.
-1967     .          .
-1968   ]
-1969 ]
-1970 
-1971 after <begin-delete-to-end-of-line> [
-1972   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
-1973 ]
-1974 before <end-delete-to-end-of-line> [
-1975   {
-1976     break-unless deleted-cells  # delete failed; don't add an undo operation
-1977     top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
-1978     cursor-row:num <- get *editor, cursor-row:offset
-1979     cursor-column:num <- get *editor, cursor-column:offset
-1980     deleted-until:&:duplex-list:char <- next before-cursor
-1981     op:&:operation <- new operation:type
-1982     *op <- merge 2/delete-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, deleted-cells/deleted, before-cursor/delete-from, deleted-until, 0/never-coalesce
-1983     editor <- add-operation editor, op
-1984     +done-adding-delete-operation
-1985   }
-1986 ]
-1987 
-1988 # undo ctrl-u
-1989 
-1990 scenario editor-can-undo-and-redo-ctrl-u [
-1991   local-scope
-1992   # create an editor
-1993   assume-screen 10/width, 5/height
-1994   contents:text <- new [abc
-1995 def]
-1996   e:&:editor <- new-editor contents, 0/left, 10/right
-1997   editor-render screen, e
-1998   # insert some text and hit delete and backspace a few times
-1999   assume-console [
-2000     left-click 1, 2
-2001     press ctrl-u
-2002   ]
-2003   editor-event-loop screen, console, e
-2004   screen-should-contain [
-2005     .          .
-2006     .c         .
-2007     .def       .
-2008     .╌╌╌╌╌╌╌╌╌╌.
-2009     .          .
-2010   ]
-2011   3:num/raw <- get *e, cursor-row:offset
-2012   4:num/raw <- get *e, cursor-column:offset
-2013   memory-should-contain [
-2014     3 <- 1
-2015     4 <- 0
-2016   ]
-2017   # undo
-2018   assume-console [
-2019     press ctrl-z
-2020   ]
-2021   run [
-2022     editor-event-loop screen, console, e
-2023   ]
-2024   screen-should-contain [
-2025     .          .
-2026     .abc       .
-2027     .def       .
-2028     .╌╌╌╌╌╌╌╌╌╌.
-2029     .          .
-2030   ]
-2031   3:num/raw <- get *e, cursor-row:offset
-2032   4:num/raw <- get *e, cursor-column:offset
-2033   memory-should-contain [
-2034     3 <- 1
-2035     4 <- 2
-2036   ]
-2037   # redo
-2038   assume-console [
-2039     press ctrl-y
-2040   ]
-2041   run [
-2042     editor-event-loop screen, console, e
-2043   ]
-2044   # first line inserted
-2045   screen-should-contain [
-2046     .          .
-2047     .c         .
-2048     .def       .
-2049     .╌╌╌╌╌╌╌╌╌╌.
-2050     .          .
-2051   ]
-2052   3:num/raw <- get *e, cursor-row:offset
-2053   4:num/raw <- get *e, cursor-column:offset
-2054   memory-should-contain [
-2055     3 <- 1
-2056     4 <- 0
-2057   ]
-2058   # cursor should be in the right place
-2059   assume-console [
-2060     type [1]
-2061   ]
-2062   run [
-2063     editor-event-loop screen, console, e
-2064   ]
-2065   screen-should-contain [
-2066     .          .
-2067     .1c        .
-2068     .def       .
-2069     .╌╌╌╌╌╌╌╌╌╌.
-2070     .          .
-2071   ]
-2072 ]
-2073 
-2074 after <begin-delete-to-start-of-line> [
-2075   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
-2076 ]
-2077 before <end-delete-to-start-of-line> [
-2078   {
-2079     break-unless deleted-cells  # delete failed; don't add an undo operation
-2080     top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
-2081     op:&:operation <- new operation:type
-2082     before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-2083     deleted-until:&:duplex-list:char <- next before-cursor
-2084     cursor-row:num <- get *editor, cursor-row:offset
-2085     cursor-column:num <- get *editor, cursor-column:offset
-2086     *op <- merge 2/delete-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, deleted-cells/deleted, before-cursor/delete-from, deleted-until, 0/never-coalesce
-2087     editor <- add-operation editor, op
-2088     +done-adding-delete-operation
-2089   }
-2090 ]
-2091 
-2092 scenario editor-can-undo-and-redo-ctrl-u-2 [
-2093   local-scope
-2094   # create an editor
-2095   assume-screen 10/width, 5/height
-2096   e:&:editor <- new-editor [], 0/left, 10/right
-2097   editor-render screen, e
-2098   # insert some text and hit delete and backspace a few times
-2099   assume-console [
-2100     type [abc]
-2101     press ctrl-u
-2102     press ctrl-z
-2103   ]
-2104   editor-event-loop screen, console, e
-2105   screen-should-contain [
-2106     .          .
-2107     .abc       .
-2108     .╌╌╌╌╌╌╌╌╌╌.
-2109     .          .
-2110   ]
-2111 ]
-
- - - -- cgit 1.4.1-2-gfad0