https://github.com/akkartik/mu1/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 ]