-- major tests for text editing flows -- Arguably this should be called edit_tests.lua, -- but that would mess up the git blame at this point. function test_initial_state() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{} Text.redraw_all(Editor_state) edit.draw(Editor_state) check_eq(#Editor_state.lines, 1, '#lines') check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos') check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line') check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos') end function test_click_to_create_drawing() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{} Text.redraw_all(Editor_state) edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, 8,Editor_state.top+8, 1) -- cursor skips drawing to always remain on text check_eq(#Editor_state.lines, 2, '#lines') check_eq(Editor_state.cursor1.line, 2, 'cursor') end function test_backspace_to_delete_drawing() -- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end) App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'```lines', '```', ''} Text.redraw_all(Editor_state) -- cursor is on text as always (outside tests this will get initialized correctly) Editor_state.cursor1.line = 2 -- backspacing deletes the drawing edit.run_after_keychord(Editor_state, 'backspace') check_eq(#Editor_state.lines, 1, '#lines') check_eq(Editor_state.cursor1.line, 1, 'cursor') end function test_backspace_from_start_of_final_line() -- display final line of text with cursor at start of it App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc', 'def'} Editor_state.screen_top1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1} Text.redraw_all(Editor_state) -- backspace scrolls up edit.run_after_keychord(Editor_state, 'backspace') check_eq(#Editor_state.lines, 1, '#lines') check_eq(Editor_state.cursor1.line, 1, 'cursor') check_eq(Editor_state.screen_top1.line, 1, 'screen_top') end function test_insert_first_character() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{} Text.redraw_all(Editor_state) edit.draw(Editor_state) edit.run_after_text_input(Editor_state, 'a') local y = Editor_state.top App.screen.check(y, 'a', 'screen:1') end function test_press_ctrl() -- press ctrl while the cursor is on text App.screen.init{width=50, height=80} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{''} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} edit.run_after_keychord(Editor_state, 'C-m') end function test_move_left() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'a'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=2} edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'left') check_eq(Editor_state.cursor1.pos, 1, 'check') end function test_move_right() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'a'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'right') check_eq(Editor_state.cursor1.pos, 2, 'check') end function test_move_left_to_previous_line() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc', 'def'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'left') check_eq(Editor_state.cursor1.line, 1, 'line') check_eq(Editor_state.cursor1.pos, 4, 'pos') -- past end of line end function test_move_right_to_next_line() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc', 'def'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=4} -- past end of line edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'right') check_eq(Editor_state.cursor1.line, 2, 'line') check_eq(Editor_state.cursor1.pos, 1, 'pos') end function test_move_to_start_of_word() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=3} edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'M-left') check_eq(Editor_state.cursor1.pos, 1, 'check') end function test_move_to_start_of_previous_word() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc def'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=4} -- at the space between words edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'M-left') check_eq(Editor_state.cursor1.pos, 1, 'check') end function test_skip_to_previous_word() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc def'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=5} -- at the start of second word edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'M-left') check_eq(Editor_state.cursor1.pos, 1, 'check') end function test_skip_past_tab_to_previous_word() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc def\tghi'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=10} -- within third word edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'M-left') check_eq(Editor_state.cursor1.pos, 9, 'check') end function test_skip_multiple_spaces_to_previous_word() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc def'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=6} -- at the start of second word edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'M-left') check_eq(Editor_state.cursor1.pos, 1, 'check') end function test_move_to_start_of_word_on_previous_line() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc def', 'ghi'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'M-left') check_eq(Editor_state.cursor1.line, 1, 'line') check_eq(Editor_state.cursor1.pos, 5, 'pos') end function test_move_past_end_of_word() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc def'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'M-right') check_eq(Editor_state.cursor1.pos, 4, 'check') end function test_skip_to_next_word() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc def'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=4} -- at the space between words edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'M-right') check_eq(Editor_state.cursor1.pos, 8, 'check') end function test_skip_past_tab_to_next_word() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc\tdef'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} -- at the space between words edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'M-right') check_eq(Editor_state.cursor1.pos, 4, 'check') end function test_skip_multiple_spaces_to_next_word() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc def'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=4} -- at the start of second word edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'M-right') check_eq(Editor_state.cursor1.pos, 9, 'check') end function test_move_past_end_of_word_on_next_line() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc def', 'ghi'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=8} edit.draw(Editor_state) edit.run_after_keychord(Editor_state, 'M-right') check_eq(Editor_state.cursor1.line, 2, 'line') check_eq(Editor_state.cursor1.pos, 4, 'pos') end function test_click_moves_cursor() App.screen.init{width=50, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc', 'def', 'xyz'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} Editor_state.selection1 = {} edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos') -- selection is empty to avoid perturbing future edits check_nil(Editor_state.selection1.line, 'selection:line') check_nil(Editor_state.selection1.pos, 'selection:pos') end function test_click_to_left_of_line() -- display a line with the cursor in the middle App.screen.init{width=50, height=80} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=3} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} Editor_state.selection1 = {} -- click to the left of the line edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1) -- cursor moves to start of line check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos') check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits') end function test_click_takes_margins_into_account() -- display two lines with cursor on one of them App.screen.init{width=100, height=80} Editor_state = edit.initialize_test_state() Editor_state.left = 50 -- occupy only right side of screen Editor_state.lines = load_array{'abc', 'def'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} Editor_state.selection1 = {} -- click on the other line edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) -- cursor moves check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos') check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits') end function test_click_on_empty_line() -- display two lines with the first one empty App.screen.init{width=50, height=80} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'', 'def'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} Editor_state.selection1 = {} -- click on the empty line edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) -- cursor moves check_eq(Editor_state.cursor1.line, 1, 'cursor') -- selection remains empty check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits') end function test_click_below_all_lines() -- display one line App.screen.init{width=50, height=80} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} Editor_state.selection1 = {} -- click below first line edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+50, 1) -- cursor doesn't move check_eq(Editor_state.cursor1.line, 1, 'cursor') -- selection remains empty check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits') end function test_draw_text() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc', 'def', 'ghi'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'screen:1') y = y + Editor_state.line_height App.screen.check(y, 'def', 'screen:2') y = y + Editor_state.line_height App.screen.check(y, 'ghi', 'screen:3') end function test_draw_wrapping_text() App.screen.init{width=50, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc', 'defgh', 'xyz'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'screen:1') y = y + Editor_state.line_height App.screen.check(y, 'de', 'screen:2') y = y + Editor_state.line_height App.screen.check(y, 'fgh', 'screen:3') end function test_draw_word_wrapping_text() App.screen.init{width=60, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc def ghi', 'jkl'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc ', 'screen:1') y = y + Editor_state.line_height App.screen.check(y, 'def ', 'screen:2') y = y + Editor_state.line_height App.screen.check(y, 'ghi', 'screen:3') end function test_click_on_wrapping_line() -- display two screen lines with cursor on one of them App.screen.init{width=50, height=80} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=20} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} -- click on the other line edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) -- cursor moves check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos') check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits') end function test_click_on_wrapping_line_takes_margins_into_account() -- display two screen lines with cursor on one of them App.screen.init{width=100, height=80} Editor_state = edit.initialize_test_state() Editor_state.left = 50 -- occupy only right side of screen Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=20} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} -- click on the other line edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) -- cursor moves check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos') check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits') end function test_draw_text_wrapping_within_word() -- arrange a screen line that needs to be split within a word App.screen.init{width=60, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'abcd e fghijk', 'xyz'} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abcd ', 'screen:1') y = y + Editor_state.line_height App.screen.check(y, 'e fgh', 'screen:2') y = y + Editor_state.line_height App.screen.check(y, 'ijk', 'screen:3') end function test_draw_wrapping_text_containing_non_ascii() -- draw a long line containing non-ASCII App.screen.init{width=60, height=60} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{'madam I’m adam', 'xyz'} -- notice the non-ASCII apostrophe Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'mad', 'screen:1') y = y + Editor_state.line_height App.screen.check(y, 'am I', 'screen:2') y = y + Editor_state.line_height App.screen.check(y, '’m a', 'screen:3') end function test_click_past_end_of_screen_line() -- display a wrapping line App.screen.init{width=75, height=80} Editor_state = edit.initialize_test_state() -- 12345678901234 Editor_state.lines = load_array{"madam I'm adam"} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'madam ', 'baseline/screen:1') y = y + Editor_state.line_height App.screen.check(y, "I'm ad", 'baseline/screen:2') y = y + Editor_state.line_height -- click past end of second screen line edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1) -- cursor moves to end of screen line check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.pos, 12, 'cursor:pos') end function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen() -- display a wrapping line from its second screen line App.screen.init{width=75, height=80} Editor_state = edit.initialize_test_state() -- 12345678901234 Editor_state.lines = load_array{"madam I'm adam"} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=8} Editor_state.screen_top1 = {line=1, pos=7} Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, "I'm ad", 'baseline/screen:2') y = y + Editor_state.line_height -- click past end of second screen line edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1) -- cursor moves to end of screen line check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.pos, 12, 'cursor:pos') end function test_click_past_end_of_wrapping_line() -- display a wrapping line App.screen.init{width=75, height=80} Editor_state = edit.initialize_test_state() -- 12345678901234 Editor_state.lines = load_array{"madam I'm adam"} Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'madam ', 'baseline/screen:1') y = y + Editor_state.line_height App.screen.check(y, "I'm ad", 'baseline/screen:2') y = y + Editor_state.line_height App.screen.check(y, 'am', 'baseline/screen:3') y = y + Editor_state.line_height -- click past the end of it edit.run_after_mouse_click(Editor_state, App.screen.w
## 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.
container programming-environment-data [
sandbox:address:sandbox-data # list of sandboxes, from top to bottom
]
container sandbox-data [
data:address:array:character
response:address:array:character
expected-response:address:array:character
# coordinates to track clicks
starting-row-on-screen:number
code-ending-row-on-screen:number # past end of code
response-starting-row-on-screen:number
screen:address:screen # prints in the sandbox go here
next-sandbox:address:sandbox-data
]
scenario run-and-show-results [
$close-trace # trace too long
assume-screen 100/width, 15/height
# recipe editor is empty
1:address:array:character <- new []
# sandbox editor contains an instruction without storing outputs
2:address:array:character <- new [divide-with-remainder 11, 3]
3:address:programming-environment-data <- new-programming-environment screen:address, 1:address:array:character, 2:address:array:character
# run the code in the editors
assume-console [
press F4
]
run [
event-loop screen:address, console:address, 3:address:programming-environment-data
]
# check that screen prints the results
screen-should-contain [
. run (F4) .
. ┊ .
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊ x.
. ┊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, [
. .
. ┊ .
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊ x.
. ┊ .
. ┊3 .
. ┊2 .
. ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊ .
]
# run another command
assume-console [
left-click 1, 80
type [add 2, 2]
press F4
]
run [
event-loop screen:address, console:address, 3:address:programming-environment-data
]
# check that screen prints the results
screen-should-contain [
. run (F4) .
. ┊ .
.┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊ x.
. ┊add 2, 2 .
. ┊4 .
. ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊ x.
. ┊divide-with-remainder 11, 3 .
. ┊3 .
. ┊2 .
. ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
. ┊ .
]
]
# hook into event-loop recipe: read non-unicode keypress from k, process it if
# necessary, then go to next level
after <global-keypress> [
# F4? load all code and run all sandboxes.
{
do-run?:boolean <- equal *k, 65532/F4
break-unless do-run?
status:address:array:character <- new [running... ]
screen <- update-status screen, status, 245/grey
error?:boolean, env, screen <- run-sandboxes env, screen
# F4 might update warnings and results on both sides
screen <- render-all screen, env
{
break-if error?
status:address:array:character <- new [ ]
screen <- update-status screen, status, 245/grey
}
screen <- update-cursor screen, recipes, current-sandbox, *sandbox-in-focus?
loop +next-event:label
}
]
recipe run-sandboxes [
local-scope
env:address:programming-environment-data <- next-ingredient
screen:address <- next-ingredient
stop?:boolean, env, screen <- update-recipes env, screen
reply-if stop?, 1/errors-found, env/same-as-ingredient:0, screen/same-as-ingredient:1
# check contents of right editor (sandbox)
current-sandbox:address:editor-data <- get *env, current-sandbox:offset
{
sandbox-contents:address:array:character <- editor-contents current-sandbox
break-unless sandbox-contents
# if contents exist, first save them
# run them and turn them into a new sandbox-data
new-sandbox:address:sandbox-data <- new sandbox-data:type
data:address:address:array:character <- get-address *new-sandbox, data:offset
*data <- copy sandbox-contents
# push to head of sandbox list
dest:address:address:sandbox-data <- get-address *env, sandbox:offset
next:address:address:sandbox-data <- get-address *new-sandbox, next-sandbox:offset
*next <- copy *dest
*dest <- copy new-sandbox
# clear sandbox editor
init:address:address:duplex-list <- get-address *current-sandbox, data:offset
*init <- push-duplex 167/§, 0/tail
top-of-screen:address:address:duplex-list <- get-address *current-sandbox, top-of-screen:offset
*top-of-screen <- copy *init
}
# save all sandboxes before running, just in case we die when running
save-sandboxes env
# run all sandboxes
curr:address:sandbox-data <- get *env, sandbox:offset
{
break-unless curr
update-sandbox curr
curr <- get *curr, next-sandbox:offset
loop
}
reply 0/no-errors-found, env/same-as-ingredient:0, screen/same-as-ingredient:1
]
# copy code from recipe editor, persist, load into mu
# replaced in a later layer
recipe update-recipes [
local-scope
env:address:programming-environment-data <- next-ingredient
screen:address <- next-ingredient
recipes:address:editor-data <- get *env, recipes:offset
in:address:array:character <- editor-contents recipes
save [recipes.mu], in
reload in
reply 0/no-errors-found, env/same-as-ingredient:0, screen/same-as-ingredient:1
]
# replaced in a later layer
recipe update-sandbox [
local-scope
sandbox:address:sandbox-data <- next-ingredient
data:address:array:character <- get *sandbox, data:offset
response:address:address:array:character <- get-address *sandbox, response:offset
fake-screen:address:address:screen <- get-address *sandbox, screen:offset
*response, _, *fake-screen <- run-interactive data
]
recipe update-status [
local-scope
screen:address <- next-ingredient
msg:address:array:character <- next-ingredient
color:number <- next-ingredient
screen <- move-cursor screen, 0, 2
screen <- print-string screen, msg, color, 238/grey/background
reply screen/same-as-ingredient:0
]
recipe save-sandboxes [
local-scope
env:address:programming-environment-data <- next-ingredient
current-sandbox:address:editor-data <- 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:address:sandbox-data <- get *env, sandbox:offset
suffix:address:array:character <- new [.out]
idx:number <- copy 0
{
break-unless curr
data:address:array:character <- get *curr, data:offset
filename:address:array:character <- integer-to-decimal-string idx
save filename,