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