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 ]