## 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 a maybe
# few other things.
#
# This layer draws the menubar buttons non-editable sandboxes but they don't
# do anything yet. Later layers implement each button.
def! main [
local-scope
open-console
initial-recipe:text <- restore [recipes.mu]
initial-sandbox:text <- new []
hide-screen 0/screen
env:&:environment <- new-programming-environment 0/screen, initial-recipe, initial-sandbox
env <- restore-sandboxes env
render-all 0/screen, env, render
event-loop 0/screen, 0/console, env
# never gets here
]
container environment [
sandbox:&:sandbox # list of sandboxes, from top to bottom
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 100/width, 15/height
# recipe editor is empty
# sandbox editor contains an instruction without storing outputs
env:&:environment <- new-programming-environment screen, [], [divide-with-remainder 11, 3]
# run the code in the editors
assume-console [
press F4
]
run [
event-loop screen, console, env
]
# 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 .
. ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊ .
]
# sandbox menu in reverse video
screen-should-contain-in-color 232/black, [
. .
. .
. .
. 0 edit copy delete .
]
# run another command
assume-console [
left-click 1, 80
type [add 2, 2]
press F4
]
run [
event-loop screen, console, env
]
# 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, env, screen <- run-sandboxes env, 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, recipes, current-sandbox, sandbox-in-focus?, env
loop +next-event:label
}
]
def run-sandboxes env:&:environment, screen:&:screen -> errors-found?:bool, env:&:environment, screen:&:screen [
local-scope
load-ingredients
errors-found?:bool, env, screen <- update-recipes env, screen
return-if errors-found?
# check contents of right editor (sandbox)
<run-sandboxes-begin>
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
# clear sandbox editor
init:&:duplex-list:char <- push 167/§, 0/tail
*current-sandbox <- put *current-sandbox, data:offset, init
*current-sandbox <- put *current-sandbox, top-of-screen:offset, init
}
# save all sandboxes before running, just in case we die when running
save-sandboxes env
# 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
}
<run-sandboxes-end>
]
# copy code from recipe editor, persist, load into mu
# replaced in a later layer (whereupon errors-found? will actually be set)
def update-recipes env:&:environment, screen:&:screen -> errors-found?:bool, env:&:environment, screen:&:screen [
local-scope
load-ingredients
recipes:&:editor <- get *env, recipes:offset
in:text <- editor-contents recipes
save [recipes.mu], in # newlayer: persistence
reload in
errors-found? <- copy 0/false
]
# replaced in a later layer
def! update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [
local-scope
load-ingredients
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-ingredients
screen <- move-cursor screen, 0, 2
screen <- print screen, msg, color, 238/grey/background
]
def save-sandboxes env:&:environment [
local-scope
load-ingredients
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
data:text <- get *curr, data:offset
filename:text <- to-text idx
save filename, data
<end-save-sandbox>
idx <- add idx, 1
curr <- get *curr, next-sandbox:offset
loop
}
]
def! render-sandbox-side screen:&:screen, env:&:environment, {render-editor: (recipe (address screen) (address editor) -> number number (address screen) (address editor))} -> screen:&:screen, env:&:environment [
local-scope
load-ingredients
trace 11, [app], [render sandbox side]
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
clear-screen-from screen, row, column, left, right
row <- add row, 1
}
# render sandboxes
draw-horizontal screen, row, left, right, 9473/horizontal-double
sandbox:&:sandbox <- get *env, sandbox:offset
row, screen <- render-sandboxes screen, sandbox, left, right, row, render-from
clear-rest-of-screen screen, row, left, right
]
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-ingredients
return-unless sandbox
screen-height:num <- screen-height screen
at-bottom?:bool <- greater-or-equal row, screen-height
return-if at-bottom?:bool
hidden?:bool <- lesser-than idx, render-from
{
break-if hidden?
# render sandbox menu
row <- add row, 1
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, 9473/horizontal-double
}
# 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
]
def render-sandbox-menu screen:&:screen, sandbox-index:num, left:num, right:num -> screen:&:screen [
local-scope
load-ingredients
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, 94/background-orange
clear-line-until screen, edit-button-right, 94/background-orange
_, col:num <- cursor-position screen
at-start-of-copy-button?:bool <- equal col, copy-button-left
assert at-start-of-copy-button?, [aaa]
print screen, [copy], 232/black, 58/background-green
clear-line-until screen, copy-button-right, 58/background-green
_, col:num <- cursor-position screen
at-start-of-delete-button?:bool <- equal col, delete-button-left
assert at-start-of-delete-button?, [bbb]
print screen, [delete], 232/black, 52/background-red
clear-line-until screen, right, 52/background-red
]
# 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-ingredients
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-ingredients
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
{
# 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
loop +next-character:label # retry i
}
i <- add i, 1
{
# 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
loop +next-character:label
}
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
]
# assumes programming environment has no sandboxes; restores them from previous session
def restore-sandboxes env:&:environment -> env:&:environment [
local-scope
load-ingredients
# read all scenarios, pushing them to end of a list of scenarios
idx:num <- copy 0
curr:&:sandbox <- copy 0
prev:&:sandbox <- copy 0
{
filename:text <- to-text idx
contents:text <- restore 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-ingredients
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 100/width, 12/height
# define a recipe (no indent for the 'add' line below so column numbers are more obvious)
recipes:text <- new [
recipe foo [
local-scope
z:num <- add 2, 2
reply z
]]
# sandbox editor contains an instruction without storing outputs
env:&:environment <- new-programming-environment screen, recipes, [foo]
# run the code in the editors
assume-console [
press F4
]
event-loop screen, console, env
screen-should-contain [
. run (F4) .
. ┊ .
.recipe foo [ ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
.local-scope ┊0 edit copy delete .
.z:num <- add 2, 2 ┊foo .
.reply z ┊4 .
.] ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ .
. ┊ .
]
# make a change (incrementing one of the args to 'add'), then rerun
assume-console [
left-click 4, 28 # one past the value of the second arg
press backspace
type [3]
press F4
]
run [
event-loop screen, console, env
]
# check that screen updates the result on the right
screen-should-contain [
. run (F4) .
. ┊ .
.recipe foo [ ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
.local-scope ┊0 edit copy delete .
.z:num <- add 2, 3 ┊foo .
.reply z ┊5 .
.] ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ .
. ┊ .
]
]
scenario run-instruction-manages-screen-per-sandbox [
local-scope
trace-until 100/app # trace too long
assume-screen 100/width, 20/height
# left editor is empty
# right editor contains an instruction
env:&:environment <- new-programming-environment screen, [], [print-integer screen, 4]
# run the code in the editor
assume-console [
press F4
]
run [
event-loop screen, console, env
]
# check that it prints a little toy screen
screen-should-contain [
. run (F4) .
. ┊ .
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊0 edit copy delete .
. ┊print-integer screen, 4 .
. ┊screen: .
. ┊ .4 . .
. ┊ . . .
. ┊ . . .
. ┊ . . .
. ┊ . . .
. ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊ .
]
]
def editor-contents editor:&:editor -> result:text [
local-scope
load-ingredients
buf:&:buffer <- 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, 0
{
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], screen, 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]
]
]
# keep the bottom of recipes from scrolling off the screen
scenario scrolling-down-past-bottom-of-recipe-editor [
local-scope
trace-until 100/app
assume-screen 100/width, 10/height
env:&:environment <- new-programming-environment screen, [], []
render-all screen, env, render
assume-console [
press enter
press down-arrow
]
event-loop screen, console, env
# no scroll
screen-should-contain [
. run (F4) .
. ┊ .
. ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ .
. ┊ .
]
]
scenario cursor-down-in-recipe-editor [
local-scope
trace-until 100/app
assume-screen 100/width, 10/height
env:&:environment <- new-programming-environment screen, [], []
render-all screen, env, render
assume-console [
press enter
press up-arrow
press down-arrow # while cursor isn't at bottom
]
event-loop screen, console, env
cursor:char <- copy 9251/␣
print screen, cursor
# cursor moves back to bottom
screen-should-contain [
. run (F4) .
. ┊ .
.␣ ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ .
. ┊ .
]
]
# we'll not use the recipe-editor's 'bottom' element directly, because later
# layers will add other stuff to the left side below the editor (error messages)
container environment [
recipe-bottom:num
]
after <render-recipe-components-end> [
*env <- put *env, recipe-bottom:offset, row
]
after <global-keypress> [
{
break-if sandbox-in-focus?
down-arrow?:bool <- equal k, 65516/down-arrow
break-unless down-arrow?
recipe-editor:&:editor <- get *env, recipes:offset
recipe-cursor-row:num <- get *recipe-editor, cursor-row:offset
recipe-editor-bottom:num <- get *recipe-editor, bottom:offset
at-bottom-of-editor?:bool <- greater-or-equal recipe-cursor-row, recipe-editor-bottom
break-unless at-bottom-of-editor?
more-to-scroll?:bool <- more-to-scroll? env, screen
break-if more-to-scroll?
loop +next-event:label
}
{
break-if sandbox-in-focus?
page-down?:bool <- equal k, 65518/page-down
break-unless page-down?
more-to-scroll?:bool <- more-to-scroll? env, screen
break-if more-to-scroll?
loop +next-event:label
}
]
after <global-type> [
{
break-if sandbox-in-focus?
page-down?:bool <- equal k, 6/ctrl-f
break-unless page-down?
more-to-scroll?:bool <- more-to-scroll? env, screen
break-if more-to-scroll?
loop +next-event:label
}
]
def more-to-scroll? env:&:environment, screen:&:screen -> result:bool [
local-scope
load-ingredients
recipe-bottom:num <- get *env, recipe-bottom:offset
height:num <- screen-height screen
result <- greater-or-equal recipe-bottom, height
]
scenario scrolling-down-past-bottom-of-recipe-editor-2 [
local-scope
trace-until 100/app
assume-screen 100/width, 10/height
env:&:environment <- new-programming-environment screen, [], []
render-all screen, env, render
assume-console [
# add a line
press enter
# cursor back to top line
press up-arrow
# try to scroll
press page-down # or ctrl-f
]
event-loop screen, console, env
# no scroll, and cursor remains at top line
screen-should-contain [
. run (F4) .
. ┊ .
. ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ .
. ┊ .
]
]
scenario scrolling-down-past-bottom-of-recipe-editor-3 [
local-scope
trace-until 100/app
assume-screen 100/width, 10/height
env:&:environment <- new-programming-environment screen, [], [ab
cd]
render-all screen, env, render
assume-console [
# add a line
press enter
# switch to sandbox
press ctrl-n
# move cursor
press down-arrow
]
event-loop screen, console, env
cursor:char <- copy 9251/␣
print screen, cursor
# no scroll on recipe side, cursor moves on sandbox side
screen-should-contain [
. run (F4) .
. ┊ab .
. ┊␣d .
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊ .
]
]
# scrolling through sandboxes
scenario scrolling-down-past-bottom-of-sandbox-editor [
local-scope
trace-until 100/app # trace too long
assume-screen 100/width, 10/height
# initialize sandbox side
env:&:environment <- new-programming-environment screen, [], [add 2, 2]
render-all screen, env, render
assume-console [
# create a sandbox
press F4
]
event-loop screen, console, env
screen-should-contain [
. run (F4) .
. ┊ .
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊0 edit copy delete .
. ┊add 2, 2 .
]
# switch to sandbox window and hit 'page-down'
assume-console [
press ctrl-n
press page-down
]
run [
event-loop screen, console, env
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
cursor:char <- copy 9251/␣
print screen, cursor
]
# sandbox editor displays again, cursor is in editor
screen-should-contain [
. run (F4) .
. ┊␣ .
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊0 edit copy delete .
. ┊add 2, 2 .
]
]
# page-down on sandbox side updates render-from to scroll sandboxes
after <global-keypress> [
{
break-unless sandbox-in-focus?
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
jump-if at-end?, +finish-event:label # render nothing
render-from <- add render-from, 1
*env <- put *env, render-from:offset, render-from
}
hide-screen screen
screen <- render-sandbox-side screen, env, render
show-screen screen
jump +finish-event:label
}
]
# update-cursor takes render-from into account
after <update-cursor-special-cases> [
{
break-unless sandbox-in-focus?
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' on sandbox side is like 'page-down': updates render-from when necessary
after <global-keypress> [
{
break-unless sandbox-in-focus?
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
hide-screen screen
screen <- render-sandbox-side screen, env, render
show-screen screen
jump +finish-event:label
}
]
# sandbox belonging to 'env' whose next-sandbox is 'in'
# return 0 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-ingredients
curr:&:sandbox <- get *env, sandbox:offset
return-unless curr, 0/nil
next:&:sandbox <- get *curr, next-sandbox:offset
{
return-unless next, 0/nil
found?:bool <- equal next, in
break-if found?
curr <- copy next
next <- get *curr, next-sandbox:offset
loop
}
return curr
]
scenario scrolling-down-on-recipe-side [
local-scope
trace-until 100/app # trace too long
assume-screen 100/width, 10/height
# initialize sandbox side and create a sandbox
recipes:text <- new [
]
# create a sandbox
env:&:environment <- new-programming-environment screen, recipes:text, [add 2, 2]
render-all screen, env, render
assume-console [
press F4
]
event-loop screen, console, env
# hit 'down' in recipe editor
assume-console [
press page-down
]
run [
event-loop screen, console, env
cursor:char <- copy 9251/␣
print screen, cursor
]
# cursor moves down on recipe side
screen-should-contain [
. run (F4) .
.␣ ┊ .
. ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete .
. ┊add 2, 2 .
]
]
scenario scrolling-through-multiple-sandboxes [
local-scope
trace-until 100/app # trace too long
assume-screen 100/width, 10/height
# initialize environment
env:&:environment <- new-programming-environment 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
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
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
]
# 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
]
# 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
]
# 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
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
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 100/width, 10/height
# initialize environment
env:&:environment <- new-programming-environment 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
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
]
# 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
]
# 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
]
# sandbox editor hidden; first sandbox displayed
# cursor moves to first sandbox
screen-should-contain [
. run (F4) .
. ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete .
. ┊add 1, 1 .
. ┊2 .
. ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊ .
]
]