about summary refs log blame commit diff stats
path: root/archive/1.vm/sandbox/005-sandbox.mu
blob: 632a5df13c659fa33934e74be01fb0583d96630f (plain) (tree)
1
2
3
4
5
6
7
8
9


                                                                       

                                                                           
 

                                                                         
 


              




                                                                               

 
                       
                                                                                            

                         


                                                
                                                

 
                   

               
                               
                                                                           


                                                   
                        


                               
             
                                       
                                   


                       
                                                                  
                                                                                                   
                                




                               
                                              




                                                        
                                                                                                                                                            
                                                        


                                                        
                                                                                                                                                            















                                                        
                                                                                                                                                            
                                                        


                                                        
                                                                                                                                                            








                                                        
                                              




                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            
                                                        


                                                        
                                                                                                                                                            






                                                        
                                     
                        
                                                                 
                                                       
                                                        
                                            

                     
                                                                   
     
                                                        
                    


   
                                                                                                                                                              
             
             
                                                             
                            
                       
                                                              
   
                                                            

                                        

                                               
                                                                   
                                  
                                              

                                                               
                          
                                                             

                                                               


                                                                  
                          
                                                

                                                                        
   
                     
                                            
                   

                     
                                         
                                          
                     

        
                     



                                              

 
                     
                                                                          
                                                                                                                                        
             
             

                                                 
                             


                           
                                                                                                          
             
             
                                        
                                                              

                                                      

 
                                                                           
             
             
                                    
                                                         

 
                                                                                      
             
             
                                   
                                                              

                                                                                    
                                            
                   

                     
                                                  





                                          

                                                                                                       
             





                                                  
                                                                                                                                
             
             
                                        
                                        
                                                              


                                                 
                         
                                                 
   
                                                         
                                        
                                                                                       

                    
                                          
                                               
                                                                                        
                                               
   
                                      

 
                                                                                                                                                                
             

                                             
                       
                                           
                                              
   


                         

                                                          
                                           
                                                          
                                                       
                                                                


                                           
                                                  
                                                                     
                                                                   
                                                                
                                                          

                            

                                                                




                                                                           
                               
                                                                                     

                       

                                                          
                                   
                                            
   
                                   
   
                        

                                                                 
                                     
   
                     
                                                             
                            
                                                                                                    

 
                                                                                                    
             
             
                                    
                                                                                                                                                      
                                                      
                                                   
                                                  

                                                                

                                                                 



                                                      























                                                                                                                                                            


                                                                                    
                                                                                                                                                                  
             
             



                                                                                
                                                                                  




                                                         

 

                                                           
                                                                                                              
             
             
                 
                         
                                           


                                           

                   
                                         


                                                
                         
     




                                           
                                                
                      
                                   






                                               
                   
                          
     













                                                   



                           
                                         







                                













                                                                
                                                                                       
                                                                                     
             
             
                                                                  
                   

                             
   

                                              
                                                                            
                                                       
                                 
                            
                                             
                         
     




                                            

                                                   
                     
                     

        
                        
                                                   

 

                                                                       
                                                                                                                      
             
             
                              
                   
                                                                  
                                         
                                 








                                                         
   
                                         





                                                       
                               

                                 
                                    
                                     


                         
                                                           

                                       

                                         

                                  
                                           


                              
                            




                             
                                     


                                  
                                                   
                         
                         





                             

 
                              
             
                                       

                                                                                           









                            
                                                                  
                                                                                                         
                                
              



                               
                                            


                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            

                                                        



                                           
                                                                     









                            
              



                  
                                              




                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            

                                                        



                                            

 
                                                     
             
                                       
                                   



                                          
                                                                                                                     
                                




                              
                                              




                                                        
                                                                                                                                                            
                                                        
                                                        





                                                        
                                                                                                                                                            



                                                        
                                                    
             
             
                                    
                                                     

                                                                              
                   
                          

                     
                                     
                        
                     

        
                               


                                          
             
                                  
                                                  




                   
                                        

                               

                         
                                 

   



                                                       
             


                                       


                                                                                
                                


                      
   
                                            

                                                        
                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            

                                                        
                   
                  
                   

       
                                              
                                
                        




                                                        
                                                                                                                                                            
                                                          

                                                        
                                                                                                                                                            

                                                        
                 
                  
                 

       
                                              
                                
                        



                                                        
                                                          
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            



                                                        
                                                   

                         
                                               
                           
                                                 
                        
                            
     



                                                                     
                      

                                                       
     
                                                     

                                                        


   
                                              

                                     

                                                      
                           
                                                          
                                                                                                        
          


   
                                                                               

                         
                                           
                         

                                                   
                          

                                                     
                                                     

                                                        



                                                       
                                                                                                                       
                                                                       
             
             
                                            
                          
                                                  
   
                            
                                 




                                          
             


                                               
             


                                       


                                                                        
                                







                      
                                            
                              
                      


                                                          
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            

                                                        
                   
                  
                   

       
                                              
                                
                        




                                                        
                                                                                                                                                            
                                                          

                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            

                                                        
                         
                  
                   

       
                                              



                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            

                                                        
                         
                  
                   

       
                                              



                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            

                                                        
                 
                  
                 

       
                                              



                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            

                                                        
                       
                  
                 

       
                                              
                                
                        




                                                          
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            

                                                        
                       
                  
                 

       
                                              
                                
                        




                                                          
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            


                                                        

                                                    
             


                                       


                                                                        
                                





                    
                                            


                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            

                                                        
                                                                                   
                  
                   

       
                                              




                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            

                                                        
                       
                  
                 

       
                                              




                                                        
                                                                                                                                                            
                                                        

                                                        
                                                                                                                                                            

                                                        
                   
                  
                   

       
                                              




                                                        
                                                                                                                                                            
                                                                     

                                                        
                                                                                                                                                            


                                                        
