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 0/screen  # non-scrolling app
  14   env:&:environment <- new-programming-environment 0/filesystem, 0/screen
  15   env <- restore-sandboxes env, 0/filesystem
  16   render-all 0/screen, env, render
  17   event-loop 0/screen, 0/console, env, 0/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   ¦ error?:bool <- run-sandboxes env, resources, screen
 133   ¦ # we could just render-all, but we do some work to minimize the number of prints to screen
 134   ¦ <render-recipe-errors-on-F4>
 135   ¦ screen <- render-sandbox-side screen, env, render
 136   ¦ {
 137   ¦ ¦ break-if error?
 138   ¦ ¦ screen <- update-status screen, [                 ], 245/grey
 139   ¦ }
 140   ¦ {
 141   ¦ ¦ break-unless error?
 142   ¦ ¦ <render-sandbox-errors-on-F4>
 143   ¦ }
 144   ¦ screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
 145   ¦ loop +next-event
 146   }
 147 ]
 148 
 149 def run-sandboxes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, resources:&:resources, screen:&:screen [
 150   local-scope
 151   load-ingredients
 152   errors-found?:bool <- update-recipes env, resources, screen
 153   jump-if errors-found?, +return
 154   # check contents of right editor (sandbox)
 155   <run-sandboxes-begin>
 156   current-sandbox:&:editor <- get *env, current-sandbox:offset
 157   {
 158   ¦ sandbox-contents:text <- editor-contents current-sandbox
 159   ¦ break-unless sandbox-contents
 160   ¦ # if contents exist, first save them
 161   ¦ # run them and turn them into a new sandbox
 162   ¦ new-sandbox:&:sandbox <- new sandbox:type
 163   ¦ *new-sandbox <- put *new-sandbox, data:offset, sandbox-contents
 164   ¦ # push to head of sandbox list
 165   ¦ dest:&:sandbox <- get *env, sandbox:offset
 166   ¦ *new-sandbox <- put *new-sandbox, next-sandbox:offset, dest
 167   ¦ *env <- put *env, sandbox:offset, new-sandbox
 168   ¦ # update sandbox count
 169   ¦ sandbox-count:num <- get *env, number-of-sandboxes:offset
 170   ¦ sandbox-count <- add sandbox-count, 1
 171   ¦ *env <- put *env, number-of-sandboxes:offset, sandbox-count
 172   ¦ # save all sandboxes
 173   ¦ # needs to be before running them, in case we die when running
 174   ¦ save-sandboxes env, resources
 175   ¦ # clear sandbox editor
 176   ¦ init:&:duplex-list:char <- push 167/§, 0/tail
 177   ¦ *current-sandbox <- put *current-sandbox, data:offset, init
 178   ¦ *current-sandbox <- put *current-sandbox, top-of-screen:offset, init
 179   }
 180   # run all sandboxes
 181   curr:&:sandbox <- get *env, sandbox:offset
 182   idx:num <- copy 0
 183   {
 184   ¦ break-unless curr
 185   ¦ curr <- update-sandbox curr, env, idx
 186   ¦ curr <- get *curr, next-sandbox:offset
 187   ¦ idx <- add idx, 1
 188   ¦ loop
 189   }
 190   <run-sandboxes-end>
 191   +return
 192   {
 193   ¦ break-if resources  # ignore this in tests
 194   ¦ $system [./snapshot_lesson]
 195   }
 196 ]
 197 
 198 # load code from disk
 199 # replaced in a later layer (whereupon errors-found? will actually be set)
 200 def update-recipes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, resources:&:resources, screen:&:screen [
 201   local-scope
 202   load-ingredients
 203   recipes:&:editor <- get *env, recipes:offset
 204   in:text <- editor-contents recipes
 205   resources <- dump resources, [lesson/recipes.mu], in
 206   reload in
 207   errors-found? <- copy 0/false
 208 ]
 209 
 210 # replaced in a later layer
 211 def update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [
 212   local-scope
 213   load-ingredients
 214   data:text <- get *sandbox, data:offset
 215   response:text, _, fake-screen:&:screen <- run-sandboxed data
 216   *sandbox <- put *sandbox, response:offset, response
 217   *sandbox <- put *sandbox, screen:offset, fake-screen
 218 ]
 219 
 220 def update-status screen:&:screen, msg:text, color:num -> screen:&:screen [
 221   local-scope
 222   load-ingredients
 223   screen <- move-cursor screen, 0, 2
 224   screen <- print screen, msg, color, 238/grey/background
 225 ]
 226 
 227 def save-sandboxes env:&:environment, resources:&:resources -> resources:&:resources [
 228   local-scope
 229   load-ingredients
 230   trace 11, [app], [save sandboxes]
 231   current-sandbox:&:editor <- get *env, current-sandbox:offset
 232   # first clear previous versions, in case we deleted some sandbox
 233   $system [rm lesson/[0-9]* >/dev/null 2>/dev/null]  # some shells can't handle '>&'
 234   curr:&:sandbox <- get *env, sandbox:offset
 235   idx:num <- copy 0
 236   {
 237   ¦ break-unless curr
 238   ¦ resources <- save-sandbox resources, curr, idx
 239   ¦ idx <- add idx, 1
 240   ¦ curr <- get *curr, next-sandbox:offset
 241   ¦ loop
 242   }
 243 ]
 244 
 245 def save-sandbox resources:&:resources, sandbox:&:sandbox, sandbox-index:num -> resources:&:resources [
 246   local-scope
 247   load-ingredients
 248   data:text <- get *sandbox, data:offset
 249   filename:text <- append [lesson/], sandbox-index
 250   resources <- dump resources, filename, data
 251   <end-save-sandbox>
 252 ]
 253 
 254 def! render-sandbox-side screen:&:screen, env:&:environment, render-editor:render-recipe -> screen:&:screen, env:&:environment [
 255   local-scope
 256   load-ingredients
 257   trace 11, [app], [render sandbox side]
 258   old-top-idx:num <- save-top-idx screen
 259   current-sandbox:&:editor <- get *env, current-sandbox:offset
 260   row:num, column:num <- copy 1, 0
 261   left:num <- get *current-sandbox, left:offset
 262   right:num <- get *current-sandbox, right:offset
 263   # render sandbox editor
 264   render-from:num <- get *env, render-from:offset
 265   {
 266   ¦ render-current-sandbox?:bool <- equal render-from, -1
 267   ¦ break-unless render-current-sandbox?
 268   ¦ row, column, screen, current-sandbox <- call render-editor, screen, current-sandbox
 269   ¦ clear-screen-from screen, row, column, left, right
 270   ¦ row <- add row, 1
 271   }
 272   # render sandboxes
 273   draw-horizontal screen, row, left, right
 274   sandbox:&:sandbox <- get *env, sandbox:offset
 275   row, screen <- render-sandboxes screen, sandbox, left, right, row, render-from
 276   clear-rest-of-screen screen, row, left, right
 277   #
 278   assert-no-scroll screen, old-top-idx
 279 ]
 280 
 281 def render-sandboxes screen:&:screen, sandbox:&:sandbox, left:num, right:num, row:num, render-from:num, idx:num -> row:num, screen:&:screen, sandbox:&:sandbox [
 282   local-scope
 283   load-ingredients
 284   return-unless sandbox
 285   screen-height:num <- screen-height screen
 286   hidden?:bool <- lesser-than idx, render-from
 287   {
 288   ¦ break-if hidden?
 289   ¦ # render sandbox menu
 290   ¦ row <- add row, 1
 291   ¦ at-bottom?:bool <- greater-or-equal row, screen-height
 292   ¦ return-if at-bottom?
 293   ¦ screen <- move-cursor screen, row, left
 294   ¦ screen <- render-sandbox-menu screen, idx, left, right
 295   ¦ # save menu row so we can detect clicks to it later
 296   ¦ *sandbox <- put *sandbox, starting-row-on-screen:offset, row
 297   ¦ # render sandbox contents
 298   ¦ row <- add row, 1
 299   ¦ screen <- move-cursor screen, row, left
 300   ¦ sandbox-data:text <- get *sandbox, data:offset
 301   ¦ row, screen <- render-code screen, sandbox-data, left, right, row
 302   ¦ *sandbox <- put *sandbox, code-ending-row-on-screen:offset, row
 303   ¦ # render sandbox warnings, screen or response, in that order
 304   ¦ sandbox-response:text <- get *sandbox, response:offset
 305   ¦ <render-sandbox-results>
 306   ¦ {
 307   ¦ ¦ sandbox-screen:&:screen <- get *sandbox, screen:offset
 308   ¦ ¦ empty-screen?:bool <- fake-screen-is-empty? sandbox-screen
 309   ¦ ¦ break-if empty-screen?
 310   ¦ ¦ row, screen <- render-screen screen, sandbox-screen, left, right, row
 311   ¦ }
 312   ¦ {
 313   ¦ ¦ break-unless empty-screen?
 314   ¦ ¦ <render-sandbox-response>
 315   ¦ ¦ row, screen <- render-text screen, sandbox-response, left, right, 245/grey, row
 316   ¦ }
 317   ¦ +render-sandbox-end
 318   ¦ at-bottom?:bool <- greater-or-equal row, screen-height
 319   ¦ return-if at-bottom?
 320   ¦ # draw solid line after sandbox
 321   ¦ draw-horizontal screen, row, left, right
 322   }
 323   # if hidden, reset row attributes
 324   {
 325   ¦ break-unless hidden?
 326   ¦ *sandbox <- put *sandbox, starting-row-on-screen:offset, 0
 327   ¦ *sandbox <- put *sandbox, code-ending-row-on-screen:offset, 0
 328   ¦ <end-render-sandbox-reset-hidden>
 329   }
 330   # draw next sandbox
 331   next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset
 332   next-idx:num <- add idx, 1
 333   row, screen <- render-sandboxes screen, next-sandbox, left, right, row, render-from, next-idx
 334 ]
 335 
 336 def render-sandbox-menu screen:&:screen, sandbox-index:num, left:num, right:num -> screen:&:screen [
 337   local-scope
 338   load-ingredients
 339   move-cursor-to-column screen, left
 340   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
 341   print screen, sandbox-index, 232/dark-grey, 245/grey
 342   start-buttons:num <- subtract edit-button-left, 1
 343   clear-line-until screen, start-buttons, 245/grey
 344   print screen, [edit], 232/black, 25/background-blue
 345   clear-line-until screen, edit-button-right, 25/background-blue
 346   print screen, [copy], 232/black, 58/background-green
 347   clear-line-until screen, copy-button-right, 58/background-green
 348   print screen, [to recipe], 232/black, 94/background-orange
 349   clear-line-until screen, recipe-button-right, 94/background-orange
 350   print screen, [delete], 232/black, 52/background-red
 351   clear-line-until screen, right, 52/background-red
 352 ]
 353 
 354 scenario skip-rendering-sandbox-menu-past-bottom-row [
 355   trace-until 100/app  # trace too long
 356   assume-screen 100/width, 6/height
 357   # recipe editor is empty
 358   assume-resources [
 359   ¦ [lesson/0] <- [|add 2, 2|]
 360   ¦ [lesson/1] <- [|add 1, 1|]
 361   ]
 362   # create two sandboxes such that the top one just barely fills the screen
 363   env:&:environment <- new-programming-environment resources, screen, []
 364   env <- restore-sandboxes env, resources
 365   run [
 366   ¦ render-all screen, env, render
 367   ]
 368   screen-should-contain [
 369   ¦ .                                                                                 run (F4)           .
 370   ¦ .                                                  ╎                                                 .
 371   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
 372   ¦ .                                                  ╎0   edit       copy       to recipe    delete    .
 373   ¦ .                                                  ╎add 2, 2                                         .
 374   ¦ .                                                  ╎─────────────────────────────────────────────────.
 375   ]
 376 ]
 377 
 378 # divide up the menu bar for a sandbox into 3 segments, for edit/copy/delete buttons
 379 # delete-button-right == right
 380 # all left/right pairs are inclusive
 381 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 [
 382   local-scope
 383   load-ingredients
 384   start-buttons:num <- add left, 4/space-for-sandbox-index
 385   buttons-space:num <- subtract right, start-buttons
 386   button-width:num <- divide-with-remainder buttons-space, 4  # integer division
 387   buttons-wide-enough?:bool <- greater-or-equal button-width, 10
 388   assert buttons-wide-enough?, [sandbox must be at least 40 or so characters wide]
 389   edit-button-left:num <- copy start-buttons
 390   copy-button-left:num <- add start-buttons, button-width
 391   edit-button-right:num <- subtract copy-button-left, 1
 392   recipe-button-left:num <- add copy-button-left, button-width
 393   copy-button-right:num <- subtract recipe-button-left, 1
 394   delete-button-left:num <- subtract right, button-width, -2  # because 'to recipe' is wider than 'delete'
 395   recipe-button-right:num <- subtract delete-button-left, 1
 396 ]
 397 
 398 # print a text 's' to 'editor' in 'color' starting at 'row'
 399 # clear rest of last line, move cursor to next line
 400 # like 'render-code' but without syntax-based colorization
 401 def render-text screen:&:screen, s:text, left:num, right:num, color:num, row:num -> row:num, screen:&:screen [
 402   local-scope
 403   load-ingredients
 404   return-unless s
 405   column:num <- copy left
 406   screen <- move-cursor screen, row, column
 407   screen-height:num <- screen-height screen
 408   i:num <- copy 0
 409   len:num <- length *s
 410   {
 411   ¦ +next-character
 412   ¦ done?:bool <- greater-or-equal i, len
 413   ¦ break-if done?
 414   ¦ done? <- greater-or-equal row, screen-height
 415   ¦ break-if done?
 416   ¦ c:char <- index *s, i
 417   ¦ {
 418   ¦ ¦ # newline? move to left rather than 0
 419   ¦ ¦ newline?:bool <- equal c, 10/newline
 420   ¦ ¦ break-unless newline?
 421   ¦ ¦ # clear rest of line in this window
 422   ¦ ¦ {
 423   ¦ ¦ ¦ done?:bool <- greater-than column, right
 424   ¦ ¦ ¦ break-if done?
 425   ¦ ¦ ¦ space:char <- copy 32/space
 426   ¦ ¦ ¦ print screen, space
 427   ¦ ¦ ¦ column <- add column, 1
 428   ¦ ¦ ¦ loop
 429   ¦ ¦ }
 430   ¦ ¦ row <- add row, 1
 431   ¦ ¦ column <- copy left
 432   ¦ ¦ screen <- move-cursor screen, row, column
 433   ¦ ¦ i <- add i, 1
 434   ¦ ¦ loop +next-character
 435   ¦ }
 436   ¦ {
 437   ¦ ¦ # at right? wrap.
 438   ¦ ¦ at-right?:bool <- equal column, right
 439   ¦ ¦ break-unless at-right?
 440   ¦ ¦ # print wrap icon
 441   ¦ ¦ wrap-icon:char <- copy 8617/loop-back-to-left
 442   ¦ ¦ print screen, wrap-icon, 245/grey
 443   ¦ ¦ column <- copy left
 444   ¦ ¦ row <- add row, 1
 445   ¦ ¦ screen <- move-cursor screen, row, column
 446   ¦ ¦ # don't increment i
 447   ¦ ¦ loop +next-character
 448   ¦ }
 449   ¦ i <- add i, 1
 450   ¦ print screen, c, color
 451   ¦ column <- add column, 1
 452   ¦ loop
 453   }
 454   was-at-left?:bool <- equal column, left
 455   clear-line-until screen, right
 456   {
 457   ¦ break-if was-at-left?
 458   ¦ row <- add row, 1
 459   }
 460   move-cursor screen, row, left
 461 ]
 462 
 463 scenario render-text-wraps-barely-long-lines [
 464   local-scope
 465   assume-screen 5/width, 5/height
 466   run [
 467   ¦ render-text screen, [abcde], 0/left, 4/right, 7/white, 1/row
 468   ]
 469   screen-should-contain [
 470   ¦ .     .
 471   ¦ .abcd↩.
 472   ¦ .e    .
 473   ¦ .     .
 474   ]
 475 ]
 476 
 477 # assumes programming environment has no sandboxes; restores them from previous session
 478 def restore-sandboxes env:&:environment, resources:&:resources -> env:&:environment [
 479   local-scope
 480   load-ingredients
 481   # read all scenarios, pushing them to end of a list of scenarios
 482   idx:num <- copy 0
 483   curr:&:sandbox <- copy 0
 484   prev:&:sandbox <- copy 0
 485   {
 486   ¦ filename:text <- append [lesson/], idx
 487   ¦ contents:text <- slurp resources, filename
 488   ¦ break-unless contents  # stop at first error; assuming file didn't exist
 489   ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦      # todo: handle empty sandbox
 490   ¦ # create new sandbox for file
 491   ¦ curr <- new sandbox:type
 492   ¦ *curr <- put *curr, data:offset, contents
 493   ¦ <end-restore-sandbox>
 494   ¦ {
 495   ¦ ¦ break-if idx
 496   ¦ ¦ *env <- put *env, sandbox:offset, curr
 497   ¦ }
 498   ¦ {
 499   ¦ ¦ break-unless idx
 500   ¦ ¦ *prev <- put *prev, next-sandbox:offset, curr
 501   ¦ }
 502   ¦ idx <- add idx, 1
 503   ¦ prev <- copy curr
 504   ¦ loop
 505   }
 506   # update sandbox count
 507   *env <- put *env, number-of-sandboxes:offset, idx
 508 ]
 509 
 510 # print the fake sandbox screen to 'screen' with appropriate delimiters
 511 # leave cursor at start of next line
 512 def render-screen screen:&:screen, sandbox-screen:&:screen, left:num, right:num, row:num -> row:num, screen:&:screen [
 513   local-scope
 514   load-ingredients
 515   return-unless sandbox-screen
 516   # print 'screen:'
 517   row <- render-text screen, [screen:], left, right, 245/grey, row
 518   screen <- move-cursor screen, row, left
 519   # start printing sandbox-screen
 520   column:num <- copy left
 521   s-width:num <- screen-width sandbox-screen
 522   s-height:num <- screen-height sandbox-screen
 523   buf:&:@:screen-cell <- get *sandbox-screen, data:offset
 524   stop-printing:num <- add left, s-width, 3
 525   max-column:num <- min stop-printing, right
 526   i:num <- copy 0
 527   len:num <- length *buf
 528   screen-height:num <- screen-height screen
 529   {
 530   ¦ done?:bool <- greater-or-equal i, len
 531   ¦ break-if done?
 532   ¦ done? <- greater-or-equal row, screen-height
 533   ¦ break-if done?
 534   ¦ column <- copy left
 535   ¦ screen <- move-cursor screen, row, column
 536   ¦ # initial leader for each row: two spaces and a '.'
 537   ¦ space:char <- copy 32/space
 538   ¦ print screen, space, 245/grey
 539   ¦ print screen, space, 245/grey
 540   ¦ full-stop:char <- copy 46/period
 541   ¦ print screen, full-stop, 245/grey
 542   ¦ column <- add left, 3
 543   ¦ {
 544   ¦ ¦ # print row
 545   ¦ ¦ row-done?:bool <- greater-or-equal column, max-column
 546   ¦ ¦ break-if row-done?
 547   ¦ ¦ curr:screen-cell <- index *buf, i
 548   ¦ ¦ c:char <- get curr, contents:offset
 549   ¦ ¦ color:num <- get curr, color:offset
 550   ¦ ¦ {
 551   ¦ ¦ ¦ # damp whites down to grey
 552   ¦ ¦ ¦ white?:bool <- equal color, 7/white
 553   ¦ ¦ ¦ break-unless white?
 554   ¦ ¦ ¦ color <- copy 245/grey
 555   ¦ ¦ }
 556   ¦ ¦ print screen, c, color
 557   ¦ ¦ column <- add column, 1
 558   ¦ ¦ i <- add i, 1
 559   ¦ ¦ loop
 560   ¦ }
 561   ¦ # print final '.'
 562   ¦ print screen, full-stop, 245/grey
 563   ¦ column <- add column, 1
 564   ¦ {
 565   ¦ ¦ # clear rest of current line
 566   ¦ ¦ line-done?:bool <- greater-than column, right
 567   ¦ ¦ break-if line-done?
 568   ¦ ¦ print screen, space
 569   ¦ ¦ column <- add column, 1
 570   ¦ ¦ loop
 571   ¦ }
 572   ¦ row <- add row, 1
 573   ¦ loop
 574   }
 575 ]
 576 
 577 scenario run-updates-results [
 578   local-scope
 579   trace-until 100/app  # trace too long
 580   assume-screen 100/width, 12/height
 581   # define a recipe (no indent for the 'add' line below so column numbers are more obvious)
 582   assume-resources [
 583   ¦ [lesson/recipes.mu] <- [
 584   ¦ ¦ ||
 585   ¦ ¦ |recipe foo [|
 586   ¦ ¦ |  local-scope|
 587   ¦ ¦ |  z:num <- add 2, 2|
 588   ¦ ¦ |  reply z|
 589   ¦ ¦ |]|
 590   ¦ ]
 591   ]
 592   # sandbox editor contains an instruction without storing outputs
 593   env:&:environment <- new-programming-environment resources, screen, [foo]  # contents of sandbox editor
 594   render-all screen, env, render
 595   $clear-trace
 596   # run the code in the editors
 597   assume-console [
 598   ¦ press F4
 599   ]
 600   event-loop screen, console, env, resources
 601   screen-should-contain [
 602   ¦ .                                                                                 run (F4)           .
 603   ¦ .                                                  ╎                                                 .
 604   ¦ .recipe foo [                                      ╎─────────────────────────────────────────────────.
 605   ¦ .  local-scope                                     ╎0   edit       copy       to recipe    delete    .
 606   ¦ .  z:num <- add 2, 2                               ╎foo                                              .
 607   ¦ .  reply z                                         ╎4                                                .
 608   ¦ .]                                                 ╎─────────────────────────────────────────────────.
 609   ¦ .                                                  ╎                                                 .
 610   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
 611   ¦ .                                                  ╎                                                 .
 612   ]
 613   # the new sandbox should be saved to disk
 614   trace-should-contain [
 615   ¦ app: save sandboxes
 616   ]
 617   # no need to update editor
 618   trace-should-not-contain [
 619   ¦ app: render recipes
 620   ]
 621   # make a change (incrementing one of the args to 'add'), then rerun
 622   $clear-trace
 623   assume-console [
 624   ¦ left-click 4, 28  # one past the value of the second arg
 625   ¦ press backspace
 626   ¦ type [3]
 627   ¦ press F4
 628   ]
 629   run [
 630   ¦ event-loop screen, console, env, resources
 631   ]
 632   # check that screen updates the result on the right
 633   screen-should-contain [
 634   ¦ .                                                                                 run (F4)           .
 635   ¦ .                                                  ╎                                                 .
 636   ¦ .recipe foo [                                      ╎─────────────────────────────────────────────────.
 637   ¦ .  local-scope                                     ╎0   edit       copy       to recipe    delete    .
 638   ¦ .  z:num <- add 2, 3                               ╎foo                                              .
 639   ¦ .  reply z                                         ╎5                                                .
 640   ¦ .]                                                 ╎─────────────────────────────────────────────────.
 641   ¦ .                                                  ╎                                                 .
 642   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
 643   ¦ .                                                  ╎                                                 .
 644   ]
 645   # no need to save sandboxes all over again
 646   trace-should-not-contain [
 647   ¦ app: save sandboxes
 648   ]
 649 ]
 650 
 651 scenario run-instruction-manages-screen-per-sandbox [
 652   local-scope
 653   trace-until 100/app  # trace too long
 654   assume-screen 100/width, 20/height
 655   # empty recipes
 656   assume-resources [
 657   ]
 658   # sandbox editor contains an instruction
 659   env:&:environment <- new-programming-environment resources, screen, [print screen, 4]  # contents of sandbox editor
 660   render-all screen, env, render
 661   # run the code in the editor
 662   assume-console [
 663   ¦ press F4
 664   ]
 665   run [
 666   ¦ event-loop screen, console, env, resources
 667   ]
 668   # check that it prints a little toy screen
 669   screen-should-contain [
 670   ¦ .                                                                                 run (F4)           .
 671   ¦ .                                                  ╎                                                 .
 672   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
 673   ¦ .                                                  ╎0   edit       copy       to recipe    delete    .
 674   ¦ .                                                  ╎print screen, 4                                  .
 675   ¦ .                                                  ╎screen:                                          .
 676   ¦ .                                                  ╎  .4                             .               .
 677   ¦ .                                                  ╎  .                              .               .
 678   ¦ .                                                  ╎  .                              .               .
 679   ¦ .                                                  ╎  .                              .               .
 680   ¦ .                                                  ╎  .                              .               .
 681   ¦ .                                                  ╎─────────────────────────────────────────────────.
 682   ¦ .                                                  ╎                                                 .
 683   ]
 684 ]
 685 
 686 def editor-contents editor:&:editor -> result:text [
 687   local-scope
 688   load-ingredients
 689   buf:&:buffer:char <- new-buffer 80
 690   curr:&:duplex-list:char <- get *editor, data:offset
 691   # skip § sentinel
 692   assert curr, [editor without data is illegal; must have at least a sentinel]
 693   curr <- next curr
 694   return-unless curr, 0
 695   {
 696   ¦ break-unless curr
 697   ¦ c:char <- get *curr, value:offset
 698   ¦ buf <- append buf, c
 699   ¦ curr <- next curr
 700   ¦ loop
 701   }
 702   result <- buffer-to-array buf
 703 ]
 704 
 705 scenario editor-provides-edited-contents [
 706   local-scope
 707   assume-screen 10/width, 5/height
 708   e:&:editor <- new-editor [abc], 0/left, 10/right
 709   assume-console [
 710   ¦ left-click 1, 2
 711   ¦ type [def]
 712   ]
 713   run [
 714   ¦ editor-event-loop screen, console, e
 715   ¦ s:text <- editor-contents e
 716   ¦ 1:@:char/raw <- copy *s
 717   ]
 718   memory-should-contain [
 719   ¦ 1:array:character <- [abdefc]
 720   ]
 721 ]
 722 
 723 # keep the bottom of recipes from scrolling off the screen
 724 
 725 scenario scrolling-down-past-bottom-of-recipe-editor [
 726   local-scope
 727   trace-until 100/app
 728   assume-screen 100/width, 10/height
 729   assume-resources [
 730   ]
 731   env:&:environment <- new-programming-environment resources, screen, []
 732   render-all screen, env, render
 733   assume-console [
 734   ¦ press enter
 735   ¦ press down-arrow
 736   ]
 737   event-loop screen, console, env, resources
 738   # no scroll
 739   screen-should-contain [
 740   ¦ .                                                                                 run (F4)           .
 741   ¦ .                                                  ╎                                                 .
 742   ¦ .                                                  ╎─────────────────────────────────────────────────.
 743   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
 744   ¦ .                                                  ╎                                                 .
 745   ]
 746 ]
 747 
 748 scenario cursor-down-in-recipe-editor [
 749   local-scope
 750   trace-until 100/app
 751   assume-screen 100/width, 10/height
 752   assume-resources [
 753   ]
 754   env:&:environment <- new-programming-environment resources, screen, []
 755   render-all screen, env, render
 756   assume-console [
 757   ¦ press enter
 758   ¦ press up-arrow
 759   ¦ press down-arrow  # while cursor isn't at bottom
 760   ]
 761   event-loop screen, console, env, resources
 762   cursor:char <- copy 9251/␣
 763   print screen, cursor
 764   # cursor moves back to bottom
 765   screen-should-contain [
 766   ¦ .                                                                                 run (F4)           .
 767   ¦ .                                                  ╎                                                 .
 768   ¦ .␣                                                 ╎─────────────────────────────────────────────────.
 769   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
 770   ¦ .                                                  ╎                                                 .
 771   ]
 772 ]
 773 
 774 scenario scrolling-down-past-bottom-of-recipe-editor-2 [
 775   local-scope
 776   trace-until 100/app
 777   assume-screen 100/width, 10/height
 778   assume-resources [
 779   ]
 780   env:&:environment <- new-programming-environment resources, screen, []
 781   render-all screen, env, render
 782   assume-console [
 783   ¦ # add a line
 784   ¦ press enter
 785   ¦ # cursor back to top line
 786   ¦ press up-arrow
 787   ¦ # try to scroll
 788   ¦ press page-down  # or ctrl-f
 789   ]
 790   event-loop screen, console, env, resources
 791   # no scroll, and cursor remains at top line
 792   screen-should-contain [
 793   ¦ .                                                                                 run (F4)           .
 794   ¦ .                                                  ╎                                                 .
 795   ¦ .                                                  ╎─────────────────────────────────────────────────.
 796   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎                                                 .
 797   ¦ .                                                  ╎                                                 .
 798   ]
 799 ]
 800 
 801 scenario scrolling-down-past-bottom-of-recipe-editor-3 [
 802   local-scope
 803   trace-until 100/app
 804   assume-screen 100/width, 10/height
 805   assume-resources [
 806   ]
 807   env:&:environment <- new-programming-environment resources, screen, [ab
 808 cd]
 809   render-all screen, env, render
 810   assume-console [
 811   ¦ # add a line
 812   ¦ press enter
 813   ¦ # switch to sandbox
 814   ¦ press ctrl-n
 815   ¦ # move cursor
 816   ¦ press down-arrow
 817   ]
 818   event-loop screen, console, env, resources
 819   cursor:char <- copy 9251/␣
 820   print screen, cursor
 821   # no scroll on recipe side, cursor moves on sandbox side
 822   screen-should-contain [
 823   ¦ .                                                                                 run (F4)           .
 824   ¦ .                                                  ╎ab                                               .
 825   ¦ .                                                  ╎␣d                                               .
 826   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
 827   ¦ .                                                  ╎                                                 .
 828   ]
 829 ]
 830 
 831 # scrolling through sandboxes
 832 
 833 scenario scrolling-down-past-bottom-of-sandbox-editor [
 834   local-scope
 835   trace-until 100/app  # trace too long
 836   assume-screen 100/width, 10/height
 837   # initialize
 838   assume-resources [
 839   ]
 840   env:&:environment <- new-programming-environment resources, screen, [add 2, 2]
 841   render-all screen, env, render
 842   assume-console [
 843   ¦ # create a sandbox
 844   ¦ press F4
 845   ]
 846   event-loop screen, console, env, resources
 847   screen-should-contain [
 848   ¦ .                                                                                 run (F4)           .
 849   ¦ .                                                  ╎                                                 .
 850   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
 851   ¦ .                                                  ╎0   edit       copy       to recipe    delete    .
 852   ¦ .                                                  ╎add 2, 2                                         .
 853   ]
 854   # switch to sandbox window and hit 'page-down'
 855   assume-console [
 856   ¦ press ctrl-n
 857   ¦ press page-down
 858   ]
 859   run [
 860   ¦ event-loop screen, console, env, resources
 861   ¦ cursor:char <- copy 9251/␣
 862   ¦ print screen, cursor
 863   ]
 864   # sandbox editor hidden; first sandbox displayed
 865   # cursor moves to first sandbox
 866   screen-should-contain [
 867   ¦ .                                                                                 run (F4)           .
 868   ¦ .                                                  ╎─────────────────────────────────────────────────.
 869   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎␣   edit       copy       to recipe    delete    .
 870   ¦ .                                                  ╎add 2, 2                                         .
 871   ¦ .                                                  ╎4                                                .
 872   ]
 873   # hit 'page-up'
 874   assume-console [
 875   ¦ press page-up
 876   ]
 877   run [
 878   ¦ event-loop screen, console, env, resources
 879   ¦ cursor:char <- copy 9251/␣
 880   ¦ print screen, cursor
 881   ]
 882   # sandbox editor displays again, cursor is in editor
 883   screen-should-contain [
 884   ¦ .                                                                                 run (F4)           .
 885   ¦ .                                                  ╎␣                                                .
 886   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
 887   ¦ .                                                  ╎0   edit       copy       to recipe    delete    .
 888   ¦ .                                                  ╎add 2, 2                                         .
 889   ]
 890 ]
 891 
 892 # page-down on sandbox side updates render-from to scroll sandboxes
 893 after <global-keypress> [
 894   {
 895   ¦ break-unless sandbox-in-focus?
 896   ¦ page-down?:bool <- equal k, 65518/page-down
 897   ¦ break-unless page-down?
 898   ¦ sandbox:&:sandbox <- get *env, sandbox:offset
 899   ¦ break-unless sandbox
 900   ¦ # slide down if possible
 901   ¦ {
 902   ¦ ¦ render-from:num <- get *env, render-from:offset
 903   ¦ ¦ number-of-sandboxes:num <- get *env, number-of-sandboxes:offset
 904   ¦ ¦ max:num <- subtract number-of-sandboxes, 1
 905   ¦ ¦ at-end?:bool <- greater-or-equal render-from, max
 906   ¦ ¦ loop-if at-end?, +next-event  # render nothing
 907   ¦ ¦ render-from <- add render-from, 1
 908   ¦ ¦ *env <- put *env, render-from:offset, render-from
 909   ¦ }
 910   ¦ screen <- render-sandbox-side screen, env, render
 911   ¦ screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
 912   ¦ loop +next-event
 913   }
 914 ]
 915 
 916 # update-cursor takes render-from into account
 917 after <update-cursor-special-cases> [
 918   {
 919   ¦ break-unless sandbox-in-focus?
 920   ¦ render-from:num <- get *env, render-from:offset
 921   ¦ scrolling?:bool <- greater-or-equal render-from, 0
 922   ¦ break-unless scrolling?
 923   ¦ cursor-column:num <- get *current-sandbox, left:offset
 924   ¦ screen <- move-cursor screen, 2/row, cursor-column  # highlighted sandbox will always start at row 2
 925   ¦ return
 926   }
 927 ]
 928 
 929 # 'page-up' on sandbox side is like 'page-down': updates render-from when necessary
 930 after <global-keypress> [
 931   {
 932   ¦ break-unless sandbox-in-focus?
 933   ¦ page-up?:bool <- equal k, 65519/page-up
 934   ¦ break-unless page-up?
 935   ¦ render-from:num <- get *env, render-from:offset
 936   ¦ at-beginning?:bool <- equal render-from, -1
 937   ¦ break-if at-beginning?
 938   ¦ render-from <- subtract render-from, 1
 939   ¦ *env <- put *env, render-from:offset, render-from
 940   ¦ screen <- render-sandbox-side screen, env, render
 941   ¦ screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
 942   ¦ loop +next-event
 943   }
 944 ]
 945 
 946 # sandbox belonging to 'env' whose next-sandbox is 'in'
 947 # return 0 if there's no such sandbox, either because 'in' doesn't exist in 'env', or because it's the first sandbox
 948 def previous-sandbox env:&:environment, in:&:sandbox -> out:&:sandbox [
 949   local-scope
 950   load-ingredients
 951   curr:&:sandbox <- get *env, sandbox:offset
 952   return-unless curr, 0/nil
 953   next:&:sandbox <- get *curr, next-sandbox:offset
 954   {
 955   ¦ return-unless next, 0/nil
 956   ¦ found?:bool <- equal next, in
 957   ¦ break-if found?
 958   ¦ curr <- copy next
 959   ¦ next <- get *curr, next-sandbox:offset
 960   ¦ loop
 961   }
 962   return curr
 963 ]
 964 
 965 scenario scrolling-through-multiple-sandboxes [
 966   local-scope
 967   trace-until 100/app  # trace too long
 968   assume-screen 100/width, 10/height
 969   # initialize environment
 970   assume-resources [
 971   ]
 972   env:&:environment <- new-programming-environment resources, screen, []
 973   render-all screen, env, render
 974   # create 2 sandboxes
 975   assume-console [
 976   ¦ press ctrl-n
 977   ¦ type [add 2, 2]
 978   ¦ press F4
 979   ¦ type [add 1, 1]
 980   ¦ press F4
 981   ]
 982   event-loop screen, console, env, resources
 983   cursor:char <- copy 9251/␣
 984   print screen, cursor
 985   screen-should-contain [
 986   ¦ .                                                                                 run (F4)           .
 987   ¦ .                                                  ╎␣                                                .
 988   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
 989   ¦ .                                                  ╎0   edit       copy       to recipe    delete    .
 990   ¦ .                                                  ╎add 1, 1                                         .
 991   ¦ .                                                  ╎2                                                .
 992   ¦ .                                                  ╎─────────────────────────────────────────────────.
 993   ¦ .                                                  ╎1   edit       copy       to recipe    delete    .
 994   ¦ .                                                  ╎add 2, 2                                         .
 995   ¦ .                                                  ╎4                                                .
 996   ]
 997   # hit 'page-down'
 998   assume-console [
 999   ¦ press page-down
1000   ]
1001   run [
1002   ¦ event-loop screen, console, env, resources
1003   ¦ cursor:char <- copy 9251/␣
1004   ¦ print screen, cursor
1005   ]
1006   # sandbox editor hidden; first sandbox displayed
1007   # cursor moves to first sandbox
1008   screen-should-contain [
1009   ¦ .                                                                                 run (F4)           .
1010   ¦ .                                                  ╎─────────────────────────────────────────────────.
1011   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎␣   edit       copy       to recipe    delete    .
1012   ¦ .                                                  ╎add 1, 1                                         .
1013   ¦ .                                                  ╎2                                                .
1014   ¦ .                                                  ╎─────────────────────────────────────────────────.
1015   ¦ .                                                  ╎1   edit       copy       to recipe    delete    .
1016   ¦ .                                                  ╎add 2, 2                                         .
1017   ¦ .                                                  ╎4                                                .
1018   ]
1019   # hit 'page-down' again
1020   assume-console [
1021   ¦ press page-down
1022   ]
1023   run [
1024   ¦ event-loop screen, console, env, resources
1025   ]
1026   # just second sandbox displayed
1027   screen-should-contain [
1028   ¦ .                                                                                 run (F4)           .
1029   ¦ .                                                  ╎─────────────────────────────────────────────────.
1030   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎1   edit       copy       to recipe    delete    .
1031   ¦ .                                                  ╎add 2, 2                                         .
1032   ¦ .                                                  ╎4                                                .
1033   ¦ .                                                  ╎─────────────────────────────────────────────────.
1034   ¦ .                                                  ╎                                                 .
1035   ]
1036   # hit 'page-down' again
1037   assume-console [
1038   ¦ press page-down
1039   ]
1040   run [
1041   ¦ event-loop screen, console, env, resources
1042   ]
1043   # no change
1044   screen-should-contain [
1045   ¦ .                                                                                 run (F4)           .
1046   ¦ .                                                  ╎─────────────────────────────────────────────────.
1047   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎1   edit       copy       to recipe    delete    .
1048   ¦ .                                                  ╎add 2, 2                                         .
1049   ¦ .                                                  ╎4                                                .
1050   ¦ .                                                  ╎─────────────────────────────────────────────────.
1051   ¦ .                                                  ╎                                                 .
1052   ]
1053   # hit 'page-up'
1054   assume-console [
1055   ¦ press page-up
1056   ]
1057   run [
1058   ¦ event-loop screen, console, env, resources
1059   ]
1060   # back to displaying both sandboxes without editor
1061   screen-should-contain [
1062   ¦ .                                                                                 run (F4)           .
1063   ¦ .                                                  ╎─────────────────────────────────────────────────.
1064   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎0   edit       copy       to recipe    delete    .
1065   ¦ .                                                  ╎add 1, 1                                         .
1066   ¦ .                                                  ╎2                                                .
1067   ¦ .                                                  ╎─────────────────────────────────────────────────.
1068   ¦ .                                                  ╎1   edit       copy       to recipe    delete    .
1069   ¦ .                                                  ╎add 2, 2                                         .
1070   ¦ .                                                  ╎4                                                .
1071   ]
1072   # hit 'page-up' again
1073   assume-console [
1074   ¦ press page-up
1075   ]
1076   run [
1077   ¦ event-loop screen, console, env, resources
1078   ¦ cursor:char <- copy 9251/␣
1079   ¦ print screen, cursor
1080   ]
1081   # back to displaying both sandboxes as well as editor
1082   screen-should-contain [
1083   ¦ .                                                                                 run (F4)           .
1084   ¦ .                                                  ╎␣                                                .
1085   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
1086   ¦ .                                                  ╎0   edit       copy       to recipe    delete    .
1087   ¦ .                                                  ╎add 1, 1                                         .
1088   ¦ .                                                  ╎2                                                .
1089   ¦ .                                                  ╎─────────────────────────────────────────────────.
1090   ¦ .                                                  ╎1   edit       copy       to recipe    delete    .
1091   ¦ .                                                  ╎add 2, 2                                         .
1092   ¦ .                                                  ╎4                                                .
1093   ]
1094   # hit 'page-up' again
1095   assume-console [
1096   ¦ press page-up
1097   ]
1098   run [
1099   ¦ event-loop screen, console, env, resources
1100   ¦ cursor:char <- copy 9251/␣
1101   ¦ print screen, cursor
1102   ]
1103   # no change
1104   screen-should-contain [
1105   ¦ .                                                                                 run (F4)           .
1106   ¦ .                                                  ╎␣                                                .
1107   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
1108   ¦ .                                                  ╎0   edit       copy       to recipe    delete    .
1109   ¦ .                                                  ╎add 1, 1                                         .
1110   ¦ .                                                  ╎2                                                .
1111   ¦ .                                                  ╎─────────────────────────────────────────────────.
1112   ¦ .                                                  ╎1   edit       copy       to recipe    delete    .
1113   ¦ .                                                  ╎add 2, 2                                         .
1114   ¦ .                                                  ╎4                                                .
1115   ]
1116 ]
1117 
1118 scenario scrolling-manages-sandbox-index-correctly [
1119   local-scope
1120   trace-until 100/app  # trace too long
1121   assume-screen 100/width, 10/height
1122   # initialize environment
1123   assume-resources [
1124   ]
1125   env:&:environment <- new-programming-environment resources, screen, []
1126   render-all screen, env, render
1127   # create a sandbox
1128   assume-console [
1129   ¦ press ctrl-n
1130   ¦ type [add 1, 1]
1131   ¦ press F4
1132   ]
1133   event-loop screen, console, env, resources
1134   screen-should-contain [
1135   ¦ .                                                                                 run (F4)           .
1136   ¦ .                                                  ╎                                                 .
1137   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
1138   ¦ .                                                  ╎0   edit       copy       to recipe    delete    .
1139   ¦ .                                                  ╎add 1, 1                                         .
1140   ¦ .                                                  ╎2                                                .
1141   ¦ .                                                  ╎─────────────────────────────────────────────────.
1142   ¦ .                                                  ╎                                                 .
1143   ]
1144   # hit 'page-down' and 'page-up' a couple of times. sandbox index should be stable
1145   assume-console [
1146   ¦ press page-down
1147   ]
1148   run [
1149   ¦ event-loop screen, console, env, resources
1150   ]
1151   # sandbox editor hidden; first sandbox displayed
1152   # cursor moves to first sandbox
1153   screen-should-contain [
1154   ¦ .                                                                                 run (F4)           .
1155   ¦ .                                                  ╎─────────────────────────────────────────────────.
1156   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎0   edit       copy       to recipe    delete    .
1157   ¦ .                                                  ╎add 1, 1                                         .
1158   ¦ .                                                  ╎2                                                .
1159   ¦ .                                                  ╎─────────────────────────────────────────────────.
1160   ¦ .                                                  ╎                                                 .
1161   ]
1162   # hit 'page-up' again
1163   assume-console [
1164   ¦ press page-up
1165   ]
1166   run [
1167   ¦ event-loop screen, console, env, resources
1168   ]
1169   # back to displaying both sandboxes as well as editor
1170   screen-should-contain [
1171   ¦ .                                                                                 run (F4)           .
1172   ¦ .                                                  ╎                                                 .
1173   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎─────────────────────────────────────────────────.
1174   ¦ .                                                  ╎0   edit       copy       to recipe    delete    .
1175   ¦ .                                                  ╎add 1, 1                                         .
1176   ¦ .                                                  ╎2                                                .
1177   ¦ .                                                  ╎─────────────────────────────────────────────────.
1178   ¦ .                                                  ╎                                                 .
1179   ]
1180   # hit 'page-down'
1181   assume-console [
1182   ¦ press page-down
1183   ]
1184   run [
1185   ¦ event-loop screen, console, env, resources
1186   ]
1187   # sandbox editor hidden; first sandbox displayed
1188   # cursor moves to first sandbox
1189   screen-should-contain [
1190   ¦ .                                                                                 run (F4)           .
1191   ¦ .                                                  ╎─────────────────────────────────────────────────.
1192   ¦ .╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╎0   edit       copy       to recipe    delete    .
1193   ¦ .                                                  ╎add 1, 1                                         .
1194   ¦ .                                                  ╎2                                                .
1195   ¦ .                                                  ╎─────────────────────────────────────────────────.
1196   ¦ .                                                  ╎                                                 .
1197   ]
1198 ]