update source editor
Dependencies
- [2]
TUN6TDO4give a test a unique name - [3]
KVJTEXMSfix all tests in LÖVE v12 - [4]
GJ4LBCIEstreamline button.lua - [5]
AQMZJXURuse editor state font for width calculations - [6]
J7A5ROM3bugfix in cursor positioning - [7]
N33WSVVOwhitespace - [8]
4FTOQOPZbugfix #2 in search UI - [9]
26V2B2QHclarify a misleading test - [10]
FQZ3U3YAstreamline one more test name - [11]
CFJ4FLCQclean up an unnecessary mutation - [12]
GZ5WULJVswitch source side to new screen-line-based render - [13]
JYB3RFWHbugfix in source editor - [14]
5XA7TKWYpull font into editor - [15]
H4R5BHVYno more Text allocations - [16]
TYFAGQWSrepeat bugfix on source editor - [17]
AIHGJ4BTbugfix: infinite loop inside a very narrow window - [18]
ORRSP7FVdeduce test names on failures - [19]
KOTNETIMrepeat changes on source editor - [20]
SWZAQHGRbugfix: up arrow when line above is a drawing - [21]
O7YTBRQYbugfix: restart search on backspace - [22]
5SM6DRHKport inscript's bugfix to source editor - [23]
LK4ZW4BBbugfix - [24]
FHNPQBLKmore carefully pass the 'key' arg around - [25]
656FM555bugfix: clear selection when clicking above or below lines - [26]
2CK5QI7Wmake love event names consistent - [27]
ZS5IYZH5stop caching screen_bottom1 - [28]
A42EMHOQplumb through all supported args in LÖVE handlers - [29]
2H76FV5Sbugfix: searching files containing unicode - [30]
KMSL74GAsupport selections in the source editor - [31]
2TCIWW6Zstop caching starty - [32]
DFGPHG5Toverzealous search-and-replace - [33]
KKMFQDR4editing source code from within the app - [34]
S2QMLRXLstop creating a singleton table for every word - [35]
OI4FPFINsupport drawings in the source editor - [*]
R5QXEHUIsomebody stop me - [*]
UBA2ZUCPremove a duplicate print to screen - [*]
LDFXFRUObring a few things in sync between run and source
Change contents
- file addition: source_text_tests_love12.lua[37.2]
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')endfunction test_click_to_create_drawing()App.screen.init{width=800, height=600}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 textcheck_eq(#Editor_state.lines, 2, '#lines')check_eq(Editor_state.cursor1.line, 2, 'cursor')endfunction 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 drawingedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(#Editor_state.lines, 1, '#lines')check_eq(Editor_state.cursor1.line, 1, 'cursor')endfunction test_backspace_from_start_of_final_line()-- display final line of text with cursor at start of itApp.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 upedit.run_after_keychord(Editor_state, 'backspace', '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')endfunction 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.topApp.screen.check(y, 'a', 'screen:1')endfunction test_press_ctrl()-- press ctrl while the cursor is on textApp.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}edit.run_after_keychord(Editor_state, 'C-m', 'm')endfunction 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', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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', 'right')check_eq(Editor_state.cursor1.pos, 2, 'check')endfunction 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', 'left')check_eq(Editor_state.cursor1.line, 1, 'line')check_eq(Editor_state.cursor1.pos, 4, 'pos') -- past end of lineendfunction 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 lineedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'right', 'right')check_eq(Editor_state.cursor1.line, 2, 'line')check_eq(Editor_state.cursor1.pos, 1, 'pos')endfunction 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', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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 wordsedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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 wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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 wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left', 'left')check_eq(Editor_state.cursor1.pos, 9, 'check')endfunction 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 wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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', 'left')check_eq(Editor_state.cursor1.line, 1, 'line')check_eq(Editor_state.cursor1.pos, 5, 'pos')endfunction 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', 'right')check_eq(Editor_state.cursor1.pos, 4, 'check')endfunction 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 wordsedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right', 'right')check_eq(Editor_state.cursor1.pos, 8, 'check')endfunction 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 wordsedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right', 'right')check_eq(Editor_state.cursor1.pos, 4, 'check')endfunction 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 wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right', 'right')check_eq(Editor_state.cursor1.pos, 9, 'check')endfunction 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', 'right')check_eq(Editor_state.cursor1.line, 2, 'line')check_eq(Editor_state.cursor1.pos, 4, 'pos')endfunction 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.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each lineedit.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 editscheck_nil(Editor_state.selection1.line, 'selection:line')check_nil(Editor_state.selection1.pos, 'selection:pos')endfunction test_click_to_left_of_line()-- display a line with the cursor in the middleApp.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.selection1 = {}-- click to the left of the lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1)-- cursor moves to start of linecheck_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')endfunction test_click_takes_margins_into_account()-- display two lines with cursor on one of themApp.screen.init{width=100, height=80}Editor_state = edit.initialize_test_state()Editor_state.left = 50 -- occupy only right side of screenEditor_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.selection1 = {}-- click on the other lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_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')endfunction test_click_on_empty_line()-- display two lines with the first one emptyApp.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.selection1 = {}-- click on the empty lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_eq(Editor_state.cursor1.line, 1, 'cursor')-- selection remains emptycheck_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')endfunction test_click_below_final_line_of_file()-- display one lineApp.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.selection1 = {}-- click below first lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+50, 1)-- cursor goes to bottomcheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')-- selection remains emptycheck_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')endfunction 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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction 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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'gh', 'screen:3')endfunction test_draw_word_wrapping_text()App.screen.init{width=54, 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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def ', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_click_on_wrapping_line()-- display two screen lines with cursor on one of themApp.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}-- click on the other lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_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')endfunction test_click_on_wrapping_line_takes_margins_into_account()-- display two screen lines with cursor on one of themApp.screen.init{width=100, height=80}Editor_state = edit.initialize_test_state()Editor_state.left = 50 -- occupy only right side of screenEditor_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}-- click on the other lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_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')endfunction test_draw_text_wrapping_within_word()-- arrange a screen line that needs to be split within a wordApp.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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abcd ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'e fghi', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jk', 'screen:3')endfunction test_draw_wrapping_text_containing_non_ascii()-- draw a long line containing non-ASCIIApp.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 apostropheText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'mada', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'm ', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'I’m a', 'screen:3')endfunction test_click_past_end_of_screen_line()-- display a wrapping lineApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'madam ', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, "I'm ada", 'baseline/screen:2')y = y + Editor_state.line_height-- click past end of second screen lineedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of screen line (one more than final character shown)check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 14, 'cursor:pos')endfunction test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()-- display a wrapping line from its second screen lineApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, "I'm ada", 'baseline/screen:2')y = y + Editor_state.line_height-- click past end of second screen lineedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of screen line (one more than final character shown)check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 14, 'cursor:pos')endfunction test_click_past_end_of_wrapping_line()-- display a wrapping lineApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'madam ', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, "I'm ada", 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'm', 'baseline/screen:3')y = y + Editor_state.line_height-- click past the end of itedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of linecheck_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-pointsendfunction test_click_past_end_of_wrapping_line_containing_non_ascii()-- display a wrapping line containing non-ASCIIApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_state.lines = load_array{'madam I’m adam'} -- notice the non-ASCII apostropheText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'madam ', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'I’m ada', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'm', 'baseline/screen:3')y = y + Editor_state.line_height-- click past the end of itedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of linecheck_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-pointsendfunction test_click_past_end_of_word_wrapping_line()-- display a long line wrapping at a word boundary on a screen of more realistic lengthApp.screen.init{width=160, height=80}Editor_state = edit.initialize_test_state()-- 0 1 2-- 123456789012345678901Editor_state.lines = load_array{'the quick brown fox jumped over the lazy dog'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')y = y + Editor_state.line_height-- click past the end of the screen lineedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of screen line (one more than final character shown)check_eq(Editor_state.cursor1.pos, 21, 'cursor')endfunction test_select_text()-- display a line of textApp.screen.init{width=75, height=80}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}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- select a letterApp.fake_key_press('lshift')edit.run_after_keychord(Editor_state, 'S-right', 'right')App.fake_key_release('lshift')edit.key_release(Editor_state, 'lshift')-- selection persists even after shift is releasedcheck_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 1, 'selection:pos')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')endfunction test_cursor_movement_without_shift_resets_selection()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- press an arrow key without shiftedit.run_after_keychord(Editor_state, 'right', 'right')-- no change to data, selection is resetcheck_nil(Editor_state.selection1.line, 'check')check_eq(Editor_state.lines[1].data, 'abc', 'data')endfunction test_edit_deletes_selection()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- press a keyedit.run_after_text_input(Editor_state, 'x')-- selected text is deleted and replaced with the keycheck_eq(Editor_state.lines[1].data, 'xbc', 'check')endfunction test_edit_with_shift_key_deletes_selection()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- mimic precise keypresses for a capital letterApp.fake_key_press('lshift')edit.keychord_press(Editor_state, 'd', 'd')edit.text_input(Editor_state, 'D')edit.key_release(Editor_state, 'd')App.fake_key_release('lshift')-- selected text is deleted and replaced with the keycheck_nil(Editor_state.selection1.line, 'check')check_eq(Editor_state.lines[1].data, 'Dbc', 'data')endfunction test_copy_does_not_reset_selection()-- display a line of text with a selectionApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- copy selectionedit.run_after_keychord(Editor_state, 'C-c', 'c')check_eq(App.clipboard, 'a', 'clipboard')-- selection is reset since shift key is not pressedcheck(Editor_state.selection1.line, 'check')endfunction test_cut()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- press a keyedit.run_after_keychord(Editor_state, 'C-x', 'x')check_eq(App.clipboard, 'a', 'clipboard')-- selected text is deletedcheck_eq(Editor_state.lines[1].data, 'bc', 'data')endfunction test_paste_replaces_selection()-- display a line of text with a selectionApp.screen.init{width=75, height=80}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}Editor_state.selection1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- set clipboardApp.clipboard = 'xyz'-- paste selectionedit.run_after_keychord(Editor_state, 'C-v', 'v')-- selection is reset since shift key is not pressed-- selection includes the newline, so it's also deletedcheck_eq(Editor_state.lines[1].data, 'xyzdef', 'check')endfunction test_deleting_selection_may_scroll()-- display lines 2/3/4App.screen.init{width=120, 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=3, pos=2}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- set up a selection starting above the currently displayed pageEditor_state.selection1 = {line=1, pos=2}-- delete selectionedit.run_after_keychord(Editor_state, 'backspace', 'backspace')-- page scrolls upcheck_eq(Editor_state.screen_top1.line, 1, 'check')check_eq(Editor_state.lines[1].data, 'ahi', 'data')endfunction test_edit_wrapping_text()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=2, pos=4}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)edit.run_after_text_input(Editor_state, 'g')local y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'g', 'screen:3')endfunction test_insert_newline()-- display a few linesApp.screen.init{width=Editor_state.left+30, 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=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- hitting the enter key splits the lineedit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'a', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'bc', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:3')endfunction test_insert_newline_at_start_of_line()-- display a lineApp.screen.init{width=Editor_state.left+30, 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=1}Editor_state.screen_top1 = {line=1, pos=1}-- hitting the enter key splits the lineedit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')check_eq(Editor_state.lines[1].data, '', 'data:1')check_eq(Editor_state.lines[2].data, 'abc', 'data:2')endfunction test_insert_from_clipboard()-- display a few linesApp.screen.init{width=Editor_state.left+30, 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=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- paste some text including a newline, check that new line is createdApp.clipboard = 'xy\nz'edit.run_after_keychord(Editor_state, 'C-v', 'v')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'axy', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'zbc', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:3')endfunction test_select_text_using_mouse()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.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each line-- press and hold on first locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- drag and release somewhere elseedit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 2, 'selection:pos')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')endfunction test_select_text_using_mouse_starting_above_text()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.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each line-- press mouse above first line of textedit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 1, 'selection:pos')endfunction test_select_text_using_mouse_starting_above_text_wrapping_line()-- first screen line starts in the middle of a lineApp.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=2, pos=5}Editor_state.screen_top1 = {line=2, pos=3}-- press mouse above first line of textedit.draw(Editor_state)edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)-- selection is at screen topcheck(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')check_eq(Editor_state.selection1.line, 2, 'selection:line')check_eq(Editor_state.selection1.pos, 3, 'selection:pos')endfunction test_select_text_using_mouse_starting_below_text()-- I'd like to test what happens when a mouse click is below some page of-- text, potentially even in the middle of a line.-- However, it's brittle to set up a text line boundary just right.-- So I'm going to just check things below the bottom of the final line of-- text when it's in the middle of the screen.-- final screen line ends in the middle of screenApp.screen.init{width=50, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abcde'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline:screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'de', 'baseline:screen:2')-- press mouse above first line of textedit.run_after_mouse_press(Editor_state, 5,App.screen.height-5, 1)-- selection is past bottom-most text in screencheck(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 6, 'selection:pos')endfunction test_select_text_using_mouse_and_shift()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.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each line-- click on first locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- hold down shift and click somewhere elseApp.fake_key_press('lshift')edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)App.fake_key_release('lshift')check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 2, 'selection:pos')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')endfunction test_select_text_repeatedly_using_mouse_and_shift()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)Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each line-- click on first locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- hold down shift and click on a second locationApp.fake_key_press('lshift')edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)-- hold down shift and click at a third locationApp.fake_key_press('lshift')edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height+5, 1)App.fake_key_release('lshift')-- selection is between first and third location. forget the second location, not the first.check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 2, 'selection:pos')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')endfunction test_select_all_text()-- display a single line of textApp.screen.init{width=75, height=80}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}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- select allApp.fake_key_press('lctrl')edit.run_after_keychord(Editor_state, 'C-a', 'a')App.fake_key_release('lctrl')edit.key_release(Editor_state, 'lctrl')-- selectioncheck_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 1, 'selection:pos')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 8, 'cursor:pos')endfunction test_cut_without_selection()-- display a few linesApp.screen.init{width=Editor_state.left+30, 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=2}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.selection1 = {}edit.draw(Editor_state)-- try to cut without selecting textedit.run_after_keychord(Editor_state, 'C-x', 'x')-- no crashcheck_nil(Editor_state.selection1.line, 'check')endfunction test_pagedown()App.screen.init{width=120, height=45}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}-- initially the first two lines are displayededit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')-- after pagedown the bottom line becomes the topedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:2')endfunction test_pagedown_skips_drawings()-- some lines of text with a drawing intermixedlocal drawing_width = 50App.screen.init{width=Editor_state.left+drawing_width, height=80}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', -- height 15'```lines', '```', -- height 25'def', -- height 15'ghi'} -- height 15Text.redraw_all(Editor_state)check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines')Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}local drawing_height = Drawing_padding_height + drawing_width/2 -- default-- initially the screen displays the first line and the drawing-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80pxedit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')-- after pagedown the screen draws the drawing up top-- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80pxedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor')y = Editor_state.top + drawing_heightApp.screen.check(y, 'def', 'screen:1')endfunction test_pagedown_can_start_from_middle_of_long_wrapping_line()-- draw a few lines starting from a very long wrapping lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu vwx yza bcd efg hij', 'XYZ'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc ', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def g', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'hi jkl ', 'baseline/screen:3')-- after pagedown we scroll down the very long wrapping lineedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 10, 'screen_top:pos')y = Editor_state.topApp.screen.check(y, 'hi jkl ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno ', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'pqr ', 'screen:3')endfunction test_pagedown_never_moves_up()-- draw the final screen line of a wrapping lineApp.screen.init{width=Editor_state.left+30, 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=10}Editor_state.screen_top1 = {line=1, pos=10}edit.draw(Editor_state)-- pagedown makes no changeedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 10, 'screen_top:pos')endfunction test_down_arrow_moves_cursor()App.screen.init{width=120, 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}-- initially the first three lines are displayededit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the down arrow, the cursor moves down by 1 lineedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor')-- the screen is unchangedy = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_down_arrow_skips_drawing()-- some lines of text with a drawing intermixedlocal drawing_width = 50App.screen.init{width=Editor_state.left+drawing_width, height=100}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', -- height 15'```lines', '```', -- height 25'ghi'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightlocal drawing_height = Drawing_padding_height + drawing_width/2 -- defaulty = y + drawing_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')check(Editor_state.cursor_x, 'baseline/cursor_x')-- after hitting the down arrow the cursor moves down by 2 lines, skipping the drawingedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.cursor1.line, 3, 'cursor')endfunction test_down_arrow_scrolls_down_by_one_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=120, 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=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the down arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 4, 'cursor')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')endfunction test_down_arrow_scrolls_down_by_one_screen_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghij kl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghij ', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the down arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghij ', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:3')endfunction test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghijkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghijk', 'baseline/screen:3')-- after hitting the down arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghijk', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'l', 'screen:3')endfunction test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()App.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghijkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghijk', 'baseline/screen:3')-- after hitting pagedown the screen scrolls down to start of a long lineedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 3, 'baseline2/screen_top')check_eq(Editor_state.cursor1.line, 3, 'baseline2/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'baseline2/cursor:pos')-- after hitting down arrow the screen doesn't scroll down further, and certainly doesn't scroll upedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 3, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'ghijk', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'l', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')endfunction test_up_arrow_moves_cursor()-- display the first 3 lines with the cursor on the bottom lineApp.screen.init{width=120, 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=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the up arrow the cursor moves up by 1 lineedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor')-- the screen is unchangedy = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_up_arrow_skips_drawing()-- some lines of text with a drawing intermixedlocal drawing_width = 50App.screen.init{width=Editor_state.left+drawing_width, height=100}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', -- height 15'```lines', '```', -- height 25'ghi'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightlocal drawing_height = Drawing_padding_height + drawing_width/2 -- defaulty = y + drawing_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')check(Editor_state.cursor_x, 'baseline/cursor_x')-- after hitting the up arrow the cursor moves up by 2 lines, skipping the drawingedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.cursor1.line, 1, 'cursor')endfunction test_up_arrow_scrolls_up_by_one_line()-- display the lines 2/3/4 with the cursor on line 2App.screen.init{width=120, 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=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up by one lineedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_up_arrow_scrolls_up_by_one_line_skipping_drawing()-- display lines 3/4/5 with a drawing just off screen at line 2App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', '```lines', '```', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=3, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up to previous text lineedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')endfunction test_up_arrow_scrolls_up_by_one_screen_line()-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=6}Editor_state.screen_top1 = {line=3, pos=6}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'kl', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:2')-- after hitting the up arrow the screen scrolls up to first screen lineedit.run_after_keychord(Editor_state, 'up', 'up')y = Editor_state.topApp.screen.check(y, 'ghi j', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')endfunction test_up_arrow_scrolls_up_to_final_screen_line()-- display lines starting just after a long lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'ghi', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up to final screen line of previous lineedit.run_after_keychord(Editor_state, 'up', 'up')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 5, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')endfunction test_up_arrow_scrolls_up_to_empty_line()-- display a screenful of text with an empty line just above it outside the screenApp.screen.init{width=120, 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=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up by one lineedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')y = Editor_state.top-- empty first liney = y + Editor_state.line_heightApp.screen.check(y, 'abc', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:3')endfunction test_pageup()App.screen.init{width=120, height=45}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}Editor_state.screen_top1 = {line=2, pos=1}-- initially the last two lines are displayededit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')-- after pageup the cursor goes to first lineedit.run_after_keychord(Editor_state, 'pageup', 'pageup')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')endfunction test_pageup_scrolls_up_by_screen_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'ghi', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the page-up key the screen scrolls up to topedit.run_after_keychord(Editor_state, 'pageup', 'pageup')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'abc ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_pageup_scrolls_up_from_middle_screen_line()-- display a few lines starting from the middle of a line (Editor_state.cursor1.pos > 1)App.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=6}Editor_state.screen_top1 = {line=2, pos=6}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'kl', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the page-up key the screen scrolls up to topedit.run_after_keychord(Editor_state, 'pageup', 'pageup')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'abc ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi j', 'screen:3')endfunction test_enter_on_bottom_line_scrolls_down()-- display a few lines with cursor on bottom lineApp.screen.init{width=Editor_state.left+30, 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=3, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the enter key the screen scrolls downedit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 4, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'g', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'hi', 'screen:3')endfunction test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()-- display just the bottom line on screenApp.screen.init{width=Editor_state.left+30, 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=4, pos=2}Editor_state.screen_top1 = {line=4, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'baseline/screen:1')-- after hitting the enter key the screen does not scroll downedit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.screen_top1.line, 4, 'screen_top')check_eq(Editor_state.cursor1.line, 5, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'j', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:2')endfunction test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom()-- display just an empty bottom line on screenApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', ''}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)-- after hitting the inserting_text key the screen does not scroll downedit.run_after_text_input(Editor_state, 'a')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')local y = Editor_state.topApp.screen.check(y, 'a', 'screen:1')endfunction test_typing_on_bottom_line_scrolls_down()-- display a few lines with cursor on bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'pqr'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=4}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after typing something the line wraps and the screen scrolls downedit.run_after_text_input(Editor_state, 'j')edit.run_after_text_input(Editor_state, 'k')edit.run_after_text_input(Editor_state, 'l')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 7, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghijk', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'l', 'screen:3')endfunction test_left_arrow_scrolls_up_in_wrapped_line()-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=3, pos=6}-- cursor is at top of screenEditor_state.cursor1 = {line=3, pos=6}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'kl', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:2')-- after hitting the left arrow the screen scrolls up to first screen lineedit.run_after_keychord(Editor_state, 'left', 'left')y = Editor_state.topApp.screen.check(y, 'ghi j', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')endfunction test_right_arrow_scrolls_down_in_wrapped_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}-- cursor is at bottom right of screenEditor_state.cursor1 = {line=3, pos=6}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi j', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the right arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'right', 'right')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 7, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi j', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:3')endfunction test_home_scrolls_up_in_wrapped_line()-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=3, pos=6}-- cursor is at top of screenEditor_state.cursor1 = {line=3, pos=6}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'kl', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:2')-- after hitting home the screen scrolls up to first screen lineedit.run_after_keychord(Editor_state, 'home', 'home')y = Editor_state.topApp.screen.check(y, 'ghi j', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')endfunction test_end_scrolls_down_in_wrapped_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}-- cursor is at bottom right of screenEditor_state.cursor1 = {line=3, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi j', 'baseline/screen:3')-- after hitting end the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'end', 'end')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 8, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi j', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:3')endfunction test_position_cursor_on_recently_edited_wrapping_line()-- draw a line wrapping over 2 screen linesApp.screen.init{width=100, height=200}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def ghi jkl mno pqr ', 'xyz'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=25}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl mno pqr ', 'baseline1/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'baseline1/screen:3')-- add to the line until it's wrapping over 3 screen linesedit.run_after_text_input(Editor_state, 's')edit.run_after_text_input(Editor_state, 't')edit.run_after_text_input(Editor_state, 'u')check_eq(Editor_state.cursor1.pos, 28, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'abc def ghi ', 'baseline2/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl mno pqr ', 'baseline2/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'stu', 'baseline2/screen:3')-- try to move the cursor earlier in the third screen line by clicking the mouseedit.run_after_mouse_release(Editor_state, Editor_state.left+2,Editor_state.top+Editor_state.line_height*2+5, 1)-- cursor should movecheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 25, 'cursor:pos')endfunction test_backspace_can_scroll_up()-- display the lines 2/3/4 with the cursor on line 2App.screen.init{width=120, 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=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- after hitting backspace the screen scrolls up by one lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')y = Editor_state.topApp.screen.check(y, 'abcdef', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')endfunction test_backspace_can_scroll_up_screen_line()-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=6}Editor_state.screen_top1 = {line=3, pos=6}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'kl', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:2')-- after hitting backspace the screen scrolls up by one screen lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')y = Editor_state.topApp.screen.check(y, 'ghi k', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'l', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')endfunction test_backspace_past_line_boundary()-- position cursor at start of a (non-first) lineApp.screen.init{width=Editor_state.left+30, 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}-- backspace joins with previous lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'abcdef', 'check')end-- some tests for operating over selections created using Shift- chords-- we're just testing delete_selection, and it works the same for all keysfunction test_backspace_over_selection()-- select just one character within a line with cursor before selectionApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.selection1 = {line=1, pos=2}-- backspace deletes the selected character, even though it's after the cursoredit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'bc', 'data')-- cursor (remains) at start of selectioncheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_backspace_over_selection_reverse()-- select just one character within a line with cursor after selectionApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.selection1 = {line=1, pos=1}-- backspace deletes the selected characteredit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'bc', 'data')-- cursor moves to start of selectioncheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_backspace_over_multiple_lines()-- select just one character within a line with cursor after selectionApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.selection1 = {line=4, pos=2}-- backspace deletes the region and joins the remaining portions of lines on either sideedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'akl', 'data:1')check_eq(Editor_state.lines[2].data, 'mno', 'data:2')-- cursor remains at start of selectioncheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_backspace_to_end_of_line()-- select region from cursor to end of lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.selection1 = {line=1, pos=4}-- backspace deletes rest of line without joining to any other lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'a', 'data:1')check_eq(Editor_state.lines[2].data, 'def', 'data:2')-- cursor remains at start of selectioncheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_backspace_to_start_of_line()-- select region from cursor to start of lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.selection1 = {line=2, pos=3}-- backspace deletes beginning of line without joining to any other lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'abc', 'data:1')check_eq(Editor_state.lines[2].data, 'f', 'data:2')-- cursor remains at start of selectioncheck_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_undo_insert_text()App.screen.init{width=120, 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=2, pos=4}Editor_state.screen_top1 = {line=1, pos=1}-- insert a characteredit.draw(Editor_state)edit.run_after_text_input(Editor_state, 'g')check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'baseline/cursor:pos')check_nil(Editor_state.selection1.line, 'baseline/selection:line')check_nil(Editor_state.selection1.pos, 'baseline/selection:pos')local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'defg', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'baseline/screen:3')-- undoedit.run_after_keychord(Editor_state, 'C-z', 'z')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')check_nil(Editor_state.selection1.line, 'selection:line')check_nil(Editor_state.selection1.pos, 'selection:pos')y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'screen:3')endfunction test_undo_delete_text()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'defg', 'xyz'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=5}Editor_state.screen_top1 = {line=1, pos=1}-- delete a characteredit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'baseline/cursor:pos')check_nil(Editor_state.selection1.line, 'baseline/selection:line')check_nil(Editor_state.selection1.pos, 'baseline/selection:pos')local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'baseline/screen:3')-- undo--? -- after undo, the backspaced key is selectededit.run_after_keychord(Editor_state, 'C-z', 'z')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')check_nil(Editor_state.selection1.line, 'selection:line')check_nil(Editor_state.selection1.pos, 'selection:pos')--? check_eq(Editor_state.selection1.line, 2, 'selection:line')--? check_eq(Editor_state.selection1.pos, 4, 'selection:pos')y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'defg', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'screen:3')endfunction test_undo_restores_selection()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- delete selected textedit.run_after_text_input(Editor_state, 'x')check_eq(Editor_state.lines[1].data, 'xbc', 'baseline')check_nil(Editor_state.selection1.line, 'baseline:selection')-- undoedit.run_after_keychord(Editor_state, 'C-z', 'z')edit.run_after_keychord(Editor_state, 'C-z', 'z')-- selection is restoredcheck_eq(Editor_state.selection1.line, 1, 'line')check_eq(Editor_state.selection1.pos, 2, 'pos')endfunction test_search()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'```lines', '```', 'def', 'ghi', '’deg'} -- contains unicode quote in final lineText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for a stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'd')edit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.cursor1.line, 2, '1/cursor:line')check_eq(Editor_state.cursor1.pos, 1, '1/cursor:pos')-- reset cursorEditor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}-- search for second occurrenceedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'de')edit.run_after_keychord(Editor_state, 'down', 'down')edit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.cursor1.line, 4, '2/cursor:line')check_eq(Editor_state.cursor1.pos, 2, '2/cursor:pos')endfunction test_search_upwards()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'’abc', 'abd'} -- contains unicode quoteText.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for a stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'a')-- search for previous occurrenceedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.cursor1.line, 1, '2/cursor:line')check_eq(Editor_state.cursor1.pos, 2, '2/cursor:pos')endfunction test_search_wrap()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'’abc', 'def'} -- contains unicode quote in first lineText.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for a stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'a')edit.run_after_keychord(Editor_state, 'return', 'return')-- cursor wrapscheck_eq(Editor_state.cursor1.line, 1, '1/cursor:line')check_eq(Editor_state.cursor1.pos, 2, '1/cursor:pos')endfunction test_search_wrap_upwards()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc ’abd'} -- contains unicode quoteText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search upwards for a stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'a')edit.run_after_keychord(Editor_state, 'up', 'up')-- cursor wrapscheck_eq(Editor_state.cursor1.line, 1, '1/cursor:line')check_eq(Editor_state.cursor1.pos, 6, '1/cursor:pos')endfunction test_search_downwards_from_end_of_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=4}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for empty stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_keychord(Editor_state, 'down', 'down')-- no crashendfunction test_search_downwards_from_final_pos_of_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=3}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for empty stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_keychord(Editor_state, 'down', 'down')-- no crashend - file addition: source_text_tests_love11.lua[37.2]
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')endfunction test_click_to_create_drawing()App.screen.init{width=800, height=600}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 textcheck_eq(#Editor_state.lines, 2, '#lines')check_eq(Editor_state.cursor1.line, 2, 'cursor')endfunction 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 drawingedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(#Editor_state.lines, 1, '#lines')check_eq(Editor_state.cursor1.line, 1, 'cursor')endfunction test_backspace_from_start_of_final_line()-- display final line of text with cursor at start of itApp.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 upedit.run_after_keychord(Editor_state, 'backspace', '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')endfunction 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.topApp.screen.check(y, 'a', 'screen:1')endfunction test_press_ctrl()-- press ctrl while the cursor is on textApp.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}edit.run_after_keychord(Editor_state, 'C-m', 'm')endfunction 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', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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', 'right')check_eq(Editor_state.cursor1.pos, 2, 'check')endfunction 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', 'left')check_eq(Editor_state.cursor1.line, 1, 'line')check_eq(Editor_state.cursor1.pos, 4, 'pos') -- past end of lineendfunction 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 lineedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'right', 'right')check_eq(Editor_state.cursor1.line, 2, 'line')check_eq(Editor_state.cursor1.pos, 1, 'pos')endfunction 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', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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 wordsedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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 wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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 wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left', 'left')check_eq(Editor_state.cursor1.pos, 9, 'check')endfunction 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 wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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', 'left')check_eq(Editor_state.cursor1.line, 1, 'line')check_eq(Editor_state.cursor1.pos, 5, 'pos')endfunction 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', 'right')check_eq(Editor_state.cursor1.pos, 4, 'check')endfunction 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 wordsedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right', 'right')check_eq(Editor_state.cursor1.pos, 8, 'check')endfunction 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 wordsedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right', 'right')check_eq(Editor_state.cursor1.pos, 4, 'check')endfunction 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 wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right', 'right')check_eq(Editor_state.cursor1.pos, 9, 'check')endfunction 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', 'right')check_eq(Editor_state.cursor1.line, 2, 'line')check_eq(Editor_state.cursor1.pos, 4, 'pos')endfunction 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.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each lineedit.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 editscheck_nil(Editor_state.selection1.line, 'selection:line')check_nil(Editor_state.selection1.pos, 'selection:pos')endfunction test_click_to_left_of_line()-- display a line with the cursor in the middleApp.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.selection1 = {}-- click to the left of the lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1)-- cursor moves to start of linecheck_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')endfunction test_click_takes_margins_into_account()-- display two lines with cursor on one of themApp.screen.init{width=100, height=80}Editor_state = edit.initialize_test_state()Editor_state.left = 50 -- occupy only right side of screenEditor_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.selection1 = {}-- click on the other lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_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')endfunction test_click_on_empty_line()-- display two lines with the first one emptyApp.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.selection1 = {}-- click on the empty lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_eq(Editor_state.cursor1.line, 1, 'cursor')-- selection remains emptycheck_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')endfunction test_click_below_final_line_of_file()-- display one lineApp.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.selection1 = {}-- click below first lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+50, 1)-- cursor goes to bottomcheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')-- selection remains emptycheck_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')endfunction 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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction 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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'gh', 'screen:3')endfunction test_draw_word_wrapping_text()App.screen.init{width=54, 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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def ', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_click_on_wrapping_line()-- display two screen lines with cursor on one of themApp.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}-- click on the other lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_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')endfunction test_click_on_wrapping_line_takes_margins_into_account()-- display two screen lines with cursor on one of themApp.screen.init{width=100, height=80}Editor_state = edit.initialize_test_state()Editor_state.left = 50 -- occupy only right side of screenEditor_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}-- click on the other lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_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')endfunction test_draw_text_wrapping_within_word()-- arrange a screen line that needs to be split within a wordApp.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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abcd ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'e fghi', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jk', 'screen:3')endfunction test_draw_wrapping_text_containing_non_ascii()-- draw a long line containing non-ASCIIApp.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 apostropheText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'mada', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'm I’', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'm ad', 'screen:3')endfunction test_click_past_end_of_screen_line()-- display a wrapping lineApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'madam ', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, "I'm ada", 'baseline/screen:2')y = y + Editor_state.line_height-- click past end of second screen lineedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of screen line (one more than final character shown)check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 14, 'cursor:pos')endfunction test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()-- display a wrapping line from its second screen lineApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, "I'm ada", 'baseline/screen:2')y = y + Editor_state.line_height-- click past end of second screen lineedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of screen line (one more than final character shown)check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 14, 'cursor:pos')endfunction test_click_past_end_of_wrapping_line()-- display a wrapping lineApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'madam ', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, "I'm ada", 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'm', 'baseline/screen:3')y = y + Editor_state.line_height-- click past the end of itedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of linecheck_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-pointsendfunction test_click_past_end_of_wrapping_line_containing_non_ascii()-- display a wrapping line containing non-ASCIIApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_state.lines = load_array{'madam I’m adam'} -- notice the non-ASCII apostropheText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'madam ', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'I’m ada', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'm', 'baseline/screen:3')y = y + Editor_state.line_height-- click past the end of itedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of linecheck_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-pointsendfunction test_click_past_end_of_word_wrapping_line()-- display a long line wrapping at a word boundary on a screen of more realistic lengthApp.screen.init{width=160, height=80}Editor_state = edit.initialize_test_state()-- 0 1 2-- 123456789012345678901Editor_state.lines = load_array{'the quick brown fox jumped over the lazy dog'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')y = y + Editor_state.line_height-- click past the end of the screen lineedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of screen line (one more than final character shown)check_eq(Editor_state.cursor1.pos, 21, 'cursor')endfunction test_select_text()-- display a line of textApp.screen.init{width=75, height=80}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}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- select a letterApp.fake_key_press('lshift')edit.run_after_keychord(Editor_state, 'S-right', 'right')App.fake_key_release('lshift')edit.key_release(Editor_state, 'lshift')-- selection persists even after shift is releasedcheck_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 1, 'selection:pos')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')endfunction test_cursor_movement_without_shift_resets_selection()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- press an arrow key without shiftedit.run_after_keychord(Editor_state, 'right', 'right')-- no change to data, selection is resetcheck_nil(Editor_state.selection1.line, 'check')check_eq(Editor_state.lines[1].data, 'abc', 'data')endfunction test_edit_deletes_selection()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- press a keyedit.run_after_text_input(Editor_state, 'x')-- selected text is deleted and replaced with the keycheck_eq(Editor_state.lines[1].data, 'xbc', 'check')endfunction test_edit_with_shift_key_deletes_selection()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- mimic precise keypresses for a capital letterApp.fake_key_press('lshift')edit.keychord_press(Editor_state, 'd', 'd')edit.text_input(Editor_state, 'D')edit.key_release(Editor_state, 'd')App.fake_key_release('lshift')-- selected text is deleted and replaced with the keycheck_nil(Editor_state.selection1.line, 'check')check_eq(Editor_state.lines[1].data, 'Dbc', 'data')endfunction test_copy_does_not_reset_selection()-- display a line of text with a selectionApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- copy selectionedit.run_after_keychord(Editor_state, 'C-c', 'c')check_eq(App.clipboard, 'a', 'clipboard')-- selection is reset since shift key is not pressedcheck(Editor_state.selection1.line, 'check')endfunction test_cut()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- press a keyedit.run_after_keychord(Editor_state, 'C-x', 'x')check_eq(App.clipboard, 'a', 'clipboard')-- selected text is deletedcheck_eq(Editor_state.lines[1].data, 'bc', 'data')endfunction test_paste_replaces_selection()-- display a line of text with a selectionApp.screen.init{width=75, height=80}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}Editor_state.selection1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- set clipboardApp.clipboard = 'xyz'-- paste selectionedit.run_after_keychord(Editor_state, 'C-v', 'v')-- selection is reset since shift key is not pressed-- selection includes the newline, so it's also deletedcheck_eq(Editor_state.lines[1].data, 'xyzdef', 'check')endfunction test_deleting_selection_may_scroll()-- display lines 2/3/4App.screen.init{width=120, 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=3, pos=2}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- set up a selection starting above the currently displayed pageEditor_state.selection1 = {line=1, pos=2}-- delete selectionedit.run_after_keychord(Editor_state, 'backspace', 'backspace')-- page scrolls upcheck_eq(Editor_state.screen_top1.line, 1, 'check')check_eq(Editor_state.lines[1].data, 'ahi', 'data')endfunction test_edit_wrapping_text()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=2, pos=4}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)edit.run_after_text_input(Editor_state, 'g')local y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'g', 'screen:3')endfunction test_insert_newline()-- display a few linesApp.screen.init{width=Editor_state.left+30, 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=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- hitting the enter key splits the lineedit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'a', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'bc', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:3')endfunction test_insert_newline_at_start_of_line()-- display a lineApp.screen.init{width=Editor_state.left+30, 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=1}Editor_state.screen_top1 = {line=1, pos=1}-- hitting the enter key splits the lineedit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')check_eq(Editor_state.lines[1].data, '', 'data:1')check_eq(Editor_state.lines[2].data, 'abc', 'data:2')endfunction test_insert_from_clipboard()-- display a few linesApp.screen.init{width=Editor_state.left+30, 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=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- paste some text including a newline, check that new line is createdApp.clipboard = 'xy\nz'edit.run_after_keychord(Editor_state, 'C-v', 'v')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'axy', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'zbc', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:3')endfunction test_select_text_using_mouse()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.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each line-- press and hold on first locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- drag and release somewhere elseedit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 2, 'selection:pos')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')endfunction test_select_text_using_mouse_starting_above_text()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.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each line-- press mouse above first line of textedit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 1, 'selection:pos')endfunction test_select_text_using_mouse_starting_above_text_wrapping_line()-- first screen line starts in the middle of a lineApp.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=2, pos=5}Editor_state.screen_top1 = {line=2, pos=3}-- press mouse above first line of textedit.draw(Editor_state)edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)-- selection is at screen topcheck(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')check_eq(Editor_state.selection1.line, 2, 'selection:line')check_eq(Editor_state.selection1.pos, 3, 'selection:pos')endfunction test_select_text_using_mouse_starting_below_text()-- I'd like to test what happens when a mouse click is below some page of-- text, potentially even in the middle of a line.-- However, it's brittle to set up a text line boundary just right.-- So I'm going to just check things below the bottom of the final line of-- text when it's in the middle of the screen.-- final screen line ends in the middle of screenApp.screen.init{width=50, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abcde'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline:screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'de', 'baseline:screen:2')-- press mouse above first line of textedit.run_after_mouse_press(Editor_state, 5,App.screen.height-5, 1)-- selection is past bottom-most text in screencheck(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 6, 'selection:pos')endfunction test_select_text_using_mouse_and_shift()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.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each line-- click on first locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- hold down shift and click somewhere elseApp.fake_key_press('lshift')edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)App.fake_key_release('lshift')check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 2, 'selection:pos')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')endfunction test_select_text_repeatedly_using_mouse_and_shift()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)Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each line-- click on first locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- hold down shift and click on a second locationApp.fake_key_press('lshift')edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)-- hold down shift and click at a third locationApp.fake_key_press('lshift')edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height+5, 1)App.fake_key_release('lshift')-- selection is between first and third location. forget the second location, not the first.check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 2, 'selection:pos')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')endfunction test_select_all_text()-- display a single line of textApp.screen.init{width=75, height=80}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}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- select allApp.fake_key_press('lctrl')edit.run_after_keychord(Editor_state, 'C-a', 'a')App.fake_key_release('lctrl')edit.key_release(Editor_state, 'lctrl')-- selectioncheck_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 1, 'selection:pos')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 8, 'cursor:pos')endfunction test_cut_without_selection()-- display a few linesApp.screen.init{width=Editor_state.left+30, 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=2}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.selection1 = {}edit.draw(Editor_state)-- try to cut without selecting textedit.run_after_keychord(Editor_state, 'C-x', 'x')-- no crashcheck_nil(Editor_state.selection1.line, 'check')endfunction test_pagedown()App.screen.init{width=120, height=45}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}-- initially the first two lines are displayededit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')-- after pagedown the bottom line becomes the topedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:2')endfunction test_pagedown_skips_drawings()-- some lines of text with a drawing intermixedlocal drawing_width = 50App.screen.init{width=Editor_state.left+drawing_width, height=80}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', -- height 15'```lines', '```', -- height 25'def', -- height 15'ghi'} -- height 15Text.redraw_all(Editor_state)check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines')Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}local drawing_height = Drawing_padding_height + drawing_width/2 -- default-- initially the screen displays the first line and the drawing-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80pxedit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')-- after pagedown the screen draws the drawing up top-- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80pxedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor')y = Editor_state.top + drawing_heightApp.screen.check(y, 'def', 'screen:1')endfunction test_pagedown_can_start_from_middle_of_long_wrapping_line()-- draw a few lines starting from a very long wrapping lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu vwx yza bcd efg hij', 'XYZ'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc ', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def ', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi j', 'baseline/screen:3')-- after pagedown we scroll down the very long wrapping lineedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 9, 'screen_top:pos')y = Editor_state.topApp.screen.check(y, 'ghi j', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl m', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'no p', 'screen:3')endfunction test_pagedown_never_moves_up()-- draw the final screen line of a wrapping lineApp.screen.init{width=Editor_state.left+30, 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=9}Editor_state.screen_top1 = {line=1, pos=9}edit.draw(Editor_state)-- pagedown makes no changeedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 9, 'screen_top:pos')endfunction test_down_arrow_moves_cursor()App.screen.init{width=120, 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}-- initially the first three lines are displayededit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the down arrow, the cursor moves down by 1 lineedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor')-- the screen is unchangedy = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_down_arrow_skips_drawing()-- some lines of text with a drawing intermixedlocal drawing_width = 50App.screen.init{width=Editor_state.left+drawing_width, height=100}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', -- height 15'```lines', '```', -- height 25'ghi'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightlocal drawing_height = Drawing_padding_height + drawing_width/2 -- defaulty = y + drawing_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')check(Editor_state.cursor_x, 'baseline/cursor_x')-- after hitting the down arrow the cursor moves down by 2 lines, skipping the drawingedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.cursor1.line, 3, 'cursor')endfunction test_down_arrow_scrolls_down_by_one_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=120, 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=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the down arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 4, 'cursor')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')endfunction test_down_arrow_scrolls_down_by_one_screen_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghij kl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghij ', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the down arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghij ', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:3')endfunction test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghijkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghijk', 'baseline/screen:3')-- after hitting the down arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghijk', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'l', 'screen:3')endfunction test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()App.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghijkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghijk', 'baseline/screen:3')-- after hitting pagedown the screen scrolls down to start of a long lineedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 3, 'baseline2/screen_top')check_eq(Editor_state.cursor1.line, 3, 'baseline2/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'baseline2/cursor:pos')-- after hitting down arrow the screen doesn't scroll down further, and certainly doesn't scroll upedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 3, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'ghijk', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'l', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')endfunction test_up_arrow_moves_cursor()-- display the first 3 lines with the cursor on the bottom lineApp.screen.init{width=120, 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=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the up arrow the cursor moves up by 1 lineedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor')-- the screen is unchangedy = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_up_arrow_skips_drawing()-- some lines of text with a drawing intermixedlocal drawing_width = 50App.screen.init{width=Editor_state.left+drawing_width, height=100}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', -- height 15'```lines', '```', -- height 25'ghi'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightlocal drawing_height = Drawing_padding_height + drawing_width/2 -- defaulty = y + drawing_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')check(Editor_state.cursor_x, 'baseline/cursor_x')-- after hitting the up arrow the cursor moves up by 2 lines, skipping the drawingedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.cursor1.line, 1, 'cursor')endfunction test_up_arrow_scrolls_up_by_one_line()-- display the lines 2/3/4 with the cursor on line 2App.screen.init{width=120, 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=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up by one lineedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_up_arrow_scrolls_up_by_one_line_skipping_drawing()-- display lines 3/4/5 with a drawing just off screen at line 2App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', '```lines', '```', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=3, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up to previous text lineedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')endfunction test_up_arrow_scrolls_up_by_one_screen_line()-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=6}Editor_state.screen_top1 = {line=3, pos=6}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'kl', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:2')-- after hitting the up arrow the screen scrolls up to first screen lineedit.run_after_keychord(Editor_state, 'up', 'up')y = Editor_state.topApp.screen.check(y, 'ghi j', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')endfunction test_up_arrow_scrolls_up_to_final_screen_line()-- display lines starting just after a long lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'ghi', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up to final screen line of previous lineedit.run_after_keychord(Editor_state, 'up', 'up')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 5, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')endfunction test_up_arrow_scrolls_up_to_empty_line()-- display a screenful of text with an empty line just above it outside the screenApp.screen.init{width=120, 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=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up by one lineedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')y = Editor_state.top-- empty first liney = y + Editor_state.line_heightApp.screen.check(y, 'abc', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:3')endfunction test_pageup()App.screen.init{width=120, height=45}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}Editor_state.screen_top1 = {line=2, pos=1}-- initially the last two lines are displayededit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')-- after pageup the cursor goes to first lineedit.run_after_keychord(Editor_state, 'pageup', 'pageup')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')endfunction test_pageup_scrolls_up_by_screen_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'ghi', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the page-up key the screen scrolls up to topedit.run_after_keychord(Editor_state, 'pageup', 'pageup')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'abc ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_pageup_scrolls_up_from_middle_screen_line()-- display a few lines starting from the middle of a line (Editor_state.cursor1.pos > 1)App.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=6}Editor_state.screen_top1 = {line=2, pos=6}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'kl', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the page-up key the screen scrolls up to topedit.run_after_keychord(Editor_state, 'pageup', 'pageup')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'abc ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi j', 'screen:3')endfunction test_enter_on_bottom_line_scrolls_down()-- display a few lines with cursor on bottom lineApp.screen.init{width=Editor_state.left+30, 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=3, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the enter key the screen scrolls downedit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 4, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'g', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'hi', 'screen:3')endfunction test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()-- display just the bottom line on screenApp.screen.init{width=Editor_state.left+30, 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=4, pos=2}Editor_state.screen_top1 = {line=4, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'baseline/screen:1')-- after hitting the enter key the screen does not scroll downedit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.screen_top1.line, 4, 'screen_top')check_eq(Editor_state.cursor1.line, 5, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'j', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:2')endfunction test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom()-- display just an empty bottom line on screenApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', ''}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)-- after hitting the inserting_text key the screen does not scroll downedit.run_after_text_input(Editor_state, 'a')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')local y = Editor_state.topApp.screen.check(y, 'a', 'screen:1')endfunction test_typing_on_bottom_line_scrolls_down()-- display a few lines with cursor on bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'pqr'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=4}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after typing something the line wraps and the screen scrolls downedit.run_after_text_input(Editor_state, 'j')edit.run_after_text_input(Editor_state, 'k')edit.run_after_text_input(Editor_state, 'l')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 7, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghijk', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'l', 'screen:3')endfunction test_left_arrow_scrolls_up_in_wrapped_line()-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=3, pos=6}-- cursor is at top of screenEditor_state.cursor1 = {line=3, pos=6}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'kl', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:2')-- after hitting the left arrow the screen scrolls up to first screen lineedit.run_after_keychord(Editor_state, 'left', 'left')y = Editor_state.topApp.screen.check(y, 'ghi j', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')endfunction test_right_arrow_scrolls_down_in_wrapped_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}-- cursor is at bottom right of screenEditor_state.cursor1 = {line=3, pos=6}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi j', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the right arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'right', 'right')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 7, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi j', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:3')endfunction test_home_scrolls_up_in_wrapped_line()-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=3, pos=6}-- cursor is at top of screenEditor_state.cursor1 = {line=3, pos=6}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'kl', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:2')-- after hitting home the screen scrolls up to first screen lineedit.run_after_keychord(Editor_state, 'home', 'home')y = Editor_state.topApp.screen.check(y, 'ghi j', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')endfunction test_end_scrolls_down_in_wrapped_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}-- cursor is at bottom right of screenEditor_state.cursor1 = {line=3, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi j', 'baseline/screen:3')-- after hitting end the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'end', 'end')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 8, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi j', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:3')endfunction test_position_cursor_on_recently_edited_wrapping_line()-- draw a line wrapping over 2 screen linesApp.screen.init{width=100, height=200}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def ghi jkl mno pqr ', 'xyz'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=25}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl mno pqr ', 'baseline1/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'baseline1/screen:3')-- add to the line until it's wrapping over 3 screen linesedit.run_after_text_input(Editor_state, 's')edit.run_after_text_input(Editor_state, 't')edit.run_after_text_input(Editor_state, 'u')check_eq(Editor_state.cursor1.pos, 28, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'abc def ghi ', 'baseline2/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl mno pqr ', 'baseline2/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'stu', 'baseline2/screen:3')-- try to move the cursor earlier in the third screen line by clicking the mouseedit.run_after_mouse_release(Editor_state, Editor_state.left+2,Editor_state.top+Editor_state.line_height*2+5, 1)-- cursor should movecheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 25, 'cursor:pos')endfunction test_backspace_can_scroll_up()-- display the lines 2/3/4 with the cursor on line 2App.screen.init{width=120, 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=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- after hitting backspace the screen scrolls up by one lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')y = Editor_state.topApp.screen.check(y, 'abcdef', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')endfunction test_backspace_can_scroll_up_screen_line()-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=6}Editor_state.screen_top1 = {line=3, pos=6}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'kl', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:2')-- after hitting backspace the screen scrolls up by one screen lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')y = Editor_state.topApp.screen.check(y, 'ghi k', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'l', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')endfunction test_backspace_past_line_boundary()-- position cursor at start of a (non-first) lineApp.screen.init{width=Editor_state.left+30, 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}-- backspace joins with previous lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'abcdef', 'check')end-- some tests for operating over selections created using Shift- chords-- we're just testing delete_selection, and it works the same for all keysfunction test_backspace_over_selection()-- select just one character within a line with cursor before selectionApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.selection1 = {line=1, pos=2}-- backspace deletes the selected character, even though it's after the cursoredit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'bc', 'data')-- cursor (remains) at start of selectioncheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_backspace_over_selection_reverse()-- select just one character within a line with cursor after selectionApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.selection1 = {line=1, pos=1}-- backspace deletes the selected characteredit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'bc', 'data')-- cursor moves to start of selectioncheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_backspace_over_multiple_lines()-- select just one character within a line with cursor after selectionApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.selection1 = {line=4, pos=2}-- backspace deletes the region and joins the remaining portions of lines on either sideedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'akl', 'data:1')check_eq(Editor_state.lines[2].data, 'mno', 'data:2')-- cursor remains at start of selectioncheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_backspace_to_end_of_line()-- select region from cursor to end of lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.selection1 = {line=1, pos=4}-- backspace deletes rest of line without joining to any other lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'a', 'data:1')check_eq(Editor_state.lines[2].data, 'def', 'data:2')-- cursor remains at start of selectioncheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_backspace_to_start_of_line()-- select region from cursor to start of lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.selection1 = {line=2, pos=3}-- backspace deletes beginning of line without joining to any other lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'abc', 'data:1')check_eq(Editor_state.lines[2].data, 'f', 'data:2')-- cursor remains at start of selectioncheck_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_undo_insert_text()App.screen.init{width=120, 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=2, pos=4}Editor_state.screen_top1 = {line=1, pos=1}-- insert a characteredit.draw(Editor_state)edit.run_after_text_input(Editor_state, 'g')check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'baseline/cursor:pos')check_nil(Editor_state.selection1.line, 'baseline/selection:line')check_nil(Editor_state.selection1.pos, 'baseline/selection:pos')local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'defg', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'baseline/screen:3')-- undoedit.run_after_keychord(Editor_state, 'C-z', 'z')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')check_nil(Editor_state.selection1.line, 'selection:line')check_nil(Editor_state.selection1.pos, 'selection:pos')y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'screen:3')endfunction test_undo_delete_text()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'defg', 'xyz'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=5}Editor_state.screen_top1 = {line=1, pos=1}-- delete a characteredit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'baseline/cursor:pos')check_nil(Editor_state.selection1.line, 'baseline/selection:line')check_nil(Editor_state.selection1.pos, 'baseline/selection:pos')local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'baseline/screen:3')-- undo--? -- after undo, the backspaced key is selectededit.run_after_keychord(Editor_state, 'C-z', 'z')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')check_nil(Editor_state.selection1.line, 'selection:line')check_nil(Editor_state.selection1.pos, 'selection:pos')--? check_eq(Editor_state.selection1.line, 2, 'selection:line')--? check_eq(Editor_state.selection1.pos, 4, 'selection:pos')y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'defg', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'screen:3')endfunction test_undo_restores_selection()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- delete selected textedit.run_after_text_input(Editor_state, 'x')check_eq(Editor_state.lines[1].data, 'xbc', 'baseline')check_nil(Editor_state.selection1.line, 'baseline:selection')-- undoedit.run_after_keychord(Editor_state, 'C-z', 'z')edit.run_after_keychord(Editor_state, 'C-z', 'z')-- selection is restoredcheck_eq(Editor_state.selection1.line, 1, 'line')check_eq(Editor_state.selection1.pos, 2, 'pos')endfunction test_search()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'```lines', '```', 'def', 'ghi', '’deg'} -- contains unicode quote in final lineText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for a stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'd')edit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.cursor1.line, 2, '1/cursor:line')check_eq(Editor_state.cursor1.pos, 1, '1/cursor:pos')-- reset cursorEditor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}-- search for second occurrenceedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'de')edit.run_after_keychord(Editor_state, 'down', 'down')edit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.cursor1.line, 4, '2/cursor:line')check_eq(Editor_state.cursor1.pos, 2, '2/cursor:pos')endfunction test_search_upwards()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'’abc', 'abd'} -- contains unicode quoteText.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for a stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'a')-- search for previous occurrenceedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.cursor1.line, 1, '2/cursor:line')check_eq(Editor_state.cursor1.pos, 2, '2/cursor:pos')endfunction test_search_wrap()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'’abc', 'def'} -- contains unicode quote in first lineText.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for a stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'a')edit.run_after_keychord(Editor_state, 'return', 'return')-- cursor wrapscheck_eq(Editor_state.cursor1.line, 1, '1/cursor:line')check_eq(Editor_state.cursor1.pos, 2, '1/cursor:pos')endfunction test_search_wrap_upwards()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc ’abd'} -- contains unicode quoteText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search upwards for a stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'a')edit.run_after_keychord(Editor_state, 'up', 'up')-- cursor wrapscheck_eq(Editor_state.cursor1.line, 1, '1/cursor:line')check_eq(Editor_state.cursor1.pos, 6, '1/cursor:pos')endfunction test_search_downwards_from_end_of_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=4}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for empty stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_keychord(Editor_state, 'down', 'down')-- no crashendfunction test_search_downwards_from_final_pos_of_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=3}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for empty stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_keychord(Editor_state, 'down', 'down')-- no crashend - replacement in source_text_tests.lua at line 2[10.3537]→[10.7:119](∅→∅),[10.119]→[10.3537:3568](∅→∅),[10.3537]→[10.3537:3568](∅→∅),[10.3603]→[10.3603:3783](∅→∅),[10.3783]→[10.23368:23649](∅→∅),[10.23649]→[10.2262:2307](∅→∅),[10.4179]→[10.2262:2307](∅→∅),[10.2307]→[10.7:48](∅→∅),[10.48]→[10.2392:2651](∅→∅),[10.2392]→[10.2392:2651](∅→∅),[10.2651]→[10.23650:23746](∅→∅),[10.23746]→[10.2813:2862](∅→∅),[10.2813]→[10.2813:2862](∅→∅),[10.2911]→[10.2911:3343](∅→∅),[10.3343]→[10.4772:4838](∅→∅),[10.4838]→[10.23747:23843](∅→∅),[10.3396]→[10.23747:23843](∅→∅),[10.3566]→[10.4179:4235](∅→∅),[10.23843]→[10.4179:4235](∅→∅),[10.4179]→[10.4179:4235](∅→∅),[10.4291]→[10.4291:4628](∅→∅),[10.4628]→[10.4839:4905](∅→∅),[10.4905]→[10.23844:23999](∅→∅),[10.4681]→[10.23844:23999](∅→∅),[10.23999]→[10.4968:5012](∅→∅),[10.4968]→[10.4968:5012](∅→∅),[10.5056]→[10.5056:5236](∅→∅),[10.5236]→[10.1184:1231](∅→∅),[10.1231]→[10.5282:5311](∅→∅),[10.5282]→[10.5282:5311](∅→∅),[10.5311]→[10.24000:24039](∅→∅),[10.24039]→[10.5382:5414](∅→∅),[10.5382]→[10.5382:5414](∅→∅),[10.5446]→[10.5446:5731](∅→∅),[10.5766]→[10.4906:4958](∅→∅),[10.4958]→[10.5813:5844](∅→∅),[10.5813]→[10.5813:5844](∅→∅),[10.5875]→[10.5875:6099](∅→∅),[10.6099]→[10.4959:5015](∅→∅),[10.5015]→[10.24040:24089](∅→∅),[10.6147]→[10.24040:24089](∅→∅),[10.24089]→[10.6209:6241](∅→∅),[10.6209]→[10.6209:6241](∅→∅),[10.6273]→[10.6273:6497](∅→∅),[10.6497]→[10.5016:5074](∅→∅),[10.5074]→[10.24090:24139](∅→∅),[10.6546]→[10.24090:24139](∅→∅),[10.24139]→[10.6609:6657](∅→∅),[10.6609]→[10.6609:6657](∅→∅),[10.6705]→[10.6705:6938](∅→∅),[10.6938]→[10.5075:5131](∅→∅),[10.5131]→[10.24140:24257](∅→∅),[10.6986]→[10.24140:24257](∅→∅),[10.24257]→[10.7175:7220](∅→∅),[10.7175]→[10.7175:7220](∅→∅),[10.7265]→[10.7265:7519](∅→∅),[10.7519]→[10.5132:5190](∅→∅),[10.5190]→[10.24258:24354](∅→∅),[10.7568]→[10.24258:24354](∅→∅),[10.24354]→[10.7730:7773](∅→∅),[10.7730]→[10.7730:7773](∅→∅),[10.7816]→[10.7816:8042](∅→∅),[10.8042]→[10.5191:5249](∅→∅),[10.5249]→[10.24355:24404](∅→∅),[10.8092]→[10.24355:24404](∅→∅),[10.24404]→[10.8166:8218](∅→∅),[10.8166]→[10.8166:8218](∅→∅),[10.8270]→[10.8270:8531](∅→∅),[10.8531]→[10.5250:5308](∅→∅),[10.5308]→[10.24405:24454](∅→∅),[10.8581]→[10.24405:24454](∅→∅),[10.24454]→[10.8664:8707](∅→∅),[10.8664]→[10.8664:8707](∅→∅),[10.8750]→[10.8750:9012](∅→∅),[10.9012]→[10.5309:5367](∅→∅),[10.5367]→[10.24455:24504](∅→∅),[10.9062]→[10.24455:24504](∅→∅),[10.24504]→[10.9136:9188](∅→∅),[10.9136]→[10.9136:9188](∅→∅),[10.9240]→[10.9240:9498](∅→∅),[10.9498]→[10.5368:5426](∅→∅),[10.5426]→[10.24505:24554](∅→∅),[10.9548]→[10.24505:24554](∅→∅),[10.24554]→[10.9631:9690](∅→∅),[10.9631]→[10.9631:9690](∅→∅),[10.9749]→[10.9749:10012](∅→∅),[10.10012]→[10.5427:5485](∅→∅),[10.5485]→[10.24555:24604](∅→∅),[10.10062]→[10.24555:24604](∅→∅),[10.24604]→[10.10152:10212](∅→∅),[10.10152]→[10.10152:10212](∅→∅),[10.10272]→[10.10272:10509](∅→∅),[10.10509]→[10.5486:5544](∅→∅),[10.5544]→[10.24605:24701](∅→∅),[10.10559]→[10.24605:24701](∅→∅),[10.24701]→[10.10751:10794](∅→∅),[10.10751]→[10.10751:10794](∅→∅),[10.10837]→[10.10837:11067](∅→∅),[10.11067]→[10.5545:5605](∅→∅),[10.5605]→[10.24702:24751](∅→∅),[10.11118]→[10.24702:24751](∅→∅),[10.24751]→[10.11192:11231](∅→∅),[10.11192]→[10.11192:11231](∅→∅),[10.11270]→[10.11270:11531](∅→∅),[10.11531]→[10.5606:5666](∅→∅),[10.5666]→[10.24752:24801](∅→∅),[10.11582]→[10.24752:24801](∅→∅),[10.24801]→[10.11652:11700](∅→∅),[10.11652]→[10.11652:11700](∅→∅),[10.11748]→[10.11748:12010](∅→∅),[10.12010]→[10.5667:5727](∅→∅),[10.5727]→[10.24802:24851](∅→∅),[10.12061]→[10.24802:24851](∅→∅),[10.24851]→[10.12140:12195](∅→∅),[10.12140]→[10.12140:12195](∅→∅),[10.12250]→[10.12250:12513](∅→∅),[10.12513]→[7.7:67](∅→∅),[7.67]→[10.24852:24901](∅→∅),[10.5789]→[10.24852:24901](∅→∅),[10.12564]→[10.24852:24901](∅→∅),[10.24901]→[10.12650:12706](∅→∅),[10.12650]→[10.12650:12706](∅→∅),[10.12762]→[10.12762:12999](∅→∅),[10.12999]→[7.68:128](∅→∅),[7.128]→[10.24902:24998](∅→∅),[10.5851]→[10.24902:24998](∅→∅),[10.13050]→[10.24902:24998](∅→∅),[10.24998]→[10.13234:13239](∅→∅),[10.13234]→[10.13234:13239](∅→∅),[10.13239]→[10.6:41](∅→∅),[10.81]→[10.81:120](∅→∅),[10.120]→[10.13399:13445](∅→∅),[10.13399]→[10.13399:13445](∅→∅),[10.13445]→[10.121:176](∅→∅),[10.176]→[10.13493:13525](∅→∅),[10.13493]→[10.13493:13525](∅→∅),[10.13525]→[10.177:218](∅→∅),[10.218]→[10.13566:13611](∅→∅),[10.13566]→[10.13566:13611](∅→∅),[10.13646]→[10.219:250](∅→∅),[10.250]→[10.1844:1917](∅→∅),[10.1917]→[10.345:433](∅→∅),[10.345]→[10.345:433](∅→∅),[10.433]→[10.24999:25109](∅→∅),[10.25109]→[10.599:656](∅→∅),[10.599]→[10.599:656](∅→∅),[10.656]→[10.25110:25228](∅→∅),[10.129]→[10.13887:13892](∅→∅),[10.830]→[10.13887:13892](∅→∅),[10.25228]→[10.13887:13892](∅→∅),[10.13887]→[10.13887:13892](∅→∅),[10.13892]→[10.831:869](∅→∅),[10.912]→[10.13995:14289](∅→∅),[10.13995]→[10.13995:14289](∅→∅),[10.14324]→[10.986:1017](∅→∅),[10.1017]→[10.14324:14506](∅→∅),[10.14324]→[10.14324:14506](∅→∅),[10.14506]→[10.25229:25436](∅→∅),[10.269]→[10.14700:14705](∅→∅),[10.1213]→[10.14700:14705](∅→∅),[10.25436]→[10.14700:14705](∅→∅),[10.14700]→[10.14700:14705](∅→∅),[10.14705]→[10.1214:1263](∅→∅),[10.1317]→[10.14830:15194](∅→∅),[10.14830]→[10.14830:15194](∅→∅),[10.15229]→[10.1018:1049](∅→∅),[10.1049]→[10.15229:15388](∅→∅),[10.15229]→[10.15229:15388](∅→∅),[10.15388]→[10.25437:25644](∅→∅),[10.420]→[10.15604:15609](∅→∅),[10.1651]→[10.15604:15609](∅→∅),[10.25644]→[10.15604:15609](∅→∅),[10.15604]→[10.15604:15609](∅→∅),[10.15609]→[10.1652:1688](∅→∅),[10.1729]→[10.15708:16004](∅→∅),[10.15708]→[10.15708:16004](∅→∅),[10.16039]→[10.1050:1081](∅→∅),[10.1081]→[10.16039:16198](∅→∅),[10.16039]→[10.16039:16198](∅→∅),[10.16198]→[10.1082:1264](∅→∅),[10.1264]→[9.218:265](∅→∅),[9.265]→[10.1302:1568](∅→∅),[10.1302]→[10.1302:1568](∅→∅),[10.1603]→[10.1603:1775](∅→∅),[10.1775]→[9.266:403](∅→∅),[9.403]→[10.1801:1927](∅→∅),[10.25696]→[10.1801:1927](∅→∅),[10.1810]→[10.16289:16320](∅→∅),[10.1927]→[10.16289:16320](∅→∅),[10.25696]→[10.16289:16320](∅→∅),[10.16289]→[10.16289:16320](∅→∅),[10.16351]→[10.16351:16610](∅→∅),[10.16645]→[10.16645:16700](∅→∅),[10.16700]→[10.25697:25738](∅→∅),[10.25738]→[10.16760:16795](∅→∅),[10.16760]→[10.16760:16795](∅→∅),[10.16795]→[10.25739:25780](∅→∅),[10.25780]→[10.16855:16890](∅→∅),[10.16855]→[10.16855:16890](∅→∅),[10.16890]→[10.25781:25822](∅→∅),[10.25822]→[10.16950:16990](∅→∅),[10.16950]→[10.16950:16990](∅→∅),[10.17030]→[10.17030:17290](∅→∅),[10.17325]→[10.17325:17380](∅→∅),[10.17380]→[10.25823:25864](∅→∅),[10.25864]→[10.17449:17484](∅→∅),[10.17449]→[10.17449:17484](∅→∅),[10.17484]→[10.25865:25905](∅→∅),[10.25905]→[10.17552:17587](∅→∅),[10.17552]→[10.17552:17587](∅→∅),[10.17587]→[10.25906:25947](∅→∅),[10.25947]→[10.17656:17701](∅→∅),[10.17656]→[10.17656:17701](∅→∅),[10.17746]→[10.17746:18005](∅→∅),[10.18040]→[10.18040:18095](∅→∅),[10.18095]→[10.25948:25990](∅→∅),[10.25990]→[10.18170:18205](∅→∅),[10.18170]→[10.18170:18205](∅→∅),[10.18205]→[10.25991:26033](∅→∅),[10.26033]→[10.18280:18315](∅→∅),[10.18280]→[10.18280:18315](∅→∅),[10.18315]→[10.26034:26075](∅→∅),[10.26075]→[10.18389:18394](∅→∅),[10.18389]→[10.18389:18394](∅→∅),[10.18394]→[10.1811:1850](∅→∅),[10.1850]→[2.169:226](∅→∅),[2.226]→[10.18549:18818](∅→∅),[10.18549]→[10.18549:18818](∅→∅),[10.18853]→[10.18853:19012](∅→∅),[10.19012]→[10.26076:26283](∅→∅),[10.561]→[10.19208:19213](∅→∅),[10.2198]→[10.19208:19213](∅→∅),[10.26283]→[10.19208:19213](∅→∅),[10.19208]→[10.19208:19213](∅→∅),[10.19213]→[10.2199:2265](∅→∅),[10.2265]→[2.227:284](∅→∅),[2.284]→[10.19422:19754](∅→∅),[10.19422]→[10.19422:19754](∅→∅),[10.19789]→[10.19789:19948](∅→∅),[10.19948]→[10.26284:26491](∅→∅),[10.729]→[10.20198:20314](∅→∅),[10.2721]→[10.20198:20314](∅→∅),[10.26491]→[10.20198:20314](∅→∅),[10.20198]→[10.20198:20314](∅→∅),[10.20366]→[10.20366:20627](∅→∅),[10.20662]→[10.20662:20717](∅→∅),[10.20717]→[10.26492:26535](∅→∅),[10.26535]→[10.20800:20835](∅→∅),[10.20800]→[10.20800:20835](∅→∅),[10.20835]→[10.26536:26579](∅→∅),[10.26579]→[10.20918:20953](∅→∅),[10.20918]→[10.20918:20953](∅→∅),[10.20953]→[10.26580:26621](∅→∅),[10.26621]→[10.21034:21138](∅→∅),[10.21034]→[10.21034:21138](∅→∅),[10.21199]→[10.21199:21499](∅→∅),[10.21534]→[10.21534:21589](∅→∅),[10.21589]→[10.26622:26663](∅→∅),[10.26663]→[10.21679:21714](∅→∅),[10.21679]→[10.21679:21714](∅→∅),[10.21714]→[10.26664:26706](∅→∅),[10.26706]→[10.21805:21840](∅→∅),[10.21805]→[10.21805:21840](∅→∅),[10.21840]→[10.26707:26751](∅→∅),[10.26751]→[10.21933:21938](∅→∅),[10.21933]→[10.21933:21938](∅→∅),[10.21938]→[2.285:331](∅→∅),[2.331]→[10.22021:22355](∅→∅),[10.22021]→[10.22021:22355](∅→∅),[10.22390]→[10.22390:22445](∅→∅),[10.22445]→[10.26752:26805](∅→∅),[10.26805]→[10.22530:22565](∅→∅),[10.22530]→[10.22530:22565](∅→∅),[10.22565]→[10.26806:26859](∅→∅),[10.26859]→[10.22650:22797](∅→∅),[10.22650]→[10.22650:22797](∅→∅),[10.22797]→[6.473:551](∅→∅),[6.551]→[10.26860:26916](∅→∅),[10.22837]→[10.26860:26916](∅→∅),[10.26916]→[6.552:607](∅→∅),[6.607]→[10.23012:23095](∅→∅),[10.26971]→[10.23012:23095](∅→∅),[10.23012]→[10.23012:23095](∅→∅),[10.23178]→[10.23178:23540](∅→∅),[10.23575]→[10.23575:23630](∅→∅),[10.23630]→[10.26972:27025](∅→∅),[10.27025]→[10.23754:23901](∅→∅),[10.23754]→[10.23754:23901](∅→∅),[10.23901]→[6.608:686](∅→∅),[6.686]→[10.27026:27082](∅→∅),[10.23941]→[10.27026:27082](∅→∅),[10.27082]→[6.687:742](∅→∅),[6.742]→[10.24194:24247](∅→∅),[10.27137]→[10.24194:24247](∅→∅),[10.24194]→[10.24194:24247](∅→∅),[10.24300]→[10.24300:24634](∅→∅),[10.24669]→[10.24669:24724](∅→∅),[10.24724]→[10.27138:27191](∅→∅),[10.27191]→[10.24818:24853](∅→∅),[10.24818]→[10.24818:24853](∅→∅),[10.24853]→[10.27192:27245](∅→∅),[10.27245]→[10.24947:24982](∅→∅),[10.24947]→[10.24947:24982](∅→∅),[10.24982]→[10.27246:27295](∅→∅),[10.27295]→[10.25072:25240](∅→∅),[10.25072]→[10.25072:25240](∅→∅),[10.25240]→[10.27296:27397](∅→∅),[10.27397]→[10.25382:25456](∅→∅),[10.25382]→[10.25382:25456](∅→∅),[10.25530]→[10.25530:25923](∅→∅),[10.25958]→[10.25958:26013](∅→∅),[10.26013]→[10.27398:27451](∅→∅),[10.27451]→[10.26128:26163](∅→∅),[10.26128]→[10.26128:26163](∅→∅),[10.26163]→[10.27452:27507](∅→∅),[10.27507]→[10.26280:26315](∅→∅),[10.26280]→[10.26280:26315](∅→∅),[10.26315]→[10.27508:27557](∅→∅),[10.27557]→[10.26426:26594](∅→∅),[10.26426]→[10.26426:26594](∅→∅),[10.26594]→[10.27558:27659](∅→∅),[10.27659]→[10.26757:26815](∅→∅),[10.26757]→[10.26757:26815](∅→∅),[10.26873]→[10.26873:27362](∅→∅),[10.27397]→[10.27397:27452](∅→∅),[10.27452]→[10.27660:27727](∅→∅),[10.27727]→[10.27565:27713](∅→∅),[10.27565]→[10.27565:27713](∅→∅),[10.27713]→[6.743:872](∅→∅),[6.872]→[10.730:763](∅→∅),[10.27779]→[10.730:763](∅→∅),[10.27850]→[10.730:763](∅→∅),[10.796]→[10.796:1072](∅→∅),[10.1107]→[10.1107:1185](∅→∅),[10.1185]→[7.129:189](∅→∅),[7.189]→[10.1236:1269](∅→∅),[10.5913]→[10.1236:1269](∅→∅),[10.1236]→[10.1236:1269](∅→∅),[10.1269]→[10.1232:1275](∅→∅),[10.1275]→[10.60:113](∅→∅),[10.113]→[10.27780:28012](∅→∅),[10.28012]→[10.1682:1750](∅→∅),[10.1682]→[10.1682:1750](∅→∅),[10.1818]→[10.1818:2158](∅→∅),[10.2193]→[10.2193:2257](∅→∅),[10.2257]→[7.190:248](∅→∅),[7.248]→[10.2306:2349](∅→∅),[10.5973]→[10.2306:2349](∅→∅),[10.2306]→[10.2306:2349](∅→∅),[10.2349]→[10.28013:28118](∅→∅),[10.2560]→[10.27850:27855](∅→∅),[10.28118]→[10.27850:27855](∅→∅),[10.27850]→[10.27850:27855](∅→∅),[10.27855]→[10.2561:2600](∅→∅),[10.2644]→[10.2644:2984](∅→∅),[10.3019]→[10.3019:3062](∅→∅),[10.3062]→[10.1328:1375](∅→∅),[10.1375]→[10.3108:3164](∅→∅),[10.3108]→[10.3108:3164](∅→∅),[10.3164]→[10.28119:28174](∅→∅),[10.28174]→[10.3245:3304](∅→∅),[10.3245]→[10.3245:3304](∅→∅),[10.3363]→[10.3363:3703](∅→∅),[10.3738]→[10.3738:3846](∅→∅),[10.3846]→[10.1376:1497](∅→∅),[10.1497]→[10.3969:4058](∅→∅),[10.3969]→[10.3969:4058](∅→∅),[10.4058]→[10.28175:28280](∅→∅),[10.28280]→[10.4251:4302](∅→∅),[10.4251]→[10.4251:4302](∅→∅),[10.4353]→[10.4353:4686](∅→∅),[10.4721]→[10.4721:4767](∅→∅),[10.4767]→[10.5974:6026](∅→∅),[10.6026]→[10.28281:28325](∅→∅),[10.4814]→[10.28281:28325](∅→∅),[10.28325]→[10.4897:4952](∅→∅),[10.4897]→[10.4897:4952](∅→∅),[10.4952]→[10.28326:28373](∅→∅),[10.28373]→[10.5032:5057](∅→∅),[10.5032]→[10.5032:5057](∅→∅),[10.5082]→[10.5082:5422](∅→∅),[10.5457]→[10.5457:5500](∅→∅),[10.5500]→[10.6027:6079](∅→∅),[10.6079]→[10.28374:28418](∅→∅),[10.5547]→[10.28374:28418](∅→∅),[10.28418]→[10.5604:5634](∅→∅),[10.5604]→[10.5604:5634](∅→∅),[10.5634]→[10.28419:28472](∅→∅),[10.28472]→[10.5700:5746](∅→∅),[10.5700]→[10.5700:5746](∅→∅),[10.5792]→[10.5792:6132](∅→∅),[10.6167]→[10.6167:6257](∅→∅),[10.6257]→[10.6080:6132](∅→∅),[10.6132]→[10.6304:6417](∅→∅),[10.6304]→[10.6304:6417](∅→∅),[10.6417]→[10.28473:28531](∅→∅),[10.28531]→[10.6503:6554](∅→∅),[10.6503]→[10.6503:6554](∅→∅),[10.6605]→[10.6605:6896](∅→∅),[10.6931]→[10.6931:6986](∅→∅),[10.6986]→[10.28532:28582](∅→∅),[10.28582]→[10.7075:7110](∅→∅),[10.7075]→[10.7075:7110](∅→∅),[10.7110]→[10.28583:28633](∅→∅),[10.28633]→[10.7199:7234](∅→∅),[10.7199]→[10.7199:7234](∅→∅),[10.7234]→[10.28634:28684](∅→∅),[10.28684]→[10.7323:7457](∅→∅),[10.7323]→[10.7323:7457](∅→∅),[10.7457]→[10.6133:6199](∅→∅),[10.6199]→[10.7510:7531](∅→∅),[10.7510]→[10.7510:7531](∅→∅),[10.7531]→[10.28685:28793](∅→∅),[10.28793]→[10.7711:7716](∅→∅),[10.7711]→[10.7711:7716](∅→∅),[10.7716]→[10.27855:27890](∅→∅),[10.27855]→[10.27855:27890](∅→∅),[10.27930]→[10.27930:28188](∅→∅),[10.28223]→[10.28223:28249](∅→∅),[10.28249]→[10.1498:1545](∅→∅),[10.1545]→[10.28295:28324](∅→∅),[10.28295]→[10.28295:28324](∅→∅),[10.28324]→[10.28794:28835](∅→∅),[10.28835]→[10.28393:28428](∅→∅),[10.28393]→[10.28393:28428](∅→∅),[10.28428]→[10.28836:28876](∅→∅),[10.28876]→[10.28496:28531](∅→∅),[10.28496]→[10.28496:28531](∅→∅),[10.28531]→[10.28877:28917](∅→∅),[10.28917]→[10.28599:28635](∅→∅),[10.28599]→[10.28599:28635](∅→∅),[10.28671]→[10.28671:28979](∅→∅),[10.29014]→[10.29014:29069](∅→∅),[10.29069]→[10.28918:28968](∅→∅),[10.28968]→[10.29143:29178](∅→∅),[10.29143]→[10.29143:29178](∅→∅),[10.29178]→[10.28969:29019](∅→∅),[10.29019]→[10.29252:29287](∅→∅),[10.29252]→[10.29252:29287](∅→∅),[10.29287]→[10.29020:29070](∅→∅),[10.29070]→[10.29361:29404](∅→∅),[10.29361]→[10.29361:29404](∅→∅),[10.29404]→[10.6200:6260](∅→∅),[10.6260]→[10.29071:29240](∅→∅),[10.29454]→[10.29071:29240](∅→∅),[10.29240]→[10.29695:29718](∅→∅),[10.29695]→[10.29695:29718](∅→∅),[10.29718]→[10.29241:29280](∅→∅),[10.29280]→[10.29781:29816](∅→∅),[10.29781]→[10.29781:29816](∅→∅),[10.29816]→[10.29281:29321](∅→∅),[10.29321]→[10.29880:29915](∅→∅),[10.29880]→[10.29880:29915](∅→∅),[10.29915]→[10.29322:29363](∅→∅),[10.29363]→[10.29980:30033](∅→∅),[10.29980]→[10.29980:30033](∅→∅),[10.30086]→[10.30086:30368](∅→∅),[10.30403]→[10.30403:30446](∅→∅),[10.30446]→[10.6261:6321](∅→∅),[10.6321]→[10.29364:29583](∅→∅),[10.30496]→[10.29364:29583](∅→∅),[10.29583]→[10.30879:30922](∅→∅),[10.30879]→[10.30879:30922](∅→∅),[10.30965]→[10.30965:31273](∅→∅),[10.31308]→[10.31308:31363](∅→∅),[10.31363]→[10.29584:29634](∅→∅),[10.29634]→[10.31444:31479](∅→∅),[10.31444]→[10.31444:31479](∅→∅),[10.31479]→[10.29635:29685](∅→∅),[10.29685]→[10.31560:31595](∅→∅),[10.31560]→[10.31560:31595](∅→∅),[10.31595]→[10.29686:29736](∅→∅),[10.29736]→[10.31676:31775](∅→∅),[10.31676]→[10.31676:31775](∅→∅),[10.31775]→[10.6322:6374](∅→∅),[10.6374]→[10.29737:29906](∅→∅),[10.31822]→[10.29737:29906](∅→∅),[10.29906]→[10.32084:32107](∅→∅),[10.32084]→[10.32084:32107](∅→∅),[10.32107]→[10.29907:29948](∅→∅),[10.29948]→[10.32179:32214](∅→∅),[10.32179]→[10.32179:32214](∅→∅),[10.32214]→[10.29949:29990](∅→∅),[10.29990]→[10.32286:32321](∅→∅),[10.32286]→[10.32286:32321](∅→∅),[10.32321]→[10.29991:30032](∅→∅),[10.30032]→[10.8022:8067](∅→∅),[10.8022]→[10.8022:8067](∅→∅),[10.8112]→[10.8112:8370](∅→∅),[10.8405]→[10.8405:8436](∅→∅),[10.8436]→[10.1918:1991](∅→∅),[10.1991]→[10.8531:8806](∅→∅),[10.8531]→[10.8531:8806](∅→∅),[10.8806]→[10.30033:30265](∅→∅),[10.30265]→[10.120:443](∅→∅),[10.478]→[10.478:509](∅→∅),[10.509]→[10.1992:2065](∅→∅),[10.2065]→[10.604:908](∅→∅),[10.604]→[10.604:908](∅→∅),[10.908]→[10.9170:9175](∅→∅),[10.30265]→[10.9170:9175](∅→∅),[10.9170]→[10.9170:9175](∅→∅),[10.9175]→[10.909:1297](∅→∅),[10.1332]→[10.1332:1374](∅→∅),[10.1374]→[4.27:53](∅→∅),[4.53]→[10.1374:2356](∅→∅),[10.1374]→[10.1374:2356](∅→∅),[10.2391]→[10.2391:2939](∅→∅),[10.2939]→[10.9175:9225](∅→∅),[10.9175]→[10.9175:9225](∅→∅),[10.9280]→[10.9280:9538](∅→∅),[10.9573]→[10.9573:9604](∅→∅),[10.9604]→[10.2066:2139](∅→∅),[10.2139]→[10.9699:10213](∅→∅),[10.9699]→[10.9699:10213](∅→∅),[10.10213]→[10.30266:30498](∅→∅),[10.30498]→[10.10617:10683](∅→∅),[10.10617]→[10.10617:10683](∅→∅),[10.10749]→[10.10749:11039](∅→∅),[10.11074]→[10.11074:11105](∅→∅),[10.11105]→[10.2140:2213](∅→∅),[10.2213]→[10.11200:12097](∅→∅),[10.11200]→[10.11200:12097](∅→∅),[10.12097]→[10.30499:30731](∅→∅),[10.30731]→[10.2940:3260](∅→∅),[10.3295]→[10.3295:3367](∅→∅),[10.3367]→[10.6375:6427](∅→∅),[10.6427]→[10.3414:3735](∅→∅),[10.3414]→[10.3414:3735](∅→∅),[10.3735]→[10.33133:33138](∅→∅),[10.12545]→[10.33133:33138](∅→∅),[10.30731]→[10.33133:33138](∅→∅),[10.33133]→[10.33133:33138](∅→∅),[10.33138]→[10.12546:12584](∅→∅),[10.12627]→[10.12627:12935](∅→∅),[10.12970]→[10.12970:13066](∅→∅),[10.13066]→[10.6428:6480](∅→∅),[10.6480]→[10.13113:13127](∅→∅),[10.13113]→[10.13113:13127](∅→∅),[10.13127]→[10.30732:30783](∅→∅),[10.30783]→[10.13203:13208](∅→∅),[10.13203]→[10.13203:13208](∅→∅),[10.13208]→[10.33138:33163](∅→∅),[10.33138]→[10.33138:33163](∅→∅),[10.33193]→[10.33193:33452](∅→∅),[10.33487]→[10.33487:33591](∅→∅),[10.33591]→[10.30784:30834](∅→∅),[10.30834]→[10.33659:33694](∅→∅),[10.33659]→[10.33659:33694](∅→∅),[10.33694]→[10.30835:30885](∅→∅),[10.30885]→[10.33762:33814](∅→∅),[10.33762]→[10.33762:33814](∅→∅),[10.33814]→[10.6481:6545](∅→∅),[10.6545]→[10.30886:30996](∅→∅),[10.33866]→[10.30886:30996](∅→∅),[10.30996]→[10.34012:34035](∅→∅),[10.34012]→[10.34012:34035](∅→∅),[10.34035]→[10.30997:31038](∅→∅),[10.31038]→[10.34094:34129](∅→∅),[10.34094]→[10.34094:34129](∅→∅),[10.34129]→[10.31039:31080](∅→∅),[10.31080]→[10.3567:3612](∅→∅),[10.34188]→[10.3567:3612](∅→∅),[10.3657]→[10.3657:4152](∅→∅),[10.4152]→[10.31081:31149](∅→∅),[10.31149]→[10.4253:4339](∅→∅),[10.4253]→[10.4253:4339](∅→∅),[10.4374]→[10.4374:4674](∅→∅),[10.4674]→[10.31150:31200](∅→∅),[10.31200]→[10.4757:4914](∅→∅),[10.4757]→[10.4757:4914](∅→∅),[10.4914]→[10.6546:6610](∅→∅),[10.6610]→[10.31201:31311](∅→∅),[10.4966]→[10.31201:31311](∅→∅),[10.31311]→[10.5142:5182](∅→∅),[10.5142]→[10.5142:5182](∅→∅),[10.5182]→[10.31312:31353](∅→∅),[10.5256]→[10.34188:34262](∅→∅),[10.31353]→[10.34188:34262](∅→∅),[10.34188]→[10.34188:34262](∅→∅),[10.34336]→[10.34336:34711](∅→∅),[10.34746]→[10.34746:34801](∅→∅),[10.34801]→[10.31354:31405](∅→∅),[10.31405]→[10.34914:34949](∅→∅),[10.34914]→[10.34914:34949](∅→∅),[10.34949]→[10.31406:31457](∅→∅),[10.31457]→[10.35062:35097](∅→∅),[10.35062]→[10.35062:35097](∅→∅),[10.35097]→[10.31458:31509](∅→∅),[10.31509]→[10.35210:35273](∅→∅),[10.35210]→[10.35210:35273](∅→∅),[10.35273]→[10.6611:6675](∅→∅),[10.6675]→[10.31510:31636](∅→∅),[10.35325]→[10.31510:31636](∅→∅),[10.31636]→[10.35575:35598](∅→∅),[10.35575]→[10.35575:35598](∅→∅),[10.35598]→[10.31637:31679](∅→∅),[10.31679]→[10.35702:35737](∅→∅),[10.35702]→[10.35702:35737](∅→∅),[10.35737]→[10.31680:31722](∅→∅),[10.31722]→[10.35841:35876](∅→∅),[10.35841]→[10.35841:35876](∅→∅),[10.35876]→[3.498:816](∅→∅),[10.46]→[10.35980:36025](∅→∅),[3.816]→[10.35980:36025](∅→∅),[10.31765]→[10.35980:36025](∅→∅),[10.35980]→[10.35980:36025](∅→∅),[10.36070]→[10.36070:36391](∅→∅),[10.36426]→[10.36426:36482](∅→∅),[10.36482]→[10.6676:6740](∅→∅),[10.6740]→[10.31766:31892](∅→∅),[10.36534]→[10.31766:31892](∅→∅),[10.31892]→[10.36726:36771](∅→∅),[10.36726]→[10.36726:36771](∅→∅),[10.36816]→[10.36816:37082](∅→∅),[10.37117]→[10.37117:37223](∅→∅),[10.37223]→[10.31893:31943](∅→∅),[10.31943]→[10.37306:37341](∅→∅),[10.37306]→[10.37306:37341](∅→∅),[10.37341]→[10.31944:31994](∅→∅),[10.31994]→[10.37424:37459](∅→∅),[10.37424]→[10.37424:37459](∅→∅),[10.37459]→[10.31995:32045](∅→∅),[10.32045]→[10.37542:37609](∅→∅),[10.37542]→[10.37542:37609](∅→∅),[10.37609]→[10.6741:6797](∅→∅),[10.6797]→[10.32046:32156](∅→∅),[10.37657]→[10.32046:32156](∅→∅),[10.32156]→[10.37833:37885](∅→∅),[10.37833]→[10.37833:37885](∅→∅),[10.37885]→[10.32157:32198](∅→∅),[10.32198]→[10.37959:37994](∅→∅),[10.37959]→[10.37959:37994](∅→∅),[10.37994]→[10.32199:32240](∅→∅),[10.32240]→[10.38068:38103](∅→∅),[10.38068]→[10.38068:38103](∅→∅),[10.38103]→[10.32241:32282](∅→∅),[10.32282]→[10.2259:2792](∅→∅),[10.2827]→[10.2827:3261](∅→∅),[10.3261]→[10.6798:6854](∅→∅),[10.6854]→[10.3309:3360](∅→∅),[10.3309]→[10.3309:3360](∅→∅),[10.3360]→[10.38177:38234](∅→∅),[10.32282]→[10.38177:38234](∅→∅),[10.38177]→[10.38177:38234](∅→∅),[10.38291]→[10.38291:38627](∅→∅),[10.38662]→[10.38662:38717](∅→∅),[10.38717]→[10.32283:32333](∅→∅),[10.32333]→[10.38812:38847](∅→∅),[10.38812]→[10.38812:38847](∅→∅),[10.38847]→[10.32334:32384](∅→∅),[10.32384]→[10.38942:38977](∅→∅),[10.38942]→[10.38942:38977](∅→∅),[10.38977]→[10.32385:32435](∅→∅),[10.32435]→[10.39072:39142](∅→∅),[10.39072]→[10.39072:39142](∅→∅),[10.39142]→[10.6855:6911](∅→∅),[10.6911]→[10.32436:32546](∅→∅),[10.39190]→[10.32436:32546](∅→∅),[10.32546]→[10.39390:39413](∅→∅),[10.39390]→[10.39390:39413](∅→∅),[10.39413]→[10.32547:32588](∅→∅),[10.32588]→[10.39499:39534](∅→∅),[10.39499]→[10.39499:39534](∅→∅),[10.39534]→[10.32589:32630](∅→∅),[10.32630]→[10.39620:39655](∅→∅),[10.39620]→[10.39620:39655](∅→∅),[10.39655]→[10.32631:32672](∅→∅),[10.32672]→[10.39741:39805](∅→∅),[10.39741]→[10.39741:39805](∅→∅),[10.39869]→[10.39869:40226](∅→∅),[10.40261]→[10.40261:40316](∅→∅),[10.40316]→[10.32673:32723](∅→∅),[10.32723]→[10.40418:40453](∅→∅),[10.40418]→[10.40418:40453](∅→∅),[10.40453]→[10.32724:32774](∅→∅),[10.32774]→[10.40555:40590](∅→∅),[10.40555]→[10.40555:40590](∅→∅),[10.40590]→[10.32775:32873](∅→∅),[10.32873]→[10.40740:40810](∅→∅),[10.40740]→[10.40740:40810](∅→∅),[10.40810]→[10.6912:6968](∅→∅),[10.6968]→[10.32874:33043](∅→∅),[10.40858]→[10.32874:33043](∅→∅),[10.33043]→[10.41183:41206](∅→∅),[10.41183]→[10.41183:41206](∅→∅),[10.41206]→[10.33044:33085](∅→∅),[10.33085]→[10.41299:41334](∅→∅),[10.41299]→[10.41299:41334](∅→∅),[10.41334]→[10.33086:33128](∅→∅),[10.33128]→[10.41428:41463](∅→∅),[10.41428]→[10.41428:41463](∅→∅),[10.41463]→[10.33129:33170](∅→∅),[10.33170]→[10.41556:41648](∅→∅),[10.41556]→[10.41556:41648](∅→∅),[10.41740]→[10.41740:42096](∅→∅),[10.42131]→[10.42131:42186](∅→∅),[10.42186]→[10.33171:33221](∅→∅),[10.33221]→[10.42316:42351](∅→∅),[10.42316]→[10.42316:42351](∅→∅),[10.42351]→[10.33222:33272](∅→∅),[10.33272]→[10.42481:42516](∅→∅),[10.42481]→[10.42481:42516](∅→∅),[10.42516]→[10.33273:33324](∅→∅),[10.33324]→[10.42647:42717](∅→∅),[10.42647]→[10.42647:42717](∅→∅),[10.42717]→[10.6969:7025](∅→∅),[10.7025]→[10.33325:33494](∅→∅),[10.42765]→[10.33325:33494](∅→∅),[10.33494]→[10.43174:43197](∅→∅),[10.43174]→[10.43174:43197](∅→∅),[10.43197]→[10.33495:33536](∅→∅),[10.33536]→[10.43318:43353](∅→∅),[10.43318]→[10.43318:43353](∅→∅),[10.43353]→[10.33537:33579](∅→∅),[10.33579]→[10.43475:43510](∅→∅),[10.43475]→[10.43475:43510](∅→∅),[10.43510]→[10.33580:33620](∅→∅),[10.33620]→[10.43630:43635](∅→∅),[10.43630]→[10.43630:43635](∅→∅),[10.43635]→[10.1614:1688](∅→∅),[10.1767]→[10.43790:44076](∅→∅),[10.43790]→[10.43790:44076](∅→∅),[10.44111]→[10.44111:44166](∅→∅),[10.44166]→[10.33621:33671](∅→∅),[10.1885]→[10.44284:44319](∅→∅),[10.33671]→[10.44284:44319](∅→∅),[10.44284]→[10.44284:44319](∅→∅),[10.44319]→[10.33672:33722](∅→∅),[10.2003]→[10.44437:44472](∅→∅),[10.33722]→[10.44437:44472](∅→∅),[10.44437]→[10.44437:44472](∅→∅),[10.44472]→[10.33723:33774](∅→∅),[10.2122]→[10.44591:44667](∅→∅),[10.33774]→[10.44591:44667](∅→∅),[10.44591]→[10.44591:44667](∅→∅),[10.44667]→[10.7026:7090](∅→∅),[10.7090]→[10.33775:33974](∅→∅),[10.44719]→[10.33775:33974](∅→∅),[10.2523]→[10.45122:45224](∅→∅),[10.33974]→[10.45122:45224](∅→∅),[10.45122]→[10.45122:45224](∅→∅),[10.45224]→[10.7091:7147](∅→∅),[10.7147]→[10.33975:34144](∅→∅),[10.45272]→[10.33975:34144](∅→∅),[10.2894]→[10.45645:45668](∅→∅),[10.34144]→[10.45645:45668](∅→∅),[10.45645]→[10.45645:45668](∅→∅),[10.45668]→[10.34145:34187](∅→∅),[10.3004]→[10.45778:45813](∅→∅),[10.34187]→[10.45778:45813](∅→∅),[10.45778]→[10.45778:45813](∅→∅),[10.45813]→[10.34188:34228](∅→∅),[10.3112]→[10.45921:45956](∅→∅),[10.34228]→[10.45921:45956](∅→∅),[10.45921]→[10.45921:45956](∅→∅),[10.45956]→[10.34229:34270](∅→∅),[10.3221]→[10.46065:46108](∅→∅),[10.34270]→[10.46065:46108](∅→∅),[10.46065]→[10.46065:46108](∅→∅),[10.46151]→[10.46151:46483](∅→∅),[10.46518]→[10.46518:46573](∅→∅),[10.46573]→[10.34271:34321](∅→∅),[10.34321]→[10.46654:46689](∅→∅),[10.46654]→[10.46654:46689](∅→∅),[10.46689]→[10.34322:34372](∅→∅),[10.34372]→[10.46770:46805](∅→∅),[10.46770]→[10.46770:46805](∅→∅),[10.46805]→[10.34373:34423](∅→∅),[10.34423]→[10.46886:46948](∅→∅),[10.46886]→[10.46886:46948](∅→∅),[10.46948]→[10.7148:7200](∅→∅),[10.7200]→[10.34424:34534](∅→∅),[10.46994]→[10.34424:34534](∅→∅),[10.34534]→[10.47166:47218](∅→∅),[10.47166]→[10.47166:47218](∅→∅),[10.47218]→[10.34535:34576](∅→∅),[10.34576]→[10.47290:47325](∅→∅),[10.47290]→[10.47290:47325](∅→∅),[10.47325]→[10.34577:34618](∅→∅),[10.34618]→[10.47397:47432](∅→∅),[10.47397]→[10.47397:47432](∅→∅),[10.47432]→[10.34619:34660](∅→∅),[10.34660]→[10.47504:47509](∅→∅),[10.47504]→[10.47504:47509](∅→∅),[10.47509]→[10.3361:3887](∅→∅),[10.3922]→[10.3922:4352](∅→∅),[10.4352]→[10.7201:7253](∅→∅),[10.7253]→[10.4398:4454](∅→∅),[10.4398]→[10.4398:4454](∅→∅),[10.4454]→[10.47509:47557](∅→∅),[10.47509]→[10.47509:47557](∅→∅),[10.47610]→[10.47610:47931](∅→∅),[10.47966]→[10.47966:48021](∅→∅),[10.48021]→[10.34661:34711](∅→∅),[10.34711]→[10.48112:48147](∅→∅),[10.48112]→[10.48112:48147](∅→∅),[10.48147]→[10.34712:34762](∅→∅),[10.34762]→[10.48238:48273](∅→∅),[10.48238]→[10.48238:48273](∅→∅),[10.48273]→[10.34763:34813](∅→∅),[10.34813]→[10.48364:48430](∅→∅),[10.48364]→[10.48364:48430](∅→∅),[10.48430]→[10.7254:7306](∅→∅),[10.7306]→[10.34814:34924](∅→∅),[10.48476]→[10.34814:34924](∅→∅),[10.34924]→[10.48668:48691](∅→∅),[10.48668]→[10.48668:48691](∅→∅),[10.48691]→[10.34925:34966](∅→∅),[10.34966]→[10.48773:48808](∅→∅),[10.48773]→[10.48773:48808](∅→∅),[10.48808]→[10.34967:35008](∅→∅),[10.35008]→[10.48890:48925](∅→∅),[10.48890]→[10.48890:48925](∅→∅),[10.48925]→[10.35009:35050](∅→∅),[10.35050]→[10.1558:1979](∅→∅),[10.2014]→[10.2014:2365](∅→∅),[10.2365]→[10.7307:7359](∅→∅),[10.7359]→[10.2411:2521](∅→∅),[10.2411]→[10.2411:2521](∅→∅),[10.2521]→[10.49007:49067](∅→∅),[10.35050]→[10.49007:49067](∅→∅),[10.49007]→[10.49007:49067](∅→∅),[10.49127]→[10.49127:49476](∅→∅),[10.49511]→[10.49511:49566](∅→∅),[10.49566]→[10.35051:35101](∅→∅),[10.35101]→[10.49664:49699](∅→∅),[10.49664]→[10.49664:49699](∅→∅),[10.49699]→[10.35102:35152](∅→∅),[10.35152]→[10.49797:49872](∅→∅),[10.49797]→[10.49797:49872](∅→∅),[10.49872]→[10.7360:7412](∅→∅),[10.7412]→[10.49918:49941](∅→∅),[10.49918]→[10.49918:49941](∅→∅),[10.49941]→[10.35153:35195](∅→∅),[10.35195]→[10.50031:50066](∅→∅),[10.50031]→[10.50031:50066](∅→∅),[10.50066]→[10.35196:35237](∅→∅),[10.35237]→[10.50155:50190](∅→∅),[10.50155]→[10.50155:50190](∅→∅),[10.50190]→[10.35238:35515](∅→∅),[10.35515]→[10.50698:50760](∅→∅),[10.50698]→[10.50698:50760](∅→∅),[10.50822]→[10.50822:51160](∅→∅),[10.51195]→[10.51195:51250](∅→∅),[10.51250]→[10.35516:35566](∅→∅),[10.35566]→[10.51350:51385](∅→∅),[10.51350]→[10.51350:51385](∅→∅),[10.51385]→[10.35567:35617](∅→∅),[10.35617]→[10.51485:51520](∅→∅),[10.51485]→[10.51485:51520](∅→∅),[10.51520]→[10.35618:35668](∅→∅),[10.35668]→[10.51620:51712](∅→∅),[10.51620]→[10.51620:51712](∅→∅),[10.51712]→[10.7413:7465](∅→∅),[10.7465]→[10.51758:51781](∅→∅),[10.51758]→[10.51758:51781](∅→∅),[10.51781]→[10.35669:35710](∅→∅),[10.35710]→[10.51872:51907](∅→∅),[10.51872]→[10.51872:51907](∅→∅),[10.51907]→[10.35711:35752](∅→∅),[10.35752]→[10.51998:52033](∅→∅),[10.51998]→[10.51998:52033](∅→∅),[10.52033]→[10.35753:36030](∅→∅),[10.36030]→[10.52551:52606](∅→∅),[10.52551]→[10.52551:52606](∅→∅),[10.52661]→[10.52661:53016](∅→∅),[10.53051]→[10.53051:53106](∅→∅),[10.53106]→[10.36031:36081](∅→∅),[10.36081]→[10.53199:53234](∅→∅),[10.53199]→[10.53199:53234](∅→∅),[10.53234]→[10.36082:36132](∅→∅),[10.36132]→[10.53327:53362](∅→∅),[10.53327]→[10.53327:53362](∅→∅),[10.53362]→[10.36133:36183](∅→∅),[10.36183]→[10.53455:53521](∅→∅),[10.53455]→[10.53455:53521](∅→∅),[10.53521]→[10.7466:7518](∅→∅),[10.7518]→[10.36184:36294](∅→∅),[10.53567]→[10.36184:36294](∅→∅),[10.36294]→[10.53763:53843](∅→∅),[10.53763]→[10.53763:53843](∅→∅),[10.53843]→[10.36295:36336](∅→∅),[10.36336]→[10.53927:53962](∅→∅),[10.53927]→[10.53927:53962](∅→∅),[10.53962]→[10.36337:36378](∅→∅),[10.36378]→[10.54046:54074](∅→∅),[10.54046]→[10.54046:54074](∅→∅),[10.54102]→[10.54102:54361](∅→∅),[10.54396]→[10.54396:54499](∅→∅),[10.54499]→[10.36379:36429](∅→∅),[10.36429]→[10.54565:54600](∅→∅),[10.54565]→[10.54565:54600](∅→∅),[10.54600]→[10.36430:36480](∅→∅),[10.36480]→[10.54666:54714](∅→∅),[10.54666]→[10.54666:54714](∅→∅),[10.54714]→[10.7519:7579](∅→∅),[10.7579]→[10.36481:36591](∅→∅),[10.54764]→[10.36481:36591](∅→∅),[10.36591]→[10.54906:54929](∅→∅),[10.54906]→[10.54906:54929](∅→∅),[10.54929]→[10.36592:36633](∅→∅),[10.36633]→[10.54986:55021](∅→∅),[10.54986]→[10.54986:55021](∅→∅),[10.55021]→[10.36634:36675](∅→∅),[10.36675]→[10.55078:55132](∅→∅),[10.55078]→[10.55078:55132](∅→∅),[10.55186]→[10.55186:55543](∅→∅),[10.55578]→[10.55578:55633](∅→∅),[10.55633]→[10.36676:36726](∅→∅),[10.36726]→[10.55725:55760](∅→∅),[10.55725]→[10.55725:55760](∅→∅),[10.55760]→[10.36727:36777](∅→∅),[10.36777]→[10.55852:55887](∅→∅),[10.55852]→[10.55852:55887](∅→∅),[10.55887]→[10.36778:36875](∅→∅),[10.36875]→[10.56026:56090](∅→∅),[10.56026]→[10.56026:56090](∅→∅),[10.56090]→[10.7580:7640](∅→∅),[10.7640]→[10.36876:37045](∅→∅),[10.56140]→[10.36876:37045](∅→∅),[10.37045]→[10.56435:56458](∅→∅),[10.56435]→[10.56435:56458](∅→∅),[10.56458]→[10.37046:37088](∅→∅),[10.37088]→[10.56542:56577](∅→∅),[10.56542]→[10.56542:56577](∅→∅),[10.56577]→[10.37089:37130](∅→∅),[10.37130]→[10.56660:56695](∅→∅),[10.56660]→[10.56660:56695](∅→∅),[10.56695]→[10.37131:37172](∅→∅),[10.37172]→[10.56778:56841](∅→∅),[10.56778]→[10.56778:56841](∅→∅),[10.56904]→[10.56904:57279](∅→∅),[10.57314]→[10.57314:57369](∅→∅),[10.57369]→[10.37173:37223](∅→∅),[10.37223]→[10.57470:57505](∅→∅),[10.57470]→[10.57470:57505](∅→∅),[10.57505]→[10.37224:37321](∅→∅),[10.37321]→[10.57653:57717](∅→∅),[10.57653]→[10.57653:57717](∅→∅),[10.57717]→[10.7641:7701](∅→∅),[10.7701]→[10.37322:37491](∅→∅),[10.57767]→[10.37322:37491](∅→∅),[10.37491]→[10.58089:58112](∅→∅),[10.58089]→[10.58089:58112](∅→∅),[10.58112]→[10.37492:37534](∅→∅),[10.37534]→[10.58205:58240](∅→∅),[10.58205]→[10.58205:58240](∅→∅),[10.58240]→[10.37535:37576](∅→∅),[10.37576]→[10.58332:58367](∅→∅),[10.58332]→[10.58332:58367](∅→∅),[10.58367]→[10.37577:37619](∅→∅),[10.37619]→[10.58460:58515](∅→∅),[10.58460]→[10.58460:58515](∅→∅),[10.58570]→[10.58570:58905](∅→∅),[10.58940]→[10.58940:58995](∅→∅),[10.58995]→[10.37620:37670](∅→∅),[10.37670]→[10.59088:59123](∅→∅),[10.59088]→[10.59088:59123](∅→∅),[10.59123]→[10.37671:37721](∅→∅),[10.37721]→[10.59216:59251](∅→∅),[10.59216]→[10.59216:59251](∅→∅),[10.59251]→[10.37722:37772](∅→∅),[10.37772]→[10.59344:59401](∅→∅),[10.59344]→[10.59344:59401](∅→∅),[10.59401]→[10.7702:7762](∅→∅),[10.7762]→[10.37773:37942](∅→∅),[10.59451]→[10.37773:37942](∅→∅),[10.37942]→[10.59749:59772](∅→∅),[10.59749]→[10.59749:59772](∅→∅),[10.59772]→[10.37943:37984](∅→∅),[10.37984]→[10.59856:59891](∅→∅),[10.59856]→[10.59856:59891](∅→∅),[10.59891]→[10.37985:38024](∅→∅),[10.38024]→[10.59973:60008](∅→∅),[10.59973]→[10.59973:60008](∅→∅),[10.60008]→[10.38025:38065](∅→∅),[10.38065]→[10.60091:60173](∅→∅),[10.60091]→[10.60091:60173](∅→∅),[10.60255]→[10.60255:60582](∅→∅),[10.60617]→[10.60617:60672](∅→∅),[10.60672]→[10.38066:38116](∅→∅),[10.38116]→[10.60792:60857](∅→∅),[10.60792]→[10.60792:60857](∅→∅),[10.60857]→[10.7763:7823](∅→∅),[10.7823]→[10.38117:38286](∅→∅),[10.60907]→[10.38117:38286](∅→∅),[10.38286]→[10.61286:61309](∅→∅),[10.61286]→[10.61286:61309](∅→∅),[10.61309]→[10.38287:38326](∅→∅),[10.38326]→[10.61418:61453](∅→∅),[10.61418]→[10.61418:61453](∅→∅),[10.61453]→[10.38327:38367](∅→∅),[10.38367]→[10.61563:61654](∅→∅),[10.61563]→[10.61563:61654](∅→∅),[10.61745]→[10.61745:62060](∅→∅),[10.62095]→[10.62095:62195](∅→∅),[10.62195]→[10.1546:1593](∅→∅),[10.1593]→[10.38368:38537](∅→∅),[10.38537]→[10.62647:62676](∅→∅),[10.62647]→[10.62647:62676](∅→∅),[10.62676]→[10.38538:38577](∅→∅),[10.38577]→[10.62794:62850](∅→∅),[10.62794]→[10.62794:62850](∅→∅),[10.62906]→[10.62906:63241](∅→∅),[10.63276]→[10.63276:63331](∅→∅),[10.63331]→[10.38578:38628](∅→∅),[10.38628]→[10.63425:63460](∅→∅),[10.63425]→[10.63425:63460](∅→∅),[10.63460]→[10.38629:38679](∅→∅),[10.38679]→[10.63554:63589](∅→∅),[10.63554]→[10.63554:63589](∅→∅),[10.63589]→[10.38680:38730](∅→∅),[10.38730]→[10.63683:63754](∅→∅),[10.63683]→[10.63683:63754](∅→∅),[10.63754]→[10.1594:1735](∅→∅),[10.1735]→[10.38731:38900](∅→∅),[10.38900]→[10.64193:64216](∅→∅),[10.64193]→[10.64193:64216](∅→∅),[10.64216]→[10.38901:38942](∅→∅),[10.38942]→[10.64301:64336](∅→∅),[10.64301]→[10.64301:64336](∅→∅),[10.64336]→[10.38943:38985](∅→∅),[10.38985]→[10.64422:64457](∅→∅),[10.64422]→[10.64422:64457](∅→∅),[10.64457]→[10.38986:39026](∅→∅),[10.39026]→[10.64541:64545](∅→∅),[10.64541]→[10.64541:64545](∅→∅)
-- Arguably this should be called source_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')endfunction test_click_to_create_drawing()App.screen.init{width=800, height=600}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 textcheck_eq(#Editor_state.lines, 2, '#lines')check_eq(Editor_state.cursor1.line, 2, 'cursor')endfunction 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 drawingedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(#Editor_state.lines, 1, '#lines')check_eq(Editor_state.cursor1.line, 1, 'cursor')endfunction test_backspace_from_start_of_final_line()-- display final line of text with cursor at start of itApp.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 upedit.run_after_keychord(Editor_state, 'backspace', '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')endfunction 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.topApp.screen.check(y, 'a', 'screen:1')endfunction test_press_ctrl()-- press ctrl while the cursor is on textApp.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}edit.run_after_keychord(Editor_state, 'C-m', 'm')endfunction 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', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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', 'right')check_eq(Editor_state.cursor1.pos, 2, 'check')endfunction 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', 'left')check_eq(Editor_state.cursor1.line, 1, 'line')check_eq(Editor_state.cursor1.pos, 4, 'pos') -- past end of lineendfunction 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 lineedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'right', 'right')check_eq(Editor_state.cursor1.line, 2, 'line')check_eq(Editor_state.cursor1.pos, 1, 'pos')endfunction 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', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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 wordsedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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 wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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 wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left', 'left')check_eq(Editor_state.cursor1.pos, 9, 'check')endfunction 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 wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left', 'left')check_eq(Editor_state.cursor1.pos, 1, 'check')endfunction 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', 'left')check_eq(Editor_state.cursor1.line, 1, 'line')check_eq(Editor_state.cursor1.pos, 5, 'pos')endfunction 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', 'right')check_eq(Editor_state.cursor1.pos, 4, 'check')endfunction 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 wordsedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right', 'right')check_eq(Editor_state.cursor1.pos, 8, 'check')endfunction 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 wordsedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right', 'right')check_eq(Editor_state.cursor1.pos, 4, 'check')endfunction 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 wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right', 'right')check_eq(Editor_state.cursor1.pos, 9, 'check')endfunction 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', 'right')check_eq(Editor_state.cursor1.line, 2, 'line')check_eq(Editor_state.cursor1.pos, 4, 'pos')endfunction 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.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each lineedit.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 editscheck_nil(Editor_state.selection1.line, 'selection:line')check_nil(Editor_state.selection1.pos, 'selection:pos')endfunction test_click_to_left_of_line()-- display a line with the cursor in the middleApp.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.selection1 = {}-- click to the left of the lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1)-- cursor moves to start of linecheck_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')endfunction test_click_takes_margins_into_account()-- display two lines with cursor on one of themApp.screen.init{width=100, height=80}Editor_state = edit.initialize_test_state()Editor_state.left = 50 -- occupy only right side of screenEditor_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.selection1 = {}-- click on the other lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_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')endfunction test_click_on_empty_line()-- display two lines with the first one emptyApp.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.selection1 = {}-- click on the empty lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_eq(Editor_state.cursor1.line, 1, 'cursor')-- selection remains emptycheck_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')endfunction test_click_below_final_line_of_file()-- display one lineApp.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.selection1 = {}-- click below first lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+50, 1)-- cursor goes to bottomcheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')-- selection remains emptycheck_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')endfunction 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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction 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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'de', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'fgh', 'screen:3')endfunction 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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def ', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_click_on_wrapping_line()-- display two screen lines with cursor on one of themApp.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}-- click on the other lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_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')endfunction test_click_on_wrapping_line_takes_margins_into_account()-- display two screen lines with cursor on one of themApp.screen.init{width=100, height=80}Editor_state = edit.initialize_test_state()Editor_state.left = 50 -- occupy only right side of screenEditor_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}-- click on the other lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_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')endfunction test_draw_text_wrapping_within_word()-- arrange a screen line that needs to be split within a wordApp.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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abcd ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'e fgh', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ijk', 'screen:3')endfunction test_draw_wrapping_text_containing_non_ascii()-- draw a long line containing non-ASCIIApp.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 apostropheText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'mad', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'am I', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, '’m a', 'screen:3')endfunction test_click_past_end_of_screen_line()-- display a wrapping lineApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'madam ', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, "I'm ad", 'baseline/screen:2')y = y + Editor_state.line_height-- click past end of second screen lineedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of screen line (one more than final character shown)check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 13, 'cursor:pos')endfunction test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()-- display a wrapping line from its second screen lineApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, "I'm ad", 'baseline/screen:2')y = y + Editor_state.line_height-- click past end of second screen lineedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of screen line (one more than final character shown)check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 13, 'cursor:pos')endfunction test_click_past_end_of_wrapping_line()-- display a wrapping lineApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_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}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'madam ', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, "I'm ad", 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'am', 'baseline/screen:3')y = y + Editor_state.line_height-- click past the end of itedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of linecheck_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-pointsendfunction test_click_past_end_of_wrapping_line_containing_non_ascii()-- display a wrapping line containing non-ASCIIApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_state.lines = load_array{'madam I’m adam'} -- notice the non-ASCII apostropheText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'madam ', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'I’m ad', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'am', 'baseline/screen:3')y = y + Editor_state.line_height-- click past the end of itedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of linecheck_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-pointsendfunction test_click_past_end_of_word_wrapping_line()-- display a long line wrapping at a word boundary on a screen of more realistic lengthApp.screen.init{width=160, height=80}Editor_state = edit.initialize_test_state()-- 0 1 2-- 123456789012345678901Editor_state.lines = load_array{'the quick brown fox jumped over the lazy dog'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')y = y + Editor_state.line_height-- click past the end of the screen lineedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of screen line (one more than final character shown)check_eq(Editor_state.cursor1.pos, 21, 'cursor')endfunction test_select_text()-- display a line of textApp.screen.init{width=75, height=80}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}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- select a letterApp.fake_key_press('lshift')edit.run_after_keychord(Editor_state, 'S-right', 'right')App.fake_key_release('lshift')edit.key_release(Editor_state, 'lshift')-- selection persists even after shift is releasedcheck_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 1, 'selection:pos')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')endfunction test_cursor_movement_without_shift_resets_selection()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- press an arrow key without shiftedit.run_after_keychord(Editor_state, 'right', 'right')-- no change to data, selection is resetcheck_nil(Editor_state.selection1.line, 'check')check_eq(Editor_state.lines[1].data, 'abc', 'data')endfunction test_edit_deletes_selection()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- press a keyedit.run_after_text_input(Editor_state, 'x')-- selected text is deleted and replaced with the keycheck_eq(Editor_state.lines[1].data, 'xbc', 'check')endfunction test_edit_with_shift_key_deletes_selection()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- mimic precise keypresses for a capital letterApp.fake_key_press('lshift')edit.keychord_press(Editor_state, 'd', 'd')edit.text_input(Editor_state, 'D')edit.key_release(Editor_state, 'd')App.fake_key_release('lshift')-- selected text is deleted and replaced with the keycheck_nil(Editor_state.selection1.line, 'check')check_eq(Editor_state.lines[1].data, 'Dbc', 'data')endfunction test_copy_does_not_reset_selection()-- display a line of text with a selectionApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- copy selectionedit.run_after_keychord(Editor_state, 'C-c', 'c')check_eq(App.clipboard, 'a', 'clipboard')-- selection is reset since shift key is not pressedcheck(Editor_state.selection1.line, 'check')endfunction test_cut()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- press a keyedit.run_after_keychord(Editor_state, 'C-x', 'x')check_eq(App.clipboard, 'a', 'clipboard')-- selected text is deletedcheck_eq(Editor_state.lines[1].data, 'bc', 'data')endfunction test_paste_replaces_selection()-- display a line of text with a selectionApp.screen.init{width=75, height=80}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}Editor_state.selection1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- set clipboardApp.clipboard = 'xyz'-- paste selectionedit.run_after_keychord(Editor_state, 'C-v', 'v')-- selection is reset since shift key is not pressed-- selection includes the newline, so it's also deletedcheck_eq(Editor_state.lines[1].data, 'xyzdef', 'check')endfunction test_deleting_selection_may_scroll()-- display lines 2/3/4App.screen.init{width=120, 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=3, pos=2}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- set up a selection starting above the currently displayed pageEditor_state.selection1 = {line=1, pos=2}-- delete selectionedit.run_after_keychord(Editor_state, 'backspace', 'backspace')-- page scrolls upcheck_eq(Editor_state.screen_top1.line, 1, 'check')check_eq(Editor_state.lines[1].data, 'ahi', 'data')endfunction test_edit_wrapping_text()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=2, pos=4}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)edit.run_after_text_input(Editor_state, 'g')local y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'de', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'fg', 'screen:3')endfunction test_insert_newline()-- display a few linesApp.screen.init{width=Editor_state.left+30, 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=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- hitting the enter key splits the lineedit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'a', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'bc', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:3')endfunction test_insert_newline_at_start_of_line()-- display a lineApp.screen.init{width=Editor_state.left+30, 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=1}Editor_state.screen_top1 = {line=1, pos=1}-- hitting the enter key splits the lineedit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')check_eq(Editor_state.lines[1].data, '', 'data:1')check_eq(Editor_state.lines[2].data, 'abc', 'data:2')endfunction test_insert_from_clipboard()-- display a few linesApp.screen.init{width=Editor_state.left+30, 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=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- paste some text including a newline, check that new line is createdApp.clipboard = 'xy\nz'edit.run_after_keychord(Editor_state, 'C-v', 'v')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'axy', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'zbc', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:3')endfunction test_select_text_using_mouse()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.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each line-- press and hold on first locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- drag and release somewhere elseedit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 2, 'selection:pos')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')endfunction test_select_text_using_mouse_starting_above_text()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.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each line-- press mouse above first line of textedit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 1, 'selection:pos')endfunction test_select_text_using_mouse_starting_above_text_wrapping_line()-- first screen line starts in the middle of a lineApp.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=2, pos=5}Editor_state.screen_top1 = {line=2, pos=3}-- press mouse above first line of textedit.draw(Editor_state)edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)-- selection is at screen topcheck(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')check_eq(Editor_state.selection1.line, 2, 'selection:line')check_eq(Editor_state.selection1.pos, 3, 'selection:pos')endfunction test_select_text_using_mouse_starting_below_text()-- I'd like to test what happens when a mouse click is below some page of-- text, potentially even in the middle of a line.-- However, it's brittle to set up a text line boundary just right.-- So I'm going to just check things below the bottom of the final line of-- text when it's in the middle of the screen.-- final screen line ends in the middle of screenApp.screen.init{width=50, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abcde'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'ab', 'baseline:screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'cde', 'baseline:screen:2')-- press mouse above first line of textedit.run_after_mouse_press(Editor_state, 5,App.screen.height-5, 1)-- selection is past bottom-most text in screencheck(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 6, 'selection:pos')endfunction test_select_text_using_mouse_and_shift()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.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each line-- click on first locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- hold down shift and click somewhere elseApp.fake_key_press('lshift')edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)App.fake_key_release('lshift')check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 2, 'selection:pos')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')endfunction test_select_text_repeatedly_using_mouse_and_shift()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)Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.selection1 = {}edit.draw(Editor_state) -- populate line_cache.startpos for each line-- click on first locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- hold down shift and click on a second locationApp.fake_key_press('lshift')edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)-- hold down shift and click at a third locationApp.fake_key_press('lshift')edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height+5, 1)App.fake_key_release('lshift')-- selection is between first and third location. forget the second location, not the first.check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 2, 'selection:pos')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')endfunction test_select_all_text()-- display a single line of textApp.screen.init{width=75, height=80}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}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- select allApp.fake_key_press('lctrl')edit.run_after_keychord(Editor_state, 'C-a', 'a')App.fake_key_release('lctrl')edit.key_release(Editor_state, 'lctrl')-- selectioncheck_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 1, 'selection:pos')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 8, 'cursor:pos')endfunction test_cut_without_selection()-- display a few linesApp.screen.init{width=Editor_state.left+30, 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=2}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.selection1 = {}edit.draw(Editor_state)-- try to cut without selecting textedit.run_after_keychord(Editor_state, 'C-x', 'x')-- no crashcheck_nil(Editor_state.selection1.line, 'check')endfunction test_pagedown()App.screen.init{width=120, height=45}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}-- initially the first two lines are displayededit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')-- after pagedown the bottom line becomes the topedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:2')endfunction test_pagedown_skips_drawings()-- some lines of text with a drawing intermixedlocal drawing_width = 50App.screen.init{width=Editor_state.left+drawing_width, height=80}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', -- height 15'```lines', '```', -- height 25'def', -- height 15'ghi'} -- height 15Text.redraw_all(Editor_state)check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines')Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}local drawing_height = Drawing_padding_height + drawing_width/2 -- default-- initially the screen displays the first line and the drawing-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80pxedit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')-- after pagedown the screen draws the drawing up top-- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80pxedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor')y = Editor_state.top + drawing_heightApp.screen.check(y, 'def', 'screen:1')endfunction test_pagedown_can_start_from_middle_of_long_wrapping_line()-- draw a few lines starting from a very long wrapping lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu vwx yza bcd efg hij', 'XYZ'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc ', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def ', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'baseline/screen:3')-- after pagedown we scroll down the very long wrapping lineedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 9, 'screen_top:pos')y = Editor_state.topApp.screen.check(y, 'ghi ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl ', 'screen:2')y = y + Editor_state.line_heightif Version == '12.0' then-- HACK: Maybe v12.0 uses a different font? Strange that it only causes-- issues in a couple of places.-- We'll need to rethink our tests if issues like this start to multiply.App.screen.check(y, 'mno ', 'screen:3')elseApp.screen.check(y, 'mn', 'screen:3')endendfunction test_pagedown_never_moves_up()-- draw the final screen line of a wrapping lineApp.screen.init{width=Editor_state.left+30, 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=9}Editor_state.screen_top1 = {line=1, pos=9}edit.draw(Editor_state)-- pagedown makes no changeedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 9, 'screen_top:pos')endfunction test_down_arrow_moves_cursor()App.screen.init{width=120, 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}-- initially the first three lines are displayededit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the down arrow, the cursor moves down by 1 lineedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor')-- the screen is unchangedy = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_down_arrow_skips_drawing()-- some lines of text with a drawing intermixedlocal drawing_width = 50App.screen.init{width=Editor_state.left+drawing_width, height=100}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', -- height 15'```lines', '```', -- height 25'ghi'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightlocal drawing_height = Drawing_padding_height + drawing_width/2 -- defaulty = y + drawing_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')check(Editor_state.cursor_x, 'baseline/cursor_x')-- after hitting the down arrow the cursor moves down by 2 lines, skipping the drawingedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.cursor1.line, 3, 'cursor')endfunction test_down_arrow_scrolls_down_by_one_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=120, 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=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the down arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 4, 'cursor')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')endfunction test_down_arrow_scrolls_down_by_one_screen_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the down arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')endfunction test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghijkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghij', 'baseline/screen:3')-- after hitting the down arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghij', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:3')endfunction test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()App.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghijkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghij', 'baseline/screen:3')-- after hitting pagedown the screen scrolls down to start of a long lineedit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 3, 'baseline2/screen_top')check_eq(Editor_state.cursor1.line, 3, 'baseline2/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'baseline2/cursor:pos')-- after hitting down arrow the screen doesn't scroll down further, and certainly doesn't scroll upedit.run_after_keychord(Editor_state, 'down', 'down')check_eq(Editor_state.screen_top1.line, 3, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'ghij', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')endfunction test_up_arrow_moves_cursor()-- display the first 3 lines with the cursor on the bottom lineApp.screen.init{width=120, 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=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the up arrow the cursor moves up by 1 lineedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor')-- the screen is unchangedy = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_up_arrow_skips_drawing()-- some lines of text with a drawing intermixedlocal drawing_width = 50App.screen.init{width=Editor_state.left+drawing_width, height=100}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', -- height 15'```lines', '```', -- height 25'ghi'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightlocal drawing_height = Drawing_padding_height + drawing_width/2 -- defaulty = y + drawing_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')check(Editor_state.cursor_x, 'baseline/cursor_x')-- after hitting the up arrow the cursor moves up by 2 lines, skipping the drawingedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.cursor1.line, 1, 'cursor')endfunction test_up_arrow_scrolls_up_by_one_line()-- display the lines 2/3/4 with the cursor on line 2App.screen.init{width=120, 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=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up by one lineedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_up_arrow_scrolls_up_by_one_line_skipping_drawing()-- display lines 3/4/5 with a drawing just off screen at line 2App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', '```lines', '```', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=3, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up to previous text lineedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')endfunction test_up_arrow_scrolls_up_by_one_screen_line()-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=6}Editor_state.screen_top1 = {line=3, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:2')-- after hitting the up arrow the screen scrolls up to first screen lineedit.run_after_keychord(Editor_state, 'up', 'up')y = Editor_state.topApp.screen.check(y, 'ghi ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')endfunction test_up_arrow_scrolls_up_to_final_screen_line()-- display lines starting just after a long lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'ghi', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up to final screen line of previous lineedit.run_after_keychord(Editor_state, 'up', 'up')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 5, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')endfunction test_up_arrow_scrolls_up_to_empty_line()-- display a screenful of text with an empty line just above it outside the screenApp.screen.init{width=120, 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=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up by one lineedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')y = Editor_state.top-- empty first liney = y + Editor_state.line_heightApp.screen.check(y, 'abc', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:3')endfunction test_pageup()App.screen.init{width=120, height=45}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}Editor_state.screen_top1 = {line=2, pos=1}-- initially the last two lines are displayededit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')-- after pageup the cursor goes to first lineedit.run_after_keychord(Editor_state, 'pageup', 'pageup')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')endfunction test_pageup_scrolls_up_by_screen_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'ghi', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the page-up key the screen scrolls up to topedit.run_after_keychord(Editor_state, 'pageup', 'pageup')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'abc ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:3')endfunction test_pageup_scrolls_up_from_middle_screen_line()-- display a few lines starting from the middle of a line (Editor_state.cursor1.pos > 1)App.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=5}Editor_state.screen_top1 = {line=2, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the page-up key the screen scrolls up to topedit.run_after_keychord(Editor_state, 'pageup', 'pageup')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'abc ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'screen:3')endfunction test_enter_on_bottom_line_scrolls_down()-- display a few lines with cursor on bottom lineApp.screen.init{width=Editor_state.left+30, 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=3, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after hitting the enter key the screen scrolls downedit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 4, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'g', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'hi', 'screen:3')endfunction test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()-- display just the bottom line on screenApp.screen.init{width=Editor_state.left+30, 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=4, pos=2}Editor_state.screen_top1 = {line=4, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'baseline/screen:1')-- after hitting the enter key the screen does not scroll downedit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.screen_top1.line, 4, 'screen_top')check_eq(Editor_state.cursor1.line, 5, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'j', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:2')endfunction test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom()-- display just an empty bottom line on screenApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', ''}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)-- after hitting the inserting_text key the screen does not scroll downedit.run_after_text_input(Editor_state, 'a')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')local y = Editor_state.topApp.screen.check(y, 'a', 'screen:1')endfunction test_typing_on_bottom_line_scrolls_down()-- display a few lines with cursor on bottom lineApp.screen.init{width=Editor_state.left+30, 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=3, pos=4}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:3')-- after typing something the line wraps and the screen scrolls downedit.run_after_text_input(Editor_state, 'j')edit.run_after_text_input(Editor_state, 'k')edit.run_after_text_input(Editor_state, 'l')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 7, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghij', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:3')end---- I'm checking the precise state of the screen in this file, an inherently-- brittle approach that depends on details of the font and text shaping-- algorithms used by a particular release of LÖVE.---- (This brittleness is one reason lines2 and its forks have no tests.)---- To manage the brittleness, there'll be one version of this file for each-- distinct LÖVE version that introduces font changes. - replacement in source_text_tests.lua at line 12[10.64546]→[10.64546:64600](∅→∅),[10.64659]→[10.64659:64967](∅→∅),[10.65002]→[10.65002:65130](∅→∅),[10.65130]→[10.39027:39077](∅→∅),[10.39077]→[10.65227:65262](∅→∅),[10.65227]→[10.65227:65262](∅→∅),[10.65262]→[10.39078:39128](∅→∅),[10.39128]→[10.65359:65436](∅→∅),[10.65359]→[10.65359:65436](∅→∅),[10.65436]→[10.7824:7880](∅→∅),[10.7880]→[10.65484:65507](∅→∅),[10.65484]→[10.65484:65507](∅→∅),[10.65507]→[10.39129:39171](∅→∅),[10.39171]→[10.65596:65631](∅→∅),[10.65596]→[10.65596:65631](∅→∅),[10.65631]→[10.39172:39213](∅→∅),[10.39213]→[10.65719:65754](∅→∅),[10.65719]→[10.65719:65754](∅→∅),[10.65754]→[10.39214:39491](∅→∅)
function test_left_arrow_scrolls_up_in_wrapped_line()-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=3, pos=5}-- cursor is at top of screenEditor_state.cursor1 = {line=3, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:2')-- after hitting the left arrow the screen scrolls up to first screen lineedit.run_after_keychord(Editor_state, 'left', 'left')y = Editor_state.topApp.screen.check(y, 'ghi ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')Version, Major_version = App.love_version()if Major_version == 11 thenload_file_from_source_or_save_directory('source_text_tests_love11.lua')elseif Major_version == 12 then-- not released/stable yetload_file_from_source_or_save_directory('source_text_tests_love12.lua') - edit in source_text_tests.lua at line 19[10.66261]→[10.66261:66319](∅→∅),[10.66381]→[10.66381:66697](∅→∅),[10.66732]→[10.66732:66869](∅→∅),[10.66869]→[10.39492:39542](∅→∅),[10.39542]→[10.66969:67004](∅→∅),[10.66969]→[10.66969:67004](∅→∅),[10.67004]→[10.39543:39593](∅→∅),[10.39593]→[10.67104:67139](∅→∅),[10.67104]→[10.67104:67139](∅→∅),[10.67139]→[10.39594:39692](∅→∅),[10.39692]→[10.67287:67358](∅→∅),[10.67287]→[10.67287:67358](∅→∅),[10.67358]→[7.249:307](∅→∅),[7.307]→[10.39693:39862](∅→∅),[10.7940]→[10.39693:39862](∅→∅),[10.67407]→[10.39693:39862](∅→∅),[10.39862]→[10.67726:67749](∅→∅),[10.67726]→[10.67726:67749](∅→∅),[10.67749]→[10.39863:39904](∅→∅),[10.39904]→[10.67840:67875](∅→∅),[10.67840]→[10.67840:67875](∅→∅),[10.67875]→[10.39905:39947](∅→∅),[10.39947]→[10.67967:68002](∅→∅),[10.67967]→[10.67967:68002](∅→∅),[10.68002]→[10.39948:39989](∅→∅),[10.39989]→[10.68093:68146](∅→∅),[10.68093]→[10.68093:68146](∅→∅),[10.68199]→[10.68199:68507](∅→∅),[10.68542]→[10.68542:68670](∅→∅),[10.68670]→[10.39990:40040](∅→∅),[10.40040]→[10.68761:68796](∅→∅),[10.68761]→[10.68761:68796](∅→∅),[10.68796]→[10.40041:40091](∅→∅),[10.40091]→[10.68887:68954](∅→∅),[10.68887]→[10.68887:68954](∅→∅),[10.68954]→[10.7941:7997](∅→∅),[10.7997]→[10.69002:69025](∅→∅),[10.69002]→[10.69002:69025](∅→∅),[10.69025]→[10.40092:40134](∅→∅),[10.40134]→[10.69108:69143](∅→∅),[10.69108]→[10.69108:69143](∅→∅),[10.69143]→[10.40135:40176](∅→∅),[10.40176]→[10.69225:69260](∅→∅),[10.69225]→[10.69225:69260](∅→∅),[10.69260]→[10.40177:40454](∅→∅),[10.40454]→[10.69733:69787](∅→∅),[10.69733]→[10.69733:69787](∅→∅),[10.69841]→[10.69841:70157](∅→∅),[10.70192]→[10.70192:70329](∅→∅),[10.70329]→[10.40455:40505](∅→∅),[10.40505]→[10.70421:70456](∅→∅),[10.70421]→[10.70421:70456](∅→∅),[10.70456]→[10.40506:40556](∅→∅),[10.40556]→[10.70548:70583](∅→∅),[10.70548]→[10.70548:70583](∅→∅),[10.70583]→[10.40557:40655](∅→∅),[10.40655]→[10.70723:70782](∅→∅),[10.70723]→[10.70723:70782](∅→∅),[10.70782]→[10.7998:8052](∅→∅),[10.8052]→[10.40656:40825](∅→∅),[10.70829]→[10.40656:40825](∅→∅),[10.40825]→[10.71124:71147](∅→∅),[10.71124]→[10.71124:71147](∅→∅),[10.71147]→[10.40826:40867](∅→∅),[10.40867]→[10.71230:71265](∅→∅),[10.71230]→[10.71230:71265](∅→∅),[10.71265]→[10.40868:40910](∅→∅),[10.40910]→[10.71349:71384](∅→∅),[10.71349]→[10.71349:71384](∅→∅),[10.71384]→[10.40911:40952](∅→∅),[10.40952]→[10.71467:71583](∅→∅),[10.71467]→[10.71467:71583](∅→∅),[10.71653]→[10.71653:71928](∅→∅),[10.71963]→[10.71963:72018](∅→∅),[10.72018]→[10.40953:41013](∅→∅),[10.41013]→[10.72136:72171](∅→∅),[10.72136]→[10.72136:72171](∅→∅),[10.72171]→[10.41014:41074](∅→∅),[10.41074]→[10.72289:72324](∅→∅),[10.72289]→[10.72289:72324](∅→∅),[10.72324]→[10.41075:41126](∅→∅),[10.41126]→[10.72433:72494](∅→∅),[10.72433]→[10.72433:72494](∅→∅),[10.72494]→[10.1736:1877](∅→∅),[10.1877]→[10.41127:41182](∅→∅),[10.41182]→[10.72745:72768](∅→∅),[10.72745]→[10.72745:72768](∅→∅),[10.72768]→[10.41183:41243](∅→∅),[10.41243]→[10.72886:72921](∅→∅),[10.72886]→[10.72886:72921](∅→∅),[10.72921]→[10.41244:41304](∅→∅),[10.41304]→[10.73039:73074](∅→∅),[10.73039]→[10.73039:73074](∅→∅),[10.73074]→[10.41305:41356](∅→∅),[10.41356]→[10.73183:73266](∅→∅),[10.73183]→[10.73183:73266](∅→∅),[10.73266]→[3.817:932](∅→∅),[3.932]→[10.73379:73403](∅→∅),[10.13324]→[10.73379:73403](∅→∅),[10.73379]→[10.73379:73403](∅→∅),[10.73403]→[10.41357:41413](∅→∅),[10.41413]→[3.933:988](∅→∅),[3.988]→[10.73630:73675](∅→∅),[10.41468]→[10.73630:73675](∅→∅),[10.73630]→[10.73630:73675](∅→∅),[10.73720]→[10.73720:74041](∅→∅),[10.74076]→[10.74076:74131](∅→∅),[10.74131]→[10.41469:41519](∅→∅),[10.41519]→[10.74214:74249](∅→∅),[10.74214]→[10.74214:74249](∅→∅),[10.74249]→[10.41520:41570](∅→∅),[10.41570]→[10.74332:74367](∅→∅),[10.74332]→[10.74332:74367](∅→∅),[10.74367]→[10.41571:41621](∅→∅),[10.41621]→[10.74450:74513](∅→∅),[10.74450]→[10.74450:74513](∅→∅),[10.74513]→[10.8053:8119](∅→∅),[10.8119]→[10.41622:41732](∅→∅),[10.74566]→[10.41622:41732](∅→∅),[10.41732]→[10.74742:74765](∅→∅),[10.74742]→[10.74742:74765](∅→∅),[10.74765]→[10.41733:41777](∅→∅),[10.41777]→[10.74842:74877](∅→∅),[10.74842]→[10.74842:74877](∅→∅),[10.74877]→[10.41778:41819](∅→∅),[10.41819]→[10.74951:74986](∅→∅),[10.74951]→[10.74951:74986](∅→∅),[10.74986]→[10.41820:41861](∅→∅),[10.41861]→[10.75060:75117](∅→∅),[10.75060]→[10.75060:75117](∅→∅),[10.75174]→[10.75174:75523](∅→∅),[10.75558]→[10.75558:75613](∅→∅),[10.75613]→[10.41862:41912](∅→∅),[10.41912]→[10.75708:75743](∅→∅),[10.75708]→[10.75708:75743](∅→∅),[10.75743]→[10.41913:41963](∅→∅),[10.41963]→[10.75838:75908](∅→∅),[10.75838]→[10.75838:75908](∅→∅),[10.75908]→[10.8120:8186](∅→∅),[10.8186]→[10.75961:75984](∅→∅),[10.75961]→[10.75961:75984](∅→∅),[10.75984]→[10.41964:42006](∅→∅),[10.42006]→[10.76071:76106](∅→∅),[10.76071]→[10.76071:76106](∅→∅),[10.76106]→[10.42007:42047](∅→∅),[10.42047]→[10.76191:76226](∅→∅),[10.76191]→[10.76191:76226](∅→∅),[10.76226]→[10.42048:42325](∅→∅),[10.42325]→[10.76719:76769](∅→∅),[10.76719]→[10.76719:76769](∅→∅),[10.76819]→[10.76819:77135](∅→∅),[10.77135]→[10.8187:8253](∅→∅),[10.8253]→[10.42326:42384](∅→∅),[10.77188]→[10.42326:42384](∅→∅),[10.42384]→[10.77278:77283](∅→∅),[10.77278]→[10.77278:77283](∅→∅),[10.77283]→[10.13325:13514](∅→∅),[10.13560]→[10.13560:14004](∅→∅),[10.14004]→[10.8254:8320](∅→∅),[10.8320]→[10.42385:42438](∅→∅),[10.14057]→[10.42385:42438](∅→∅),[10.42438]→[10.14144:14188](∅→∅),[10.14144]→[10.14144:14188](∅→∅),[10.14188]→[10.42439:42549](∅→∅),[10.42549]→[10.14366:14392](∅→∅),[10.14366]→[10.14366:14392](∅→∅),[10.14392]→[10.42550:42605](∅→∅),[10.42605]→[10.14481:14535](∅→∅),[10.14481]→[10.14481:14535](∅→∅),[10.14589]→[10.14589:14997](∅→∅),[10.14997]→[10.8321:8387](∅→∅),[10.8387]→[10.42606:42659](∅→∅),[10.15050]→[10.42606:42659](∅→∅),[10.42659]→[10.15145:15185](∅→∅),[10.15145]→[10.15145:15185](∅→∅),[10.15185]→[10.42660:42770](∅→∅),[10.42770]→[10.15379:15405](∅→∅),[10.15379]→[10.15379:15405](∅→∅),[10.15405]→[10.42771:42826](∅→∅),[10.42826]→[10.15502:15553](∅→∅),[10.15502]→[10.15502:15553](∅→∅),[10.15604]→[10.15604:16057](∅→∅),[10.16057]→[10.8388:8454](∅→∅),[10.8454]→[10.42827:42939](∅→∅),[10.16110]→[10.42827:42939](∅→∅),[10.42939]→[10.16300:16342](∅→∅),[10.16300]→[10.16300:16342](∅→∅),[10.16342]→[10.42940:43050](∅→∅),[10.43050]→[10.16530:16556](∅→∅),[10.16530]→[10.16530:16556](∅→∅),[10.16556]→[10.43051:43106](∅→∅),[10.43106]→[10.16650:16696](∅→∅),[10.16650]→[10.16650:16696](∅→∅),[10.16742]→[10.16742:17147](∅→∅),[10.17147]→[10.8455:8521](∅→∅),[10.8521]→[10.43107:43217](∅→∅),[10.17200]→[10.43107:43217](∅→∅),[10.43217]→[10.17382:17424](∅→∅),[10.17382]→[10.17382:17424](∅→∅),[10.17424]→[10.43218:43328](∅→∅),[10.43328]→[10.17606:17632](∅→∅),[10.17606]→[10.17606:17632](∅→∅),[10.17632]→[10.43329:43384](∅→∅),[10.43384]→[10.17723:17771](∅→∅),[10.17723]→[10.17723:17771](∅→∅),[10.17819]→[10.17819:18231](∅→∅),[10.18231]→[10.8522:8588](∅→∅),[10.8588]→[10.43385:43495](∅→∅),[10.18284]→[10.43385:43495](∅→∅),[10.43495]→[10.18466:18508](∅→∅),[10.18466]→[10.18466:18508](∅→∅),[10.18508]→[10.43496:43606](∅→∅),[10.43606]→[10.18690:18716](∅→∅),[10.18690]→[10.18690:18716](∅→∅),[10.18716]→[10.43607:43662](∅→∅),[10.43662]→[10.18807:18812](∅→∅),[10.18807]→[10.18807:18812](∅→∅),[10.18812]→[10.77283:77316](∅→∅),[10.77283]→[10.77283:77316](∅→∅),[10.77354]→[10.77354:77613](∅→∅),[10.77648]→[10.77648:77698](∅→∅),[10.77698]→[10.1878:1925](∅→∅),[10.1925]→[10.43663:43927](∅→∅),[10.19001]→[10.77924:77953](∅→∅),[10.43927]→[10.77924:77953](∅→∅),[10.77924]→[10.77924:77953](∅→∅),[10.77953]→[10.43928:43978](∅→∅),[10.43978]→[10.78029:78064](∅→∅),[10.78029]→[10.78029:78064](∅→∅),[10.78064]→[10.43979:44030](∅→∅),[10.44030]→[10.78141:78176](∅→∅),[10.78141]→[10.78141:78176](∅→∅),[10.78176]→[10.44031:44081](∅→∅),[10.44081]→[10.78252:78262](∅→∅),[10.78252]→[10.78252:78262](∅→∅),[10.78262]→[10.8589:8641](∅→∅),[10.8641]→[10.44082:44310](∅→∅),[10.78309]→[10.44082:44310](∅→∅),[10.19172]→[10.78471:78494](∅→∅),[10.44310]→[10.78471:78494](∅→∅),[10.78471]→[10.78471:78494](∅→∅),[10.78494]→[10.44311:44352](∅→∅),[10.44352]→[10.78561:78596](∅→∅),[10.78561]→[10.78561:78596](∅→∅),[10.78596]→[10.44353:44394](∅→∅),[10.44394]→[10.78663:78698](∅→∅),[10.78663]→[10.78663:78698](∅→∅),[10.78698]→[10.44395:44436](∅→∅),[10.44436]→[10.78765:78803](∅→∅),[10.78765]→[10.78765:78803](∅→∅),[10.78841]→[10.78841:79101](∅→∅),[10.79136]→[10.79136:79160](∅→∅),[10.79160]→[10.8642:8708](∅→∅),[10.8708]→[10.44437:44701](∅→∅),[10.79213]→[10.44437:44701](∅→∅),[10.19361]→[10.79393:79422](∅→∅),[10.44701]→[10.79393:79422](∅→∅),[10.79393]→[10.79393:79422](∅→∅),[10.79422]→[10.44702:44752](∅→∅),[10.44752]→[10.79498:79533](∅→∅),[10.79498]→[10.79498:79533](∅→∅),[10.79533]→[10.44753:44803](∅→∅),[10.44803]→[10.79609:79644](∅→∅),[10.79609]→[10.79609:79644](∅→∅),[10.79644]→[10.44804:44854](∅→∅),[10.44854]→[10.79720:79782](∅→∅),[10.79720]→[10.79720:79782](∅→∅),[10.79782]→[10.8709:8761](∅→∅),[10.8761]→[10.44855:45213](∅→∅),[10.79829]→[10.44855:45213](∅→∅),[10.19714]→[10.79991:80014](∅→∅),[10.45213]→[10.79991:80014](∅→∅),[10.79991]→[10.79991:80014](∅→∅),[10.80014]→[10.45214:45255](∅→∅),[10.45255]→[10.80081:80116](∅→∅),[10.80081]→[10.80081:80116](∅→∅),[10.80116]→[10.45256:45298](∅→∅),[10.45298]→[10.80184:80219](∅→∅),[10.80184]→[10.80184:80219](∅→∅),[10.80219]→[10.45299:45340](∅→∅),[10.45340]→[10.80286:80291](∅→∅),[10.80286]→[10.80286:80291](∅→∅),[10.80291]→[10.19715:19755](∅→∅),[10.19800]→[10.19800:20140](∅→∅),[10.20175]→[10.20175:20227](∅→∅),[10.20227]→[10.1926:1973](∅→∅),[10.1973]→[10.45341:45463](∅→∅),[10.45463]→[10.20461:20471](∅→∅),[10.20461]→[10.20461:20471](∅→∅),[10.20471]→[10.8762:8866](∅→∅),[10.8866]→[10.20565:20592](∅→∅),[10.20565]→[10.20565:20592](∅→∅),[10.20592]→[10.45464:45566](∅→∅),[10.45566]→[10.20760:20765](∅→∅),[10.20760]→[10.20760:20765](∅→∅),[10.20765]→[10.80291:80314](∅→∅),[10.80291]→[10.80291:80314](∅→∅),[10.80342]→[10.80342:80428](∅→∅),[10.80428]→[10.685:803](∅→∅),[10.803]→[10.80490:80608](∅→∅),[10.5331]→[10.80490:80608](∅→∅),[10.80490]→[10.80490:80608](∅→∅),[10.80643]→[10.80643:80694](∅→∅),[10.80694]→[10.8867:8919](∅→∅),[10.8919]→[10.1974:2021](∅→∅),[10.80741]→[10.1974:2021](∅→∅),[10.2021]→[10.8920:8980](∅→∅),[10.8980]→[10.45567:45681](∅→∅),[10.80837]→[10.45567:45681](∅→∅),[10.45681]→[10.80983:81121](∅→∅),[10.80983]→[10.80983:81121](∅→∅),[10.81121]→[10.8981:9033](∅→∅),[10.9033]→[10.2022:2070](∅→∅),[10.81168]→[10.2022:2070](∅→∅),[10.2070]→[10.9034:9150](∅→∅),[10.9150]→[10.45682:45740](∅→∅),[10.81313]→[10.45682:45740](∅→∅),[10.45740]→[10.804:860](∅→∅),[10.860]→[10.81459:81495](∅→∅),[10.45796]→[10.81459:81495](∅→∅),[10.81459]→[10.81459:81495](∅→∅),[10.81531]→[10.81531:81617](∅→∅),[10.81617]→[10.861:939](∅→∅),[10.939]→[10.81662:81694](∅→∅),[10.81662]→[10.81662:81694](∅→∅),[10.81694]→[10.940:981](∅→∅),[10.981]→[10.81735:81780](∅→∅),[10.81735]→[10.81735:81780](∅→∅),[10.81815]→[10.81815:81866](∅→∅),[10.81866]→[10.9151:9203](∅→∅),[10.9203]→[10.2071:2118](∅→∅),[10.81913]→[10.2071:2118](∅→∅),[10.2118]→[10.81959:81995](∅→∅),[10.81959]→[10.81959:81995](∅→∅),[10.81995]→[10.9204:9256](∅→∅),[10.9256]→[10.45797:45855](∅→∅),[10.82041]→[10.45797:45855](∅→∅),[10.45855]→[10.982:1038](∅→∅),[10.1038]→[10.82203:82236](∅→∅),[10.45911]→[10.82203:82236](∅→∅),[10.82203]→[10.82203:82236](∅→∅),[10.82269]→[10.82269:82355](∅→∅),[10.82355]→[10.1039:1131](∅→∅),[10.1131]→[10.82396:82428](∅→∅),[10.82396]→[10.82396:82428](∅→∅),[10.82428]→[10.1132:1173](∅→∅),[10.1173]→[10.82469:82514](∅→∅),[10.82469]→[10.82469:82514](∅→∅),[10.82549]→[10.82549:82600](∅→∅),[10.82600]→[10.9257:9309](∅→∅),[10.9309]→[10.2119:2166](∅→∅),[10.82647]→[10.2119:2166](∅→∅),[10.2166]→[10.9310:9370](∅→∅),[10.9370]→[10.82743:82761](∅→∅),[10.82743]→[10.82743:82761](∅→∅),[10.82761]→[10.45912:45970](∅→∅),[10.45970]→[10.1174:1230](∅→∅),[10.1230]→[10.82917:82958](∅→∅),[10.46026]→[10.82917:82958](∅→∅),[10.82917]→[10.82917:82958](∅→∅),[10.82999]→[10.82999:83085](∅→∅),[10.83085]→[10.1231:1306](∅→∅),[10.1306]→[10.83130:83248](∅→∅),[10.83130]→[10.83130:83248](∅→∅),[10.83283]→[10.83283:83342](∅→∅),[10.83342]→[10.9371:9423](∅→∅),[10.9423]→[10.2167:2214](∅→∅),[10.83389]→[10.2167:2214](∅→∅),[10.2214]→[10.9424:9476](∅→∅),[10.9476]→[10.83481:83499](∅→∅),[10.83481]→[10.83481:83499](∅→∅),[10.83499]→[10.46027:46085](∅→∅),[10.46085]→[10.1307:1363](∅→∅),[10.1363]→[10.499:768](∅→∅),[10.768]→[8.505:1002](∅→∅),[8.1002]→[10.768:990](∅→∅),[10.768]→[10.768:990](∅→∅),[10.990]→[10.83671:83675](∅→∅),[10.1363]→[10.83671:83675](∅→∅),[10.46141]→[10.83671:83675](∅→∅),[10.83671]→[10.83671:83675](∅→∅)
function test_right_arrow_scrolls_down_in_wrapped_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}-- cursor is at bottom right of screenEditor_state.cursor1 = {line=3, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the right arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'right', 'right')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')endfunction test_home_scrolls_up_in_wrapped_line()-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=3, pos=5}-- cursor is at top of screenEditor_state.cursor1 = {line=3, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:2')-- after hitting home the screen scrolls up to first screen lineedit.run_after_keychord(Editor_state, 'home', 'home')y = Editor_state.topApp.screen.check(y, 'ghi ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')endfunction test_end_scrolls_down_in_wrapped_line()-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}-- cursor is at bottom right of screenEditor_state.cursor1 = {line=3, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting end the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'end', 'end')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 8, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')endfunction test_position_cursor_on_recently_edited_wrapping_line()-- draw a line wrapping over 2 screen linesApp.screen.init{width=100, height=200}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def ghi jkl mno pqr ', 'xyz'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=25}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl mno pqr ', 'baseline1/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'baseline1/screen:3')-- add to the line until it's wrapping over 3 screen linesedit.run_after_text_input(Editor_state, 's')edit.run_after_text_input(Editor_state, 't')edit.run_after_text_input(Editor_state, 'u')check_eq(Editor_state.cursor1.pos, 28, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'abc def ghi ', 'baseline2/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl mno pqr ', 'baseline2/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'stu', 'baseline2/screen:3')-- try to move the cursor earlier in the third screen line by clicking the mouseedit.run_after_mouse_release(Editor_state, Editor_state.left+2,Editor_state.top+Editor_state.line_height*2+5, 1)-- cursor should movecheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 25, 'cursor:pos')endfunction test_backspace_can_scroll_up()-- display the lines 2/3/4 with the cursor on line 2App.screen.init{width=120, 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=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- after hitting backspace the screen scrolls up by one lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')y = Editor_state.topApp.screen.check(y, 'abcdef', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')endfunction test_backspace_can_scroll_up_screen_line()-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=5}Editor_state.screen_top1 = {line=3, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'baseline/screen:2')-- after hitting backspace the screen scrolls up by one screen lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')y = Editor_state.topApp.screen.check(y, 'ghij', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'screen:3')check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')endfunction test_backspace_past_line_boundary()-- position cursor at start of a (non-first) lineApp.screen.init{width=Editor_state.left+30, 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}-- backspace joins with previous lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'abcdef', 'check')end-- some tests for operating over selections created using Shift- chords-- we're just testing delete_selection, and it works the same for all keysfunction test_backspace_over_selection()-- select just one character within a line with cursor before selectionApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.selection1 = {line=1, pos=2}-- backspace deletes the selected character, even though it's after the cursoredit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'bc', 'data')-- cursor (remains) at start of selectioncheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_backspace_over_selection_reverse()-- select just one character within a line with cursor after selectionApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.selection1 = {line=1, pos=1}-- backspace deletes the selected characteredit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'bc', 'data')-- cursor moves to start of selectioncheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_backspace_over_multiple_lines()-- select just one character within a line with cursor after selectionApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.selection1 = {line=4, pos=2}-- backspace deletes the region and joins the remaining portions of lines on either sideedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'akl', 'data:1')check_eq(Editor_state.lines[2].data, 'mno', 'data:2')-- cursor remains at start of selectioncheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_backspace_to_end_of_line()-- select region from cursor to end of lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.selection1 = {line=1, pos=4}-- backspace deletes rest of line without joining to any other lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'a', 'data:1')check_eq(Editor_state.lines[2].data, 'def', 'data:2')-- cursor remains at start of selectioncheck_eq(Editor_state.cursor1.line, 1, 'cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_backspace_to_start_of_line()-- select region from cursor to start of lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.selection1 = {line=2, pos=3}-- backspace deletes beginning of line without joining to any other lineedit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.lines[1].data, 'abc', 'data:1')check_eq(Editor_state.lines[2].data, 'f', 'data:2')-- cursor remains at start of selectioncheck_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')-- selection is clearedcheck_nil(Editor_state.selection1.line, 'selection')endfunction test_undo_insert_text()App.screen.init{width=120, 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=2, pos=4}Editor_state.screen_top1 = {line=1, pos=1}-- insert a characteredit.draw(Editor_state)edit.run_after_text_input(Editor_state, 'g')check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'baseline/cursor:pos')check_nil(Editor_state.selection1.line, 'baseline/selection:line')check_nil(Editor_state.selection1.pos, 'baseline/selection:pos')local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'defg', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'baseline/screen:3')-- undoedit.run_after_keychord(Editor_state, 'C-z', 'z')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')check_nil(Editor_state.selection1.line, 'selection:line')check_nil(Editor_state.selection1.pos, 'selection:pos')y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'screen:3')endfunction test_undo_delete_text()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'defg', 'xyz'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=5}Editor_state.screen_top1 = {line=1, pos=1}-- delete a characteredit.run_after_keychord(Editor_state, 'backspace', 'backspace')check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'baseline/cursor:pos')check_nil(Editor_state.selection1.line, 'baseline/selection:line')check_nil(Editor_state.selection1.pos, 'baseline/selection:pos')local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'baseline/screen:3')-- undo--? -- after undo, the backspaced key is selectededit.run_after_keychord(Editor_state, 'C-z', 'z')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')check_nil(Editor_state.selection1.line, 'selection:line')check_nil(Editor_state.selection1.pos, 'selection:pos')--? check_eq(Editor_state.selection1.line, 2, 'selection:line')--? check_eq(Editor_state.selection1.pos, 4, 'selection:pos')y = Editor_state.topApp.screen.check(y, 'abc', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'defg', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'screen:3')endfunction test_undo_restores_selection()-- display a line of text with some part selectedApp.screen.init{width=75, 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.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- delete selected textedit.run_after_text_input(Editor_state, 'x')check_eq(Editor_state.lines[1].data, 'xbc', 'baseline')check_nil(Editor_state.selection1.line, 'baseline:selection')-- undoedit.run_after_keychord(Editor_state, 'C-z', 'z')edit.run_after_keychord(Editor_state, 'C-z', 'z')-- selection is restoredcheck_eq(Editor_state.selection1.line, 1, 'line')check_eq(Editor_state.selection1.pos, 2, 'pos')endfunction test_search()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'```lines', '```', 'def', 'ghi', '’deg'} -- contains unicode quote in final lineText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for a stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'd')edit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.cursor1.line, 2, '1/cursor:line')check_eq(Editor_state.cursor1.pos, 1, '1/cursor:pos')-- reset cursorEditor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}-- search for second occurrenceedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'de')edit.run_after_keychord(Editor_state, 'down', 'down')edit.run_after_keychord(Editor_state, 'return', 'return')check_eq(Editor_state.cursor1.line, 4, '2/cursor:line')check_eq(Editor_state.cursor1.pos, 2, '2/cursor:pos')endfunction test_search_upwards()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'’abc', 'abd'} -- contains unicode quoteText.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for a stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'a')-- search for previous occurrenceedit.run_after_keychord(Editor_state, 'up', 'up')check_eq(Editor_state.cursor1.line, 1, '2/cursor:line')check_eq(Editor_state.cursor1.pos, 2, '2/cursor:pos')endfunction test_search_wrap()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'’abc', 'def'} -- contains unicode quote in first lineText.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for a stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'a')edit.run_after_keychord(Editor_state, 'return', 'return')-- cursor wrapscheck_eq(Editor_state.cursor1.line, 1, '1/cursor:line')check_eq(Editor_state.cursor1.pos, 2, '1/cursor:pos')endfunction test_search_wrap_upwards()App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc ’abd'} -- contains unicode quoteText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search upwards for a stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_text_input(Editor_state, 'a')edit.run_after_keychord(Editor_state, 'up', 'up')-- cursor wrapscheck_eq(Editor_state.cursor1.line, 1, '1/cursor:line')check_eq(Editor_state.cursor1.pos, 6, '1/cursor:pos')endfunction test_search_downwards_from_end_of_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=4}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for empty stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_keychord(Editor_state, 'down', 'down')-- no crashendfunction test_search_downwards_from_final_pos_of_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=3}Editor_state.screen_top1 = {line=1, pos=1}edit.draw(Editor_state)-- search for empty stringedit.run_after_keychord(Editor_state, 'C-f', 'f')edit.run_after_keychord(Editor_state, 'down', 'down')-- no crashend - edit in source_text.lua at line 71
--? -- render screen line--? App.color(Text_color)--? App.screen.print(screen_line, State.left,y) - replacement in source_text.lua at line 122[10.2428]→[10.2428:2472](∅→∅),[10.2472]→[5.2235:2284](∅→∅),[5.2284]→[10.2511:2815](∅→∅),[10.2511]→[10.2511:2815](∅→∅),[10.2815]→[5.2285:2368](∅→∅),[5.2368]→[10.193:385](∅→∅),[10.2886]→[10.193:385](∅→∅),[10.385]→[10.2956:3180](∅→∅),[10.2956]→[10.2956:3180](∅→∅),[10.3180]→[5.2369:2416](∅→∅),[5.2416]→[10.3217:3264](∅→∅),[10.3217]→[10.3217:3264](∅→∅)
for frag in line.data:gmatch('%S*%s*') dolocal frag_width = State.font:getWidth(frag)--? print('-- frag:', frag, pos, x, frag_width, State.width)while x + frag_width > State.width do--? print('frag:', frag, pos, x, frag_width, State.width)if x < 0.8 * State.width then-- long word; chop it at some letter-- We're not going to reimplement TeX here.local bpos = Text.nearest_pos_less_than(State.font, frag, State.width - x)if x == 0 and bpos == 0 thenassert(false, ("Infinite loop while line-wrapping. Editor is %dpx wide; window is %dpx wide"):format(State.width, App.screen.width))endpos = pos + bposlocal boffset = Text.offset(frag, bpos+1) -- byte _after_ bposfrag = string.sub(frag, boffset)--? if bpos > 0 then--? print('after chop:', frag)--? endfrag_width = State.font:getWidth(frag)end--? print('screen line:', pos)for pos,char in utf8chars(line.data) dolocal w = State.font:getWidth(char)if Text.should_word_wrap(State, line.data, pos, char, x)or x+w > State.width -- truncate within a wordthen - replacement in source_text.lua at line 128
x = 0 -- new screen linex = 0 - replacement in source_text.lua at line 130
x = x + frag_widthpos = pos + utf8.len(frag)x = x + wendend-- Check whether to word-wrap line at pos which will be positioned at x.---- We wrap at the start of a word (non-space just after space) if the word-- (non-spaces followed by spaces) wouldn't fit in the rest of the line.---- x lies between 0 and editor.width.---- Postcondition:-- Current line is not wider than editor.width---- Desired properties in priority order:-- Next line doesn't start with whitespace-- Current line ends with whitespace (a.k.a. word wrap)-- Current line is close to full-- None of these is guaranteed. But we should never satisfy a lower priority-- before a higher one.function Text.should_word_wrap(editor, line, pos, char, x)if char:match('%s') then return false endif pos == 1 then return false endif Text.match(line, pos-1, '%S') then return false endlocal offset = Text.offset(line, pos)-- most of the time a word is printable chars + whitespacelocal s = line:match('%S+%s*', offset)assert(s)local w = editor.font:getWidth(s)if x+w < editor.width then return false endif w > editor.width then return false end -- we're going to need to truncate the next word anywayif x < 0.8*editor.width thenlocal s2 = line:match('%S+', offset)local w2 = editor.font:getWidth(s2)if x+w2 > editor.width then-- there'll be some non-whitespace left over for the next linereturn falseend - edit in source_text.lua at line 169
return true - replacement in source_text.lua at line 252
--== shortcuts that mutate text--== shortcuts that mutate text (must schedule_save) - edit in source_text.lua at line 1196[39.406][10.147057]
end-- create a new iterator for s which provides the index and UTF-8 bytes corresponding to each codepointfunction utf8chars(s, startpos)local next_pos = startpos or 1 -- in code pointslocal next_offset = utf8.offset(s, next_pos) -- in bytesreturn function()assert(next_offset) -- never call the iterator after it returns nillocal curr_pos = next_posnext_pos = next_pos+1local curr_offset = next_offsetnext_offset = utf8.offset(s, 2, next_offset)if next_offset == nil then return endlocal curr_char = s:sub(curr_offset, next_offset-1)return curr_pos, curr_charend - replacement in source_edit.lua at line 545
function edit.update_font_settings(State, font_height)function edit.update_font_settings(State, font_height, font) - replacement in source_edit.lua at line 547
State.font = love.graphics.newFont(State.font_height)State.font = font or love.graphics.newFont(State.font_height)