## running code from the editor and creating sandboxes
#
# Running code in the sandbox editor prepends its contents to a list of
# (non-editable) sandboxes below the editor, showing the result and maybe a
# few other things (later layers).
#
# This layer draws the menubar buttons in non-editable sandboxes but they
# don't do anything yet. Later layers implement each button.

def! main [
  local-scope
  open-console
  clear-screen null/screen  # non-scrolling app
  env:&:environment <- new-programming-environment null/filesystem, null/screen
  env <- restore-sandboxes env, null/filesystem
  render-all null/screen, env, render
  event-loop null/screen, null/console, env, null/filesystem
]

container environment [
  sandbox:&:sandbox  # list of sandboxes, from top to bottom. TODO: switch to &:list:sandbox
  render-from:num
  number-of-sandboxes:num
]

after <programming-environment-initialization> [
  *result <- put *result, render-from:offset, -1
]

container sandbox [
  data:text
  response:text
  # coordinates to track clicks
  # constraint: will be 0 for sandboxes at positions before env.render-from
  starting-row-on-screen:num
  code-ending-row-on-screen:num  # past end of code
  screen:&:screen  # prints in the sandbox go here
  next-sandbox:&:sandbox
]

scenario run-and-show-results [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 15/height
  # recipes.mu is empty
  assume-resources [
  ]
  # sandbox editor contains an instruction without storing outputs
  env:&:environment <- new-programming-environment resources, screen, [divide-with-remainder 11, 3]
  render-all screen, env, render
  # run the code in the editors
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # check that screen prints the results
  screen-should-contain [
    .                               run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .divide-with-remainder 11, 3                       .
    .3                                                 .
    .2                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  screen-should-contain-in-color 7/white, [
    .                                                  .
    .                                                  .
    .                                                  .
    .                                                  .
    .divide-with-remainder 11, 3                       .
    .                                                  .
    .                                                  .
    .                                                  .
    .                                                  .
  ]
  screen-should-contain-in-color 245/grey, [
    .                                                  .
    .                                                  .
    .──────────────────────────────────────────────────.
    .                                                  .
    .                                                  .
    .3                                                 .
    .2                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # run another command
  assume-console [
    left-click 1, 80
    type [add 2, 2]
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # check that screen prints both sandboxes
  screen-should-contain [
    .                               run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .add 2, 2                                          .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .1   edit           copy           delete          .
    .divide-with-remainder 11, 3                       .
    .3                                                 .
    .2                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
]

after <global-keypress> [
  # F4? load all code and run all sandboxes.
  {
    do-run?:bool <- equal k, 65532/F4
    break-unless do-run?
    screen <- update-status screen, [running...       ], 245/grey
    error?:bool <- run-sandboxes env, resources, screen
    # F4 might update warnings and results on both sides
    screen <- render-all screen, env, render
    {
      break-if error?
      screen <- update-status screen, [                 ], 245/grey
    }
    screen <- update-cursor screen, current-sandbox, env
    loop +next-event
  }
]

def run-sandboxes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, resources:&:resources, screen:&:screen [
  local-scope
  load-inputs
  errors-found?:bool <- update-recipes env, resources, screen
  # check contents of editor
  <begin-run-sandboxes>
  current-sandbox:&:editor <- get *env, current-sandbox:offset
  {
    sandbox-contents:text <- editor-contents current-sandbox
    break-unless sandbox-contents
    # if contents exist, first save them
    # run them and turn them into a new sandbox
    new-sandbox:&:sandbox <- new sandbox:type
    *new-sandbox <- put *new-sandbox, data:offset, sandbox-contents
    # push to head of sandbox list
    dest:&:sandbox <- get *env, sandbox:offset
    *new-sandbox <- put *new-sandbox, next-sandbox:offset, dest
    *env <- put *env, sandbox:offset, new-sandbox
    # update sandbox count
    sandbox-count:num <- get *env, number-of-sandboxes:offset
    sandbox-count <- add sandbox-count, 1
    *env <- put *env, number-of-sandboxes:offset, sandbox-count
    # save all sandboxes
    # needs to be before running them, in case we die when running
    save-sandboxes env, resources
    # clear sandbox editor
    init:&:duplex-list:char <- push 167/§, null
    *current-sandbox <- put *current-sandbox, data:offset, init
    *current-sandbox <- put *current-sandbox, top-of-screen:offset, init
  }
  # run all sandboxes
  curr:&:sandbox <- get *env, sandbox:offset
  idx:num <- copy 0
  {
    break-unless curr
    curr <- update-sandbox curr, env, idx
    curr <- get *curr, next-sandbox:offset
    idx <- add idx, 1
    loop
  }
  <end-run-sandboxes>
  {
    break-if resources  # ignore this in tests
    $system [./snapshot_lesson]
  }
]

# load code from disk
# replaced in a later layer (whereupon errors-found? will actually be set)
def update-recipes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, screen:&:screen [
  local-scope
  load-inputs
  in:text <- slurp resources, [lesson/recipes.mu]
  reload in
  errors-found? <- copy false
]

# replaced in a later layer
def update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [
  local-scope
  load-inputs
  data:text <- get *sandbox, data:offset
  response:text, _, fake-screen:&:screen <- run-sandboxed data
  *sandbox <- put *sandbox, response:offset, response
  *sandbox <- put *sandbox, screen:offset, fake-screen
]

def update-status screen:&:screen, msg:text, color:num -> screen:&:screen [
  local-scope
  load-inputs
  screen <- move-cursor screen, 0, 2
  screen <- print screen, msg, color, 238/grey/background
]

def save-sandboxes env:&:environment, resources:&:resources -> resources:&:resources [
  local-scope
  load-inputs
  trace 11, [app], [save sandboxes]
  current-sandbox:&:editor <- get *env, current-sandbox:offset
  # first clear previous versions, in case we deleted some sandbox
  $system [rm lesson/[0-9]* >/dev/null 2>/dev/null]  # some shells can't handle '>&'
  curr:&:sandbox <- get *env, sandbox:offset
  idx:num <- copy 0
  {
    break-unless curr
    resources <- save-sandbox resources, curr, idx
    idx <- add idx, 1
    curr <- get *curr, next-sandbox:offset
    loop
  }
]

def save-sandbox resources:&:resources, sandbox:&:sandbox, sandbox-index:num -> resources:&:resources [
  local-scope
  load-inputs
  data:text <- get *sandbox, data:offset
  filename:text <- append [lesson/], sandbox-index
  resources <- dump resources, filename, data
  <end-save-sandbox>
]

def! render-sandbox-side screen:&:screen, env:&:environment, render-editor:render-recipe -> screen:&:screen, env:&:environment [
  local-scope
  load-inputs
  trace 11, [app], [render sandbox side]
  old-top-idx:num <- save-top-idx screen
  current-sandbox:&:editor <- get *env, current-sandbox:offset
  row:num, column:num <- copy 1, 0
  left:num <- get *current-sandbox, left:offset
  right:num <- get *current-sandbox, right:offset
  # render sandbox editor
  render-from:num <- get *env, render-from:offset
  {
    render-current-sandbox?:bool <- equal render-from, -1
    break-unless render-current-sandbox?
    row, column, screen, current-sandbox <- call render-editor, screen, current-sandbox
  }
  # render sandboxes
  draw-horizontal screen, row, left, right
  sandbox:&:sandbox <- get *env, sandbox:offset
  row, screen <- render-sandboxes screen, sandbox, left, right, row, render-from, 0, env
  clear-rest-of-screen screen, row, left, right
  #
  assert-no-scroll screen, old-top-idx
]

def render-sandboxes screen:&:screen, sandbox:&:sandbox, left:num, right:num, row:num, render-from:num, idx:num -> row:num, screen:&:screen, sandbox:&:sandbox [
  local-scope
  load-inputs
  env:&:environment, _/optional <- next-input
  return-unless sandbox
  screen-height:num <- screen-height screen
  hidden?:bool <- lesser-than idx, render-from
  {
    break-if hidden?
    # render sandbox menu
    row <- add row, 1
    at-bottom?:bool <- greater-or-equal row, screen-height
    return-if at-bottom?
    screen <- move-cursor screen, row, left
    screen <- render-sandbox-menu screen, idx, left, right
    # save menu row so we can detect clicks to it later
    *sandbox <- put *sandbox, starting-row-on-screen:offset, row
    # render sandbox contents
    row <- add row, 1
    screen <- move-cursor screen, row, left
    sandbox-data:text <- get *sandbox, data:offset
    row, screen <- render-code screen, sandbox-data, left, right, row
    *sandbox <- put *sandbox, code-ending-row-on-screen:offset, row
    # render sandbox warnings, screen or response, in that order
    sandbox-response:text <- get *sandbox, response:offset
    <render-sandbox-results>
    {
      sandbox-screen:&:screen <- get *sandbox, screen:offset
      empty-screen?:bool <- fake-screen-is-empty? sandbox-screen
      break-if empty-screen?
      row, screen <- render-screen screen, sandbox-screen, left, right, row
    }
    {
      break-unless empty-screen?
      <render-sandbox-response>
      row, screen <- render-text screen, sandbox-response, left, right, 245/grey, row
    }
    +render-sandbox-end
    at-bottom?:bool <- greater-or-equal row, screen-height
    return-if at-bottom?
    # draw solid line after sandbox
    draw-horizontal screen, row, left, right
  }
  # if hidden, reset row attributes
  {
    break-unless hidden?
    *sandbox <- put *sandbox, starting-row-on-screen:offset, 0
    *sandbox <- put *sandbox, code-ending-row-on-screen:offset, 0
    <end-render-sandbox-reset-hidden>
  }
  # draw next sandbox
  next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset
  next-idx:num <- add idx, 1
  row, screen <- render-sandboxes screen, next-sandbox, left, right, row, render-from, next-idx, env
]

def render-sandbox-menu screen:&:screen, sandbox-index:num, left:num, right:num -> screen:&:screen [
  local-scope
  load-inputs
  move-cursor-to-column screen, left
  edit-button-left:num, edit-button-right:num, copy-button-left:num, copy-button-right:num, delete-button-left:num <- sandbox-menu-columns left, right
  print screen, sandbox-index, 232/dark-grey, 245/grey
  start-buttons:num <- subtract edit-button-left, 1
  clear-line-until screen, start-buttons, 245/grey
  print screen, [edit], 232/black, 25/background-blue
  clear-line-until screen, edit-button-right, 25/background-blue
  print screen, [copy], 232/black, 58/background-green
  clear-line-until screen, copy-button-right, 58/background-green
  print screen, [delete], 232/black, 52/background-red
  clear-line-until screen, right, 52/background-red
]

scenario skip-rendering-sandbox-menu-past-bottom-row [
  trace-until 100/app  # trace too long
  assume-screen 50/width, 6/height
  # recipes.mu is empty
  assume-resources [
    [lesson/0] <- [|add 2, 2|]
    [lesson/1] <- [|add 1, 1|]
  ]
  # create two sandboxes such that the top one just barely fills the screen
  env:&:environment <- new-programming-environment resources, screen, []
  env <- restore-sandboxes env, resources
  run [
    render-all screen, env, render
  ]
  screen-should-contain [
    .                               run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .add 2, 2                                          .
    .──────────────────────────────────────────────────.
  ]
]

# divide up the menu bar for a sandbox into 3 segments, for edit/copy/delete buttons
# delete-button-right == right
# all left/right pairs are inclusive
def sandbox-menu-columns left:num, right:num -> edit-button-left:num, edit-button-right:num, copy-button-left:num, copy-button-right:num, delete-button-left:num [
  local-scope
  load-inputs
  start-buttons:num <- add left, 4/space-for-sandbox-index
  buttons-space:num <- subtract right, start-buttons
  button-width:num <- divide-with-remainder buttons-space, 3  # integer division
  buttons-wide-enough?:bool <- greater-or-equal button-width, 8
  assert buttons-wide-enough?, [sandbox must be at least 30 or so characters wide]
  edit-button-left:num <- copy start-buttons
  copy-button-left:num <- add start-buttons, button-width
  edit-button-right:num <- subtract copy-button-left, 1
  delete-button-left:num <- subtract right, button-width
  copy-button-right:num <- subtract delete-button-left, 1
]

# print a text 's' to 'editor' in 'color' starting at 'row'
# clear rest of last line, move cursor to next line
def render-text screen:&:screen, s:text, left:num, right:num, color:num, row:num -> row:num, screen:&:screen [
  local-scope
  load-inputs
  return-unless s
  column:num <- copy left
  screen <- move-cursor screen, row, column
  screen-height:num <- screen-height screen
  i:num <- copy 0
  len:num <- length *s
  {
    +next-character
    done?:bool <- greater-or-equal i, len
    break-if done?
    done? <- greater-or-equal row, screen-height
    break-if done?
    c:char <- index *s, i
    {
      # newline? move to left rather than 0
      newline?:bool <- equal c, 10/newline
      break-unless newline?
      # clear rest of line in this window
      {
        done?:bool <- greater-than column, right
        break-if done?
        space:char <- copy 32/space
        print screen, space
        column <- add column, 1
        loop
      }
      row <- add row, 1
      column <- copy left
      screen <- move-cursor screen, row, column
      i <- add i, 1
      loop +next-character
    }
    {
      # at right? wrap.
      at-right?:bool <- equal column, right
      break-unless at-right?
      # print wrap icon
      wrap-icon:char <- copy 8617/loop-back-to-left
      print screen, wrap-icon, 245/grey
      column <- copy left
      row <- add row, 1
      screen <- move-cursor screen, row, column
      # don't increment i
      loop +next-character
    }
    i <- add i, 1
    print screen, c, color
    column <- add column, 1
    loop
  }
  was-at-left?:bool <- equal column, left
  clear-line-until screen, right
  {
    break-if was-at-left?
    row <- add row, 1
  }
  move-cursor screen, row, left
]

scenario render-text-wraps-barely-long-lines [
  local-scope
  assume-screen 5/width, 5/height
  run [
    render-text screen, [abcde], 0/left, 4/right, 7/white, 1/row
  ]
  screen-should-contain [
    .     .
    .abcd.
    .e    .
    .     .
  ]
]

# assumes programming environment has no sandboxes; restores them from previous session
def restore-sandboxes env:&:environment, resources:&:resources -> env:&:environment [
  local-scope
  load-inputs
  # read all scenarios, pushing them to end of a list of scenarios
  idx:num <- copy 0
  curr:&:sandbox <- copy null
  prev:&:sandbox <- copy null
  {
    filename:text <- append [lesson/], idx
    contents:text <- slurp resources, filename
    break-unless contents  # stop at first error; assuming file didn't exist
                           # todo: handle empty sandbox
    # create new sandbox for file
    curr <- new sandbox:type
    *curr <- put *curr, data:offset, contents
    <end-restore-sandbox>
    {
      break-if idx
      *env <- put *env, sandbox:offset, curr
    }
    {
      break-unless idx
      *prev <- put *prev, next-sandbox:offset, curr
    }
    idx <- add idx, 1
    prev <- copy curr
    loop
  }
  # update sandbox count
  *env <- put *env, number-of-sandboxes:offset, idx
]

# print the fake sandbox screen to 'screen' with appropriate delimiters
# leave cursor at start of next line
def render-screen screen:&:screen, sandbox-screen:&:screen, left:num, right:num, row:num -> row:num, screen:&:screen [
  local-scope
  load-inputs
  return-unless sandbox-screen
  # print 'screen:'
  row <- render-text screen, [screen:], left, right, 245/grey, row
  screen <- move-cursor screen, row, left
  # start printing sandbox-screen
  column:num <- copy left
  s-width:num <- screen-width sandbox-screen
  s-height:num <- screen-height sandbox-screen
  buf:&:@:screen-cell <- get *sandbox-screen, data:offset
  stop-printing:num <- add left, s-width, 3
  max-column:num <- min stop-printing, right
  i:num <- copy 0
  len:num <- length *buf
  screen-height:num <- screen-height screen
  {
    done?:bool <- greater-or-equal i, len
    break-if done?
    done? <- greater-or-equal row, screen-height
    break-if done?
    column <- copy left
    screen <- move-cursor screen, row, column
    # initial leader for each row: two spaces and a '.'
    space:char <- copy 32/space
    print screen, space, 245/grey
    print screen, space, 245/grey
    full-stop:char <- copy 46/period
    print screen, full-stop, 245/grey
    column <- add left, 3
    {
      # print row
      row-done?:bool <- greater-or-equal column, max-column
      break-if row-done?
      curr:screen-cell <- index *buf, i
      c:char <- get curr, contents:offset
      color:num <- get curr, color:offset
      {
        # damp whites down to grey
        white?:bool <- equal color, 7/white
        break-unless white?
        color <- copy 245/grey
      }
      print screen, c, color
      column <- add column, 1
      i <- add i, 1
      loop
    }
    # print final '.'
    print screen, full-stop, 245/grey
    column <- add column, 1
    {
      # clear rest of current line
      line-done?:bool <- greater-than column, right
      break-if line-done?
      print screen, space
      column <- add column, 1
      loop
    }
    row <- add row, 1
    loop
  }
]

scenario run-updates-results [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 12/height
  # define a recipe (no indent for the 'add' line below so column numbers are more obvious)
  assume-resources [
    [lesson/recipes.mu] <- [
      ||
      |recipe foo [|
      |  local-scope|
      |  z:num <- add 2, 2|
      |  reply z|
      |]|
    ]
  ]
  # sandbox editor contains an instruction without storing outputs
  env:&:environment <- new-programming-environment resources, screen, [foo]  # contents of sandbox editor
  render-all screen, env, render
  $clear-trace
  # run the code in the editors
  assume-console [
    press F4
  ]
  event-loop screen, console, env, resources
  screen-should-contain [
    .                               run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo                                               .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # the new sandbox should be saved to disk
  trace-should-contain [
    app: save sandboxes
  ]
  # make a change (incrementing one of the args to 'add'), then rerun
  assume-resources [
    [lesson/recipes.mu] <- [
      ||
      |recipe foo [|
      |  local-scope|
      |  z:num <- add 2, 3|
      |  reply z|
      |]|
    ]
  ]
  $clear-trace
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # check that screen updates the result on the right
  screen-should-contain [
    .                               run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .foo                                               .
    .5                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # no need to save sandboxes all over again
  trace-should-not-contain [
    app: save sandboxes
  ]
]

scenario run-instruction-manages-screen-per-sandbox [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  # empty recipes
  assume-resources [
  ]
  # sandbox editor contains an instruction
  env:&:environment <- new-programming-environment resources, screen, [print screen, 4]  # contents of sandbox editor
  render-all screen, env, render
  # run the code in the editor
  assume-console [
    press F4
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # check that it prints a little toy screen
  screen-should-contain [
    .                               run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .print screen, 4                                   .
    .screen:                                           .
    .  .4                             .                .
    .  .                              .                .
    .  .                              .                .
    .  .                              .                .
    .  .                              .                .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
]

def editor-contents editor:&:editor -> result:text [
  local-scope
  load-inputs
  buf:&:buffer:char <- new-buffer 80
  curr:&:duplex-list:char <- get *editor, data:offset
  # skip § sentinel
  assert curr, [editor without data is illegal; must have at least a sentinel]
  curr <- next curr
  return-unless curr, null
  {
    break-unless curr
    c:char <- get *curr, value:offset
    buf <- append buf, c
    curr <- next curr
    loop
  }
  result <- buffer-to-array buf
]

scenario editor-provides-edited-contents [
  local-scope
  assume-screen 10/width, 5/height
  e:&:editor <- new-editor [abc], 0/left, 10/right
  assume-console [
    left-click 1, 2
    type [def]
  ]
  run [
    editor-event-loop screen, console, e
    s:text <- editor-contents e
    1:@:char/raw <- copy *s
  ]
  memory-should-contain [
    1:array:character <- [abdefc]
  ]
]

# scrolling through sandboxes

scenario scrolling-down-past-bottom-of-sandbox-editor [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  # initialize
  assume-resources [
  ]
  env:&:environment <- new-programming-environment resources, screen, [add 2, 2]
  render-all screen, env, render
  assume-console [
    # create a sandbox
    press F4
  ]
  event-loop screen, console, env, resources
  screen-should-contain [
    .                               run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .add 2, 2                                          .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # hit 'page-down'
  assume-console [
    press page-down
  ]
  run [
    event-loop screen, console, env, resources
    cursor:char <- copy 9251/    print screen, cursor
  ]
  # sandbox editor hidden; first sandbox displayed
  # cursor moves to first sandbox
  screen-should-contain [
    .                               run (F4)           .
    .──────────────────────────────────────────────────.
    .   edit           copy           delete          .
    .add 2, 2                                          .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # hit 'page-up'
  assume-console [
    press page-up
  ]
  run [
    event-loop screen, console, env, resources
    cursor:char <- copy 9251/    print screen, cursor
  ]
  # sandbox editor displays again
  screen-should-contain [
    .                               run (F4)           .
    .                                                 .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .add 2, 2                                          .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
]

# page-down updates render-from to scroll sandboxes
after <global-keypress> [
  {
    page-down?:bool <- equal k, 65518/page-down
    break-unless page-down?
    sandbox:&:sandbox <- get *env, sandbox:offset
    break-unless sandbox
    # slide down if possible
    {
      render-from:num <- get *env, render-from:offset
      number-of-sandboxes:num <- get *env, number-of-sandboxes:offset
      max:num <- subtract number-of-sandboxes, 1
      at-end?:bool <- greater-or-equal render-from, max
      break-if at-end?
      render-from <- add render-from, 1
      *env <- put *env, render-from:offset, render-from
    }
    screen <- render-sandbox-side screen, env, render
    screen <- update-cursor screen, current-sandbox, env
    loop +next-event
  }
]

# update-cursor takes render-from into account
after <update-cursor-special-cases> [
  {
    render-from:num <- get *env, render-from:offset
    scrolling?:bool <- greater-or-equal render-from, 0
    break-unless scrolling?
    cursor-column:num <- get *current-sandbox, left:offset
    screen <- move-cursor screen, 2/row, cursor-column  # highlighted sandbox will always start at row 2
    return
  }
]

# 'page-up' is like 'page-down': updates first-sandbox-to-render when necessary
after <global-keypress> [
  {
    page-up?:bool <- equal k, 65519/page-up
    break-unless page-up?
    render-from:num <- get *env, render-from:offset
    at-beginning?:bool <- equal render-from, -1
    break-if at-beginning?
    render-from <- subtract render-from, 1
    *env <- put *env, render-from:offset, render-from
    screen <- render-sandbox-side screen, env, render
    screen <- update-cursor screen, current-sandbox, env
    loop +next-event
  }
]

# sandbox belonging to 'env' whose next-sandbox is 'in'
# return null if there's no such sandbox, either because 'in' doesn't exist in 'env', or because it's the first sandbox
def previous-sandbox env:&:environment, in:&:sandbox -> out:&:sandbox [
  local-scope
  load-inputs
  curr:&:sandbox <- get *env, sandbox:offset
  return-unless curr, null
  next:&:sandbox <- get *curr, next-sandbox:offset
  {
    return-unless next, null
    found?:bool <- equal next, in
    break-if found?
    curr <- copy next
    next <- get *curr, next-sandbox:offset
    loop
  }
  return curr
]

scenario scrolling-through-multiple-sandboxes [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  # initialize environment
  assume-resources [
  ]
  env:&:environment <- new-programming-environment resources, screen, []
  render-all screen, env, render
  # create 2 sandboxes
  assume-console [
    press ctrl-n
    type [add 2, 2]
    press F4
    type [add 1, 1]
    press F4
  ]
  event-loop screen, console, env, resources
  cursor:char <- copy 9251/  print screen, cursor
  screen-should-contain [
    .                               run (F4)           .
    .                                                 .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .add 1, 1                                          .
    .2                                                 .
    .──────────────────────────────────────────────────.
    .1   edit           copy           delete          .
    .add 2, 2                                          .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # hit 'page-down'
  assume-console [
    press page-down
  ]
  run [
    event-loop screen, console, env, resources
    cursor:char <- copy 9251/    print screen, cursor
  ]
  # sandbox editor hidden; first sandbox displayed
  # cursor moves to first sandbox
  screen-should-contain [
    .                               run (F4)           .
    .──────────────────────────────────────────────────.
    .   edit           copy           delete          .
    .add 1, 1                                          .
    .2                                                 .
    .──────────────────────────────────────────────────.
    .1   edit           copy           delete          .
    .add 2, 2                                          .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # hit 'page-down' again
  assume-console [
    press page-down
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # just second sandbox displayed
  screen-should-contain [
    .                               run (F4)           .
    .──────────────────────────────────────────────────.
    .1   edit           copy           delete          .
    .add 2, 2                                          .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # hit 'page-down' again
  assume-console [
    press page-down
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # no change
  screen-should-contain [
    .                               run (F4)           .
    .──────────────────────────────────────────────────.
    .1   edit           copy           delete          .
    .add 2, 2                                          .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # hit 'page-up'
  assume-console [
    press page-up
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # back to displaying both sandboxes without editor
  screen-should-contain [
    .                               run (F4)           .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .add 1, 1                                          .
    .2                                                 .
    .──────────────────────────────────────────────────.
    .1   edit           copy           delete          .
    .add 2, 2                                          .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # hit 'page-up' again
  assume-console [
    press page-up
  ]
  run [
    event-loop screen, console, env, resources
    cursor:char <- copy 9251/    print screen, cursor
  ]
  # back to displaying both sandboxes as well as editor
  screen-should-contain [
    .                               run (F4)           .
    .                                                 .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .add 1, 1                                          .
    .2                                                 .
    .──────────────────────────────────────────────────.
    .1   edit           copy           delete          .
    .add 2, 2                                          .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # hit 'page-up' again
  assume-console [
    press page-up
  ]
  run [
    event-loop screen, console, env, resources
    cursor:char <- copy 9251/    print screen, cursor
  ]
  # no change
  screen-should-contain [
    .                               run (F4)           .
    .                                                 .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .add 1, 1                                          .
    .2                                                 .
    .──────────────────────────────────────────────────.
    .1   edit           copy           delete          .
    .add 2, 2                                          .
    .4                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
]

scenario scrolling-manages-sandbox-index-correctly [
  local-scope
  trace-until 100/app  # trace too long
  assume-screen 50/width, 20/height
  # initialize environment
  assume-resources [
  ]
  env:&:environment <- new-programming-environment resources, screen, []
  render-all screen, env, render
  # create a sandbox
  assume-console [
    press ctrl-n
    type [add 1, 1]
    press F4
  ]
  event-loop screen, console, env, resources
  screen-should-contain [
    .                               run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .add 1, 1                                          .
    .2                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # hit 'page-down' and 'page-up' a couple of times. sandbox index should be stable
  assume-console [
    press page-down
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # sandbox editor hidden; first sandbox displayed
  # cursor moves to first sandbox
  screen-should-contain [
    .                               run (F4)           .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .add 1, 1                                          .
    .2                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # hit 'page-up' again
  assume-console [
    press page-up
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # back to displaying both sandboxes as well as editor
  screen-should-contain [
    .                               run (F4)           .
    .                                                  .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .
    .add 1, 1                                          .
    .2                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
  # hit 'page-down'
  assume-console [
    press page-down
  ]
  run [
    event-loop screen, console, env, resources
  ]
  # sandbox editor hidden; first sandbox displayed
  # cursor moves to first sandbox
  screen-should-contain [
    .                               run (F4)           .
    .──────────────────────────────────────────────────.
    .0   edit           copy           delete          .  # no change
    .add 1, 1                                          .
    .2                                                 .
    .──────────────────────────────────────────────────.
    .                                                  .
  ]
]