update source editor

akkartik
Aug 16, 2025, 8:33 PM
VZPH3XJK5HIK7RF2DN5QCGC23BGVMVWVKDGV3HXBU4GBWU3HROJAC

Dependencies

  • [2] TUN6TDO4 give a test a unique name
  • [3] KVJTEXMS fix all tests in LÖVE v12
  • [4] GJ4LBCIE streamline button.lua
  • [5] AQMZJXUR use editor state font for width calculations
  • [6] J7A5ROM3 bugfix in cursor positioning
  • [7] N33WSVVO whitespace
  • [8] 4FTOQOPZ bugfix #2 in search UI
  • [9] 26V2B2QH clarify a misleading test
  • [10] FQZ3U3YA streamline one more test name
  • [11] CFJ4FLCQ clean up an unnecessary mutation
  • [12] GZ5WULJV switch source side to new screen-line-based render
  • [13] JYB3RFWH bugfix in source editor
  • [14] 5XA7TKWY pull font into editor
  • [15] H4R5BHVY no more Text allocations
  • [16] TYFAGQWS repeat bugfix on source editor
  • [17] AIHGJ4BT bugfix: infinite loop inside a very narrow window
  • [18] ORRSP7FV deduce test names on failures
  • [19] KOTNETIM repeat changes on source editor
  • [20] SWZAQHGR bugfix: up arrow when line above is a drawing
  • [21] O7YTBRQY bugfix: restart search on backspace
  • [22] 5SM6DRHK port inscript's bugfix to source editor
  • [23] LK4ZW4BB bugfix
  • [24] FHNPQBLK more carefully pass the 'key' arg around
  • [25] 656FM555 bugfix: clear selection when clicking above or below lines
  • [26] 2CK5QI7W make love event names consistent
  • [27] ZS5IYZH5 stop caching screen_bottom1
  • [28] A42EMHOQ plumb through all supported args in LÖVE handlers
  • [29] 2H76FV5S bugfix: searching files containing unicode
  • [30] KMSL74GA support selections in the source editor
  • [31] 2TCIWW6Z stop caching starty
  • [32] DFGPHG5T overzealous search-and-replace
  • [33] KKMFQDR4 editing source code from within the app
  • [34] S2QMLRXL stop creating a singleton table for every word
  • [35] OI4FPFIN support drawings in the source editor
  • [*] R5QXEHUI somebody stop me
  • [*] UBA2ZUCP remove a duplicate print to screen
  • [*] LDFXFRUO bring 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')
    end
    function 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 text
    check_eq(#Editor_state.lines, 2, '#lines')
    check_eq(Editor_state.cursor1.line, 2, 'cursor')
    end
    function test_backspace_to_delete_drawing()
    -- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'```lines', '```', ''}
    Text.redraw_all(Editor_state)
    -- cursor is on text as always (outside tests this will get initialized correctly)
    Editor_state.cursor1.line = 2
    -- backspacing deletes the drawing
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    end
    function test_backspace_from_start_of_final_line()
    -- display final line of text with cursor at start of it
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Editor_state.screen_top1 = {line=2, pos=1}
    Editor_state.cursor1 = {line=2, pos=1}
    Text.redraw_all(Editor_state)
    -- backspace scrolls up
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    end
    function test_insert_first_character()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{}
    Text.redraw_all(Editor_state)
    edit.draw(Editor_state)
    edit.run_after_text_input(Editor_state, 'a')
    local y = Editor_state.top
    App.screen.check(y, 'a', 'screen:1')
    end
    function test_press_ctrl()
    -- press ctrl while the cursor is on text
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{''}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.run_after_keychord(Editor_state, 'C-m', 'm')
    end
    function test_move_left()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'a'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_right()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'a'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'right', 'right')
    check_eq(Editor_state.cursor1.pos, 2, 'check')
    end
    function test_move_left_to_previous_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'left', 'left')
    check_eq(Editor_state.cursor1.line, 1, 'line')
    check_eq(Editor_state.cursor1.pos, 4, 'pos') -- past end of line
    end
    function test_move_right_to_next_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- past end of line
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'right', 'right')
    check_eq(Editor_state.cursor1.line, 2, 'line')
    check_eq(Editor_state.cursor1.pos, 1, 'pos')
    end
    function test_move_to_start_of_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_to_start_of_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_skip_to_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=5} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_skip_past_tab_to_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def\tghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=10} -- within third word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 9, 'check')
    end
    function test_skip_multiple_spaces_to_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=6} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_to_start_of_word_on_previous_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.line, 1, 'line')
    check_eq(Editor_state.cursor1.pos, 5, 'pos')
    end
    function test_move_past_end_of_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 4, 'check')
    end
    function test_skip_to_next_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 8, 'check')
    end
    function test_skip_past_tab_to_next_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc\tdef'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 4, 'check')
    end
    function test_skip_multiple_spaces_to_next_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 9, 'check')
    end
    function test_move_past_end_of_word_on_next_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=8}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.line, 2, 'line')
    check_eq(Editor_state.cursor1.pos, 4, 'pos')
    end
    function test_click_moves_cursor()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.startpos for each line
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is empty to avoid perturbing future edits
    check_nil(Editor_state.selection1.line, 'selection:line')
    check_nil(Editor_state.selection1.pos, 'selection:pos')
    end
    function test_click_to_left_of_line()
    -- display a line with the cursor in the middle
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click to the left of the line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1)
    -- cursor moves to start of line
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_takes_margins_into_account()
    -- display two lines with cursor on one of them
    App.screen.init{width=100, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.left = 50 -- occupy only right side of screen
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_on_empty_line()
    -- display two lines with the first one empty
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click on the empty line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    -- selection remains empty
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_below_final_line_of_file()
    -- display one line
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click below first line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+50, 1)
    -- cursor goes to bottom
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    -- selection remains empty
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_draw_text()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_draw_wrapping_text()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'defgh', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'gh', 'screen:3')
    end
    function 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.top
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_click_on_wrapping_line()
    -- display two screen lines with cursor on one of them
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=20}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_on_wrapping_line_takes_margins_into_account()
    -- display two screen lines with cursor on one of them
    App.screen.init{width=100, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.left = 50 -- occupy only right side of screen
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=20}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_draw_text_wrapping_within_word()
    -- arrange a screen line that needs to be split within a word
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abcd e fghijk', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abcd ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'e fghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jk', 'screen:3')
    end
    function test_draw_wrapping_text_containing_non_ascii()
    -- draw a long line containing non-ASCII
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'madam I’m adam', 'xyz'} -- notice the non-ASCII apostrophe
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'mada', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'm ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'I’m a', 'screen:3')
    end
    function test_click_past_end_of_screen_line()
    -- display a wrapping line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, "I'm ada", 'baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line (one more than final character shown)
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 14, 'cursor:pos')
    end
    function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()
    -- display a wrapping line from its second screen line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=8}
    Editor_state.screen_top1 = {line=1, pos=7}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, "I'm ada", 'baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line (one more than final character shown)
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 14, 'cursor:pos')
    end
    function test_click_past_end_of_wrapping_line()
    -- display a wrapping line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, "I'm ada", 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'm', 'baseline/screen:3')
    y = y + Editor_state.line_height
    -- click past the end of it
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of line
    check_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_wrapping_line_containing_non_ascii()
    -- display a wrapping line containing non-ASCII
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{'madam I’m adam'} -- notice the non-ASCII apostrophe
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'I’m ada', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'm', 'baseline/screen:3')
    y = y + Editor_state.line_height
    -- click past the end of it
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of line
    check_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_word_wrapping_line()
    -- display a long line wrapping at a word boundary on a screen of more realistic length
    App.screen.init{width=160, height=80}
    Editor_state = edit.initialize_test_state()
    -- 0 1 2
    -- 123456789012345678901
    Editor_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.top
    App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    -- click past the end of the screen line
    edit.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')
    end
    function test_select_text()
    -- display a line of text
    App.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 letter
    App.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 released
    check_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')
    end
    function test_cursor_movement_without_shift_resets_selection()
    -- display a line of text with some part selected
    App.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 shift
    edit.run_after_keychord(Editor_state, 'right', 'right')
    -- no change to data, selection is reset
    check_nil(Editor_state.selection1.line, 'check')
    check_eq(Editor_state.lines[1].data, 'abc', 'data')
    end
    function test_edit_deletes_selection()
    -- display a line of text with some part selected
    App.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 key
    edit.run_after_text_input(Editor_state, 'x')
    -- selected text is deleted and replaced with the key
    check_eq(Editor_state.lines[1].data, 'xbc', 'check')
    end
    function test_edit_with_shift_key_deletes_selection()
    -- display a line of text with some part selected
    App.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 letter
    App.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 key
    check_nil(Editor_state.selection1.line, 'check')
    check_eq(Editor_state.lines[1].data, 'Dbc', 'data')
    end
    function test_copy_does_not_reset_selection()
    -- display a line of text with a selection
    App.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 selection
    edit.run_after_keychord(Editor_state, 'C-c', 'c')
    check_eq(App.clipboard, 'a', 'clipboard')
    -- selection is reset since shift key is not pressed
    check(Editor_state.selection1.line, 'check')
    end
    function test_cut()
    -- display a line of text with some part selected
    App.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 key
    edit.run_after_keychord(Editor_state, 'C-x', 'x')
    check_eq(App.clipboard, 'a', 'clipboard')
    -- selected text is deleted
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    end
    function test_paste_replaces_selection()
    -- display a line of text with a selection
    App.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 clipboard
    App.clipboard = 'xyz'
    -- paste selection
    edit.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 deleted
    check_eq(Editor_state.lines[1].data, 'xyzdef', 'check')
    end
    function test_deleting_selection_may_scroll()
    -- display lines 2/3/4
    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=3, pos=2}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- set up a selection starting above the currently displayed page
    Editor_state.selection1 = {line=1, pos=2}
    -- delete selection
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    -- page scrolls up
    check_eq(Editor_state.screen_top1.line, 1, 'check')
    check_eq(Editor_state.lines[1].data, 'ahi', 'data')
    end
    function 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.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'g', 'screen:3')
    end
    function test_insert_newline()
    -- display a few lines
    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'}
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- hitting the enter key splits the line
    edit.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.top
    App.screen.check(y, 'a', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'bc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function test_insert_newline_at_start_of_line()
    -- display a line
    App.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 line
    edit.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')
    end
    function test_insert_from_clipboard()
    -- display a few lines
    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'}
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- paste some text including a newline, check that new line is created
    App.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.top
    App.screen.check(y, 'axy', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'zbc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function 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 location
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- drag and release somewhere else
    edit.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')
    end
    function 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 text
    edit.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')
    end
    function test_select_text_using_mouse_starting_above_text_wrapping_line()
    -- first screen line starts in the middle of a line
    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=2, pos=5}
    Editor_state.screen_top1 = {line=2, pos=3}
    -- press mouse above first line of text
    edit.draw(Editor_state)
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
    -- selection is at screen top
    check(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')
    end
    function 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 screen
    App.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.top
    App.screen.check(y, 'abc', 'baseline:screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'de', 'baseline:screen:2')
    -- press mouse above first line of text
    edit.run_after_mouse_press(Editor_state, 5,App.screen.height-5, 1)
    -- selection is past bottom-most text in screen
    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, 6, 'selection:pos')
    end
    function 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 location
    edit.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 else
    App.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')
    end
    function 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 location
    edit.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 location
    App.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 location
    App.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')
    end
    function test_select_all_text()
    -- display a single line of text
    App.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 all
    App.fake_key_press('lctrl')
    edit.run_after_keychord(Editor_state, 'C-a', 'a')
    App.fake_key_release('lctrl')
    edit.key_release(Editor_state, 'lctrl')
    -- selection
    check_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')
    end
    function test_cut_without_selection()
    -- display a few lines
    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'}
    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 text
    edit.run_after_keychord(Editor_state, 'C-x', 'x')
    -- no crash
    check_nil(Editor_state.selection1.line, 'check')
    end
    function 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 displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    -- after pagedown the bottom line becomes the top
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    end
    function test_pagedown_skips_drawings()
    -- some lines of text with a drawing intermixed
    local drawing_width = 50
    App.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 15
    Text.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 80px
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.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 80px
    edit.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_height
    App.screen.check(y, 'def', 'screen:1')
    end
    function test_pagedown_can_start_from_middle_of_long_wrapping_line()
    -- draw a few lines starting from a very long wrapping line
    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 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.top
    App.screen.check(y, 'abc ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def g', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'hi jkl ', 'baseline/screen:3')
    -- after pagedown we scroll down the very long wrapping line
    edit.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.top
    App.screen.check(y, 'hi jkl ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'pqr ', 'screen:3')
    end
    function test_pagedown_never_moves_up()
    -- draw the final screen line of a wrapping line
    App.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 change
    edit.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')
    end
    function 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 displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the down arrow, the cursor moves down by 1 line
    edit.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 unchanged
    y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_down_arrow_skips_drawing()
    -- some lines of text with a drawing intermixed
    local drawing_width = 50
    App.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    local drawing_height = Drawing_padding_height + drawing_width/2 -- default
    y = y + drawing_height
    App.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 drawing
    edit.run_after_keychord(Editor_state, 'down', 'down')
    check_eq(Editor_state.cursor1.line, 3, 'cursor')
    end
    function test_down_arrow_scrolls_down_by_one_line()
    -- display the first three lines with the cursor on the bottom line
    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=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line()
    -- display the first three lines with the cursor on the bottom line
    App.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the down arrow the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()
    -- display the first three lines with the cursor on the bottom line
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghijk', 'baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghijk', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'l', 'screen:3')
    end
    function 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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghijk', 'baseline/screen:3')
    -- after hitting pagedown the screen scrolls down to start of a long line
    edit.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 up
    edit.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.top
    App.screen.check(y, 'ghijk', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'l', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'screen:3')
    end
    function test_up_arrow_moves_cursor()
    -- display the first 3 lines with the cursor on the bottom line
    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=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the up arrow the cursor moves up by 1 line
    edit.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 unchanged
    y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_up_arrow_skips_drawing()
    -- some lines of text with a drawing intermixed
    local drawing_width = 50
    App.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    local drawing_height = Drawing_padding_height + drawing_width/2 -- default
    y = y + drawing_height
    App.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 drawing
    edit.run_after_keychord(Editor_state, 'up', 'up')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    end
    function test_up_arrow_scrolls_up_by_one_line()
    -- display the lines 2/3/4 with the cursor on line 2
    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=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    edit.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
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_up_arrow_scrolls_up_by_one_line_skipping_drawing()
    -- display lines 3/4/5 with a drawing just off screen at line 2
    App.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.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to previous text line
    edit.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')
    end
    function test_up_arrow_scrolls_up_by_one_screen_line()
    -- display lines starting from second screen line of a line
    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=3, pos=6}
    Editor_state.screen_top1 = {line=3, pos=6}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'kl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting the up arrow the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'up', 'up')
    y = Editor_state.top
    App.screen.check(y, 'ghi j', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_up_arrow_scrolls_up_to_final_screen_line()
    -- display lines starting just after a long line
    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=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to final screen line of previous line
    edit.run_after_keychord(Editor_state, 'up', 'up')
    y = Editor_state.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_up_arrow_scrolls_up_to_empty_line()
    -- display a screenful of text with an empty line just above it outside the screen
    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=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    edit.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 line
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function 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 displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    -- after pageup the cursor goes to first line
    edit.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.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    end
    function test_pageup_scrolls_up_by_screen_line()
    -- display the first three lines with the cursor on the bottom line
    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=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    edit.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.top
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_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.top
    App.screen.check(y, 'kl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    edit.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.top
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi j', 'screen:3')
    end
    function test_enter_on_bottom_line_scrolls_down()
    -- display a few lines with cursor on bottom line
    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'}
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the enter key the screen scrolls down
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'g', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'hi', 'screen:3')
    end
    function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    -- display just the bottom line on screen
    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'}
    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.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    -- after hitting the enter key the screen does not scroll down
    edit.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.top
    App.screen.check(y, 'j', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    end
    function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    -- display just an empty bottom line on screen
    App.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 down
    edit.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.top
    App.screen.check(y, 'a', 'screen:1')
    end
    function test_typing_on_bottom_line_scrolls_down()
    -- display a few lines with cursor on bottom line
    App.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after typing something the line wraps and the screen scrolls down
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghijk', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'l', 'screen:3')
    end
    function test_left_arrow_scrolls_up_in_wrapped_line()
    -- display lines starting from second screen line of a line
    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.screen_top1 = {line=3, pos=6}
    -- cursor is at top of screen
    Editor_state.cursor1 = {line=3, pos=6}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'kl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting the left arrow the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'left', 'left')
    y = Editor_state.top
    App.screen.check(y, 'ghi j', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_right_arrow_scrolls_down_in_wrapped_line()
    -- display the first three lines with the cursor on the bottom line
    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.screen_top1 = {line=1, pos=1}
    -- cursor is at bottom right of screen
    Editor_state.cursor1 = {line=3, pos=6}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi j', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the right arrow the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi j', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:3')
    end
    function test_home_scrolls_up_in_wrapped_line()
    -- display lines starting from second screen line of a line
    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.screen_top1 = {line=3, pos=6}
    -- cursor is at top of screen
    Editor_state.cursor1 = {line=3, pos=6}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'kl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting home the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'home', 'home')
    y = Editor_state.top
    App.screen.check(y, 'ghi j', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_end_scrolls_down_in_wrapped_line()
    -- display the first three lines with the cursor on the bottom line
    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.screen_top1 = {line=1, pos=1}
    -- cursor is at bottom right of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi j', 'baseline/screen:3')
    -- after hitting end the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi j', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:3')
    end
    function test_position_cursor_on_recently_edited_wrapping_line()
    -- draw a line wrapping over 2 screen lines
    App.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.top
    App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl mno pqr ', 'baseline1/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'baseline1/screen:3')
    -- add to the line until it's wrapping over 3 screen lines
    edit.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.top
    App.screen.check(y, 'abc def ghi ', 'baseline2/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl mno pqr ', 'baseline2/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'stu', 'baseline2/screen:3')
    -- try to move the cursor earlier in the third screen line by clicking the mouse
    edit.run_after_mouse_release(Editor_state, Editor_state.left+2,Editor_state.top+Editor_state.line_height*2+5, 1)
    -- cursor should move
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 25, 'cursor:pos')
    end
    function test_backspace_can_scroll_up()
    -- display the lines 2/3/4 with the cursor on line 2
    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=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting backspace the screen scrolls up by one line
    edit.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.top
    App.screen.check(y, 'abcdef', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_backspace_can_scroll_up_screen_line()
    -- display lines starting from second screen line of a line
    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=3, pos=6}
    Editor_state.screen_top1 = {line=3, pos=6}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'kl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting backspace the screen scrolls up by one screen line
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    y = Editor_state.top
    App.screen.check(y, 'ghi k', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'l', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_backspace_past_line_boundary()
    -- position cursor at start of a (non-first) line
    App.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 line
    edit.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 keys
    function test_backspace_over_selection()
    -- select just one character within a line with cursor before selection
    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=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    -- backspace deletes the selected character, even though it's after the cursor
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    -- cursor (remains) at start of selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_over_selection_reverse()
    -- select just one character within a line with cursor after selection
    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=1, pos=2}
    Editor_state.selection1 = {line=1, pos=1}
    -- backspace deletes the selected character
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    -- cursor moves to start of selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_over_multiple_lines()
    -- select just one character within a line with cursor after selection
    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=1, pos=2}
    Editor_state.selection1 = {line=4, pos=2}
    -- backspace deletes the region and joins the remaining portions of lines on either side
    edit.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 selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_to_end_of_line()
    -- select region from cursor to end of line
    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=1, pos=2}
    Editor_state.selection1 = {line=1, pos=4}
    -- backspace deletes rest of line without joining to any other line
    edit.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 selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_to_start_of_line()
    -- select region from cursor to start of line
    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=1}
    Editor_state.selection1 = {line=2, pos=3}
    -- backspace deletes beginning of line without joining to any other line
    edit.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 selection
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function 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 character
    edit.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'defg', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'baseline/screen:3')
    -- undo
    edit.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.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'screen:3')
    end
    function 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 character
    edit.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'baseline/screen:3')
    -- undo
    --? -- after undo, the backspaced key is selected
    edit.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.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'defg', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'screen:3')
    end
    function test_undo_restores_selection()
    -- display a line of text with some part selected
    App.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 text
    edit.run_after_text_input(Editor_state, 'x')
    check_eq(Editor_state.lines[1].data, 'xbc', 'baseline')
    check_nil(Editor_state.selection1.line, 'baseline:selection')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z', 'z')
    edit.run_after_keychord(Editor_state, 'C-z', 'z')
    -- selection is restored
    check_eq(Editor_state.selection1.line, 1, 'line')
    check_eq(Editor_state.selection1.pos, 2, 'pos')
    end
    function 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 line
    Text.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 string
    edit.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 cursor
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- search for second occurrence
    edit.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')
    end
    function 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 quote
    Text.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 string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_text_input(Editor_state, 'a')
    -- search for previous occurrence
    edit.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')
    end
    function 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 line
    Text.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 string
    edit.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 wraps
    check_eq(Editor_state.cursor1.line, 1, '1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, '1/cursor:pos')
    end
    function 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 quote
    Text.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 string
    edit.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 wraps
    check_eq(Editor_state.cursor1.line, 1, '1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 6, '1/cursor:pos')
    end
    function 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 string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_keychord(Editor_state, 'down', 'down')
    -- no crash
    end
    function 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 string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_keychord(Editor_state, 'down', 'down')
    -- no crash
    end
  • 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')
    end
    function 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 text
    check_eq(#Editor_state.lines, 2, '#lines')
    check_eq(Editor_state.cursor1.line, 2, 'cursor')
    end
    function test_backspace_to_delete_drawing()
    -- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'```lines', '```', ''}
    Text.redraw_all(Editor_state)
    -- cursor is on text as always (outside tests this will get initialized correctly)
    Editor_state.cursor1.line = 2
    -- backspacing deletes the drawing
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    end
    function test_backspace_from_start_of_final_line()
    -- display final line of text with cursor at start of it
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Editor_state.screen_top1 = {line=2, pos=1}
    Editor_state.cursor1 = {line=2, pos=1}
    Text.redraw_all(Editor_state)
    -- backspace scrolls up
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    end
    function test_insert_first_character()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{}
    Text.redraw_all(Editor_state)
    edit.draw(Editor_state)
    edit.run_after_text_input(Editor_state, 'a')
    local y = Editor_state.top
    App.screen.check(y, 'a', 'screen:1')
    end
    function test_press_ctrl()
    -- press ctrl while the cursor is on text
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{''}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.run_after_keychord(Editor_state, 'C-m', 'm')
    end
    function test_move_left()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'a'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_right()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'a'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'right', 'right')
    check_eq(Editor_state.cursor1.pos, 2, 'check')
    end
    function test_move_left_to_previous_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'left', 'left')
    check_eq(Editor_state.cursor1.line, 1, 'line')
    check_eq(Editor_state.cursor1.pos, 4, 'pos') -- past end of line
    end
    function test_move_right_to_next_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- past end of line
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'right', 'right')
    check_eq(Editor_state.cursor1.line, 2, 'line')
    check_eq(Editor_state.cursor1.pos, 1, 'pos')
    end
    function test_move_to_start_of_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_to_start_of_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_skip_to_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=5} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_skip_past_tab_to_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def\tghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=10} -- within third word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 9, 'check')
    end
    function test_skip_multiple_spaces_to_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=6} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_to_start_of_word_on_previous_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.line, 1, 'line')
    check_eq(Editor_state.cursor1.pos, 5, 'pos')
    end
    function test_move_past_end_of_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 4, 'check')
    end
    function test_skip_to_next_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 8, 'check')
    end
    function test_skip_past_tab_to_next_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc\tdef'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 4, 'check')
    end
    function test_skip_multiple_spaces_to_next_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 9, 'check')
    end
    function test_move_past_end_of_word_on_next_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=8}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.line, 2, 'line')
    check_eq(Editor_state.cursor1.pos, 4, 'pos')
    end
    function test_click_moves_cursor()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.startpos for each line
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is empty to avoid perturbing future edits
    check_nil(Editor_state.selection1.line, 'selection:line')
    check_nil(Editor_state.selection1.pos, 'selection:pos')
    end
    function test_click_to_left_of_line()
    -- display a line with the cursor in the middle
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click to the left of the line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1)
    -- cursor moves to start of line
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_takes_margins_into_account()
    -- display two lines with cursor on one of them
    App.screen.init{width=100, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.left = 50 -- occupy only right side of screen
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_on_empty_line()
    -- display two lines with the first one empty
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click on the empty line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    -- selection remains empty
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_below_final_line_of_file()
    -- display one line
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click below first line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+50, 1)
    -- cursor goes to bottom
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    -- selection remains empty
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_draw_text()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_draw_wrapping_text()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'defgh', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'gh', 'screen:3')
    end
    function 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.top
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_click_on_wrapping_line()
    -- display two screen lines with cursor on one of them
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=20}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_on_wrapping_line_takes_margins_into_account()
    -- display two screen lines with cursor on one of them
    App.screen.init{width=100, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.left = 50 -- occupy only right side of screen
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=20}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_draw_text_wrapping_within_word()
    -- arrange a screen line that needs to be split within a word
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abcd e fghijk', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abcd ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'e fghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jk', 'screen:3')
    end
    function test_draw_wrapping_text_containing_non_ascii()
    -- draw a long line containing non-ASCII
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'madam I’m adam', 'xyz'} -- notice the non-ASCII apostrophe
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'mada', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'm I’', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'm ad', 'screen:3')
    end
    function test_click_past_end_of_screen_line()
    -- display a wrapping line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, "I'm ada", 'baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line (one more than final character shown)
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 14, 'cursor:pos')
    end
    function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()
    -- display a wrapping line from its second screen line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=8}
    Editor_state.screen_top1 = {line=1, pos=7}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, "I'm ada", 'baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line (one more than final character shown)
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 14, 'cursor:pos')
    end
    function test_click_past_end_of_wrapping_line()
    -- display a wrapping line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, "I'm ada", 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'm', 'baseline/screen:3')
    y = y + Editor_state.line_height
    -- click past the end of it
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of line
    check_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_wrapping_line_containing_non_ascii()
    -- display a wrapping line containing non-ASCII
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{'madam I’m adam'} -- notice the non-ASCII apostrophe
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'I’m ada', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'm', 'baseline/screen:3')
    y = y + Editor_state.line_height
    -- click past the end of it
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of line
    check_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_word_wrapping_line()
    -- display a long line wrapping at a word boundary on a screen of more realistic length
    App.screen.init{width=160, height=80}
    Editor_state = edit.initialize_test_state()
    -- 0 1 2
    -- 123456789012345678901
    Editor_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.top
    App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    -- click past the end of the screen line
    edit.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')
    end
    function test_select_text()
    -- display a line of text
    App.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 letter
    App.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 released
    check_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')
    end
    function test_cursor_movement_without_shift_resets_selection()
    -- display a line of text with some part selected
    App.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 shift
    edit.run_after_keychord(Editor_state, 'right', 'right')
    -- no change to data, selection is reset
    check_nil(Editor_state.selection1.line, 'check')
    check_eq(Editor_state.lines[1].data, 'abc', 'data')
    end
    function test_edit_deletes_selection()
    -- display a line of text with some part selected
    App.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 key
    edit.run_after_text_input(Editor_state, 'x')
    -- selected text is deleted and replaced with the key
    check_eq(Editor_state.lines[1].data, 'xbc', 'check')
    end
    function test_edit_with_shift_key_deletes_selection()
    -- display a line of text with some part selected
    App.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 letter
    App.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 key
    check_nil(Editor_state.selection1.line, 'check')
    check_eq(Editor_state.lines[1].data, 'Dbc', 'data')
    end
    function test_copy_does_not_reset_selection()
    -- display a line of text with a selection
    App.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 selection
    edit.run_after_keychord(Editor_state, 'C-c', 'c')
    check_eq(App.clipboard, 'a', 'clipboard')
    -- selection is reset since shift key is not pressed
    check(Editor_state.selection1.line, 'check')
    end
    function test_cut()
    -- display a line of text with some part selected
    App.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 key
    edit.run_after_keychord(Editor_state, 'C-x', 'x')
    check_eq(App.clipboard, 'a', 'clipboard')
    -- selected text is deleted
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    end
    function test_paste_replaces_selection()
    -- display a line of text with a selection
    App.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 clipboard
    App.clipboard = 'xyz'
    -- paste selection
    edit.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 deleted
    check_eq(Editor_state.lines[1].data, 'xyzdef', 'check')
    end
    function test_deleting_selection_may_scroll()
    -- display lines 2/3/4
    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=3, pos=2}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- set up a selection starting above the currently displayed page
    Editor_state.selection1 = {line=1, pos=2}
    -- delete selection
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    -- page scrolls up
    check_eq(Editor_state.screen_top1.line, 1, 'check')
    check_eq(Editor_state.lines[1].data, 'ahi', 'data')
    end
    function 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.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'g', 'screen:3')
    end
    function test_insert_newline()
    -- display a few lines
    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'}
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- hitting the enter key splits the line
    edit.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.top
    App.screen.check(y, 'a', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'bc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function test_insert_newline_at_start_of_line()
    -- display a line
    App.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 line
    edit.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')
    end
    function test_insert_from_clipboard()
    -- display a few lines
    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'}
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- paste some text including a newline, check that new line is created
    App.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.top
    App.screen.check(y, 'axy', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'zbc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function 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 location
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- drag and release somewhere else
    edit.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')
    end
    function 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 text
    edit.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')
    end
    function test_select_text_using_mouse_starting_above_text_wrapping_line()
    -- first screen line starts in the middle of a line
    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=2, pos=5}
    Editor_state.screen_top1 = {line=2, pos=3}
    -- press mouse above first line of text
    edit.draw(Editor_state)
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
    -- selection is at screen top
    check(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')
    end
    function 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 screen
    App.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.top
    App.screen.check(y, 'abc', 'baseline:screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'de', 'baseline:screen:2')
    -- press mouse above first line of text
    edit.run_after_mouse_press(Editor_state, 5,App.screen.height-5, 1)
    -- selection is past bottom-most text in screen
    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, 6, 'selection:pos')
    end
    function 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 location
    edit.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 else
    App.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')
    end
    function 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 location
    edit.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 location
    App.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 location
    App.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')
    end
    function test_select_all_text()
    -- display a single line of text
    App.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 all
    App.fake_key_press('lctrl')
    edit.run_after_keychord(Editor_state, 'C-a', 'a')
    App.fake_key_release('lctrl')
    edit.key_release(Editor_state, 'lctrl')
    -- selection
    check_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')
    end
    function test_cut_without_selection()
    -- display a few lines
    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'}
    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 text
    edit.run_after_keychord(Editor_state, 'C-x', 'x')
    -- no crash
    check_nil(Editor_state.selection1.line, 'check')
    end
    function 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 displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    -- after pagedown the bottom line becomes the top
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    end
    function test_pagedown_skips_drawings()
    -- some lines of text with a drawing intermixed
    local drawing_width = 50
    App.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 15
    Text.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 80px
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.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 80px
    edit.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_height
    App.screen.check(y, 'def', 'screen:1')
    end
    function test_pagedown_can_start_from_middle_of_long_wrapping_line()
    -- draw a few lines starting from a very long wrapping line
    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 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.top
    App.screen.check(y, 'abc ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def ', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi j', 'baseline/screen:3')
    -- after pagedown we scroll down the very long wrapping line
    edit.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.top
    App.screen.check(y, 'ghi j', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl m', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'no p', 'screen:3')
    end
    function test_pagedown_never_moves_up()
    -- draw the final screen line of a wrapping line
    App.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 change
    edit.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')
    end
    function 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 displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the down arrow, the cursor moves down by 1 line
    edit.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 unchanged
    y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_down_arrow_skips_drawing()
    -- some lines of text with a drawing intermixed
    local drawing_width = 50
    App.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    local drawing_height = Drawing_padding_height + drawing_width/2 -- default
    y = y + drawing_height
    App.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 drawing
    edit.run_after_keychord(Editor_state, 'down', 'down')
    check_eq(Editor_state.cursor1.line, 3, 'cursor')
    end
    function test_down_arrow_scrolls_down_by_one_line()
    -- display the first three lines with the cursor on the bottom line
    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=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line()
    -- display the first three lines with the cursor on the bottom line
    App.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the down arrow the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()
    -- display the first three lines with the cursor on the bottom line
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghijk', 'baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghijk', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'l', 'screen:3')
    end
    function 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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghijk', 'baseline/screen:3')
    -- after hitting pagedown the screen scrolls down to start of a long line
    edit.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 up
    edit.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.top
    App.screen.check(y, 'ghijk', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'l', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'screen:3')
    end
    function test_up_arrow_moves_cursor()
    -- display the first 3 lines with the cursor on the bottom line
    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=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the up arrow the cursor moves up by 1 line
    edit.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 unchanged
    y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_up_arrow_skips_drawing()
    -- some lines of text with a drawing intermixed
    local drawing_width = 50
    App.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    local drawing_height = Drawing_padding_height + drawing_width/2 -- default
    y = y + drawing_height
    App.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 drawing
    edit.run_after_keychord(Editor_state, 'up', 'up')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    end
    function test_up_arrow_scrolls_up_by_one_line()
    -- display the lines 2/3/4 with the cursor on line 2
    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=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    edit.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
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_up_arrow_scrolls_up_by_one_line_skipping_drawing()
    -- display lines 3/4/5 with a drawing just off screen at line 2
    App.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.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to previous text line
    edit.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')
    end
    function test_up_arrow_scrolls_up_by_one_screen_line()
    -- display lines starting from second screen line of a line
    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=3, pos=6}
    Editor_state.screen_top1 = {line=3, pos=6}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'kl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting the up arrow the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'up', 'up')
    y = Editor_state.top
    App.screen.check(y, 'ghi j', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_up_arrow_scrolls_up_to_final_screen_line()
    -- display lines starting just after a long line
    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=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to final screen line of previous line
    edit.run_after_keychord(Editor_state, 'up', 'up')
    y = Editor_state.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_up_arrow_scrolls_up_to_empty_line()
    -- display a screenful of text with an empty line just above it outside the screen
    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=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    edit.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 line
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function 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 displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    -- after pageup the cursor goes to first line
    edit.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.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    end
    function test_pageup_scrolls_up_by_screen_line()
    -- display the first three lines with the cursor on the bottom line
    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=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    edit.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.top
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_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.top
    App.screen.check(y, 'kl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    edit.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.top
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi j', 'screen:3')
    end
    function test_enter_on_bottom_line_scrolls_down()
    -- display a few lines with cursor on bottom line
    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'}
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the enter key the screen scrolls down
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'g', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'hi', 'screen:3')
    end
    function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    -- display just the bottom line on screen
    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'}
    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.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    -- after hitting the enter key the screen does not scroll down
    edit.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.top
    App.screen.check(y, 'j', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    end
    function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    -- display just an empty bottom line on screen
    App.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 down
    edit.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.top
    App.screen.check(y, 'a', 'screen:1')
    end
    function test_typing_on_bottom_line_scrolls_down()
    -- display a few lines with cursor on bottom line
    App.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after typing something the line wraps and the screen scrolls down
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghijk', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'l', 'screen:3')
    end
    function test_left_arrow_scrolls_up_in_wrapped_line()
    -- display lines starting from second screen line of a line
    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.screen_top1 = {line=3, pos=6}
    -- cursor is at top of screen
    Editor_state.cursor1 = {line=3, pos=6}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'kl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting the left arrow the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'left', 'left')
    y = Editor_state.top
    App.screen.check(y, 'ghi j', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_right_arrow_scrolls_down_in_wrapped_line()
    -- display the first three lines with the cursor on the bottom line
    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.screen_top1 = {line=1, pos=1}
    -- cursor is at bottom right of screen
    Editor_state.cursor1 = {line=3, pos=6}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi j', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the right arrow the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi j', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:3')
    end
    function test_home_scrolls_up_in_wrapped_line()
    -- display lines starting from second screen line of a line
    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.screen_top1 = {line=3, pos=6}
    -- cursor is at top of screen
    Editor_state.cursor1 = {line=3, pos=6}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'kl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting home the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'home', 'home')
    y = Editor_state.top
    App.screen.check(y, 'ghi j', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_end_scrolls_down_in_wrapped_line()
    -- display the first three lines with the cursor on the bottom line
    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.screen_top1 = {line=1, pos=1}
    -- cursor is at bottom right of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi j', 'baseline/screen:3')
    -- after hitting end the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi j', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:3')
    end
    function test_position_cursor_on_recently_edited_wrapping_line()
    -- draw a line wrapping over 2 screen lines
    App.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.top
    App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl mno pqr ', 'baseline1/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'baseline1/screen:3')
    -- add to the line until it's wrapping over 3 screen lines
    edit.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.top
    App.screen.check(y, 'abc def ghi ', 'baseline2/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl mno pqr ', 'baseline2/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'stu', 'baseline2/screen:3')
    -- try to move the cursor earlier in the third screen line by clicking the mouse
    edit.run_after_mouse_release(Editor_state, Editor_state.left+2,Editor_state.top+Editor_state.line_height*2+5, 1)
    -- cursor should move
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 25, 'cursor:pos')
    end
    function test_backspace_can_scroll_up()
    -- display the lines 2/3/4 with the cursor on line 2
    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=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting backspace the screen scrolls up by one line
    edit.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.top
    App.screen.check(y, 'abcdef', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_backspace_can_scroll_up_screen_line()
    -- display lines starting from second screen line of a line
    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=3, pos=6}
    Editor_state.screen_top1 = {line=3, pos=6}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'kl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting backspace the screen scrolls up by one screen line
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    y = Editor_state.top
    App.screen.check(y, 'ghi k', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'l', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_backspace_past_line_boundary()
    -- position cursor at start of a (non-first) line
    App.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 line
    edit.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 keys
    function test_backspace_over_selection()
    -- select just one character within a line with cursor before selection
    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=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    -- backspace deletes the selected character, even though it's after the cursor
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    -- cursor (remains) at start of selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_over_selection_reverse()
    -- select just one character within a line with cursor after selection
    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=1, pos=2}
    Editor_state.selection1 = {line=1, pos=1}
    -- backspace deletes the selected character
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    -- cursor moves to start of selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_over_multiple_lines()
    -- select just one character within a line with cursor after selection
    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=1, pos=2}
    Editor_state.selection1 = {line=4, pos=2}
    -- backspace deletes the region and joins the remaining portions of lines on either side
    edit.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 selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_to_end_of_line()
    -- select region from cursor to end of line
    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=1, pos=2}
    Editor_state.selection1 = {line=1, pos=4}
    -- backspace deletes rest of line without joining to any other line
    edit.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 selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_to_start_of_line()
    -- select region from cursor to start of line
    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=1}
    Editor_state.selection1 = {line=2, pos=3}
    -- backspace deletes beginning of line without joining to any other line
    edit.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 selection
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function 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 character
    edit.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'defg', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'baseline/screen:3')
    -- undo
    edit.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.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'screen:3')
    end
    function 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 character
    edit.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'baseline/screen:3')
    -- undo
    --? -- after undo, the backspaced key is selected
    edit.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.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'defg', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'screen:3')
    end
    function test_undo_restores_selection()
    -- display a line of text with some part selected
    App.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 text
    edit.run_after_text_input(Editor_state, 'x')
    check_eq(Editor_state.lines[1].data, 'xbc', 'baseline')
    check_nil(Editor_state.selection1.line, 'baseline:selection')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z', 'z')
    edit.run_after_keychord(Editor_state, 'C-z', 'z')
    -- selection is restored
    check_eq(Editor_state.selection1.line, 1, 'line')
    check_eq(Editor_state.selection1.pos, 2, 'pos')
    end
    function 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 line
    Text.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 string
    edit.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 cursor
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- search for second occurrence
    edit.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')
    end
    function 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 quote
    Text.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 string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_text_input(Editor_state, 'a')
    -- search for previous occurrence
    edit.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')
    end
    function 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 line
    Text.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 string
    edit.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 wraps
    check_eq(Editor_state.cursor1.line, 1, '1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, '1/cursor:pos')
    end
    function 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 quote
    Text.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 string
    edit.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 wraps
    check_eq(Editor_state.cursor1.line, 1, '1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 6, '1/cursor:pos')
    end
    function 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 string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_keychord(Editor_state, 'down', 'down')
    -- no crash
    end
    function 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 string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_keychord(Editor_state, 'down', 'down')
    -- no crash
    end
  • 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')
    end
    function 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 text
    check_eq(#Editor_state.lines, 2, '#lines')
    check_eq(Editor_state.cursor1.line, 2, 'cursor')
    end
    function test_backspace_to_delete_drawing()
    -- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'```lines', '```', ''}
    Text.redraw_all(Editor_state)
    -- cursor is on text as always (outside tests this will get initialized correctly)
    Editor_state.cursor1.line = 2
    -- backspacing deletes the drawing
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    end
    function test_backspace_from_start_of_final_line()
    -- display final line of text with cursor at start of it
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Editor_state.screen_top1 = {line=2, pos=1}
    Editor_state.cursor1 = {line=2, pos=1}
    Text.redraw_all(Editor_state)
    -- backspace scrolls up
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    end
    function test_insert_first_character()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{}
    Text.redraw_all(Editor_state)
    edit.draw(Editor_state)
    edit.run_after_text_input(Editor_state, 'a')
    local y = Editor_state.top
    App.screen.check(y, 'a', 'screen:1')
    end
    function test_press_ctrl()
    -- press ctrl while the cursor is on text
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{''}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.run_after_keychord(Editor_state, 'C-m', 'm')
    end
    function test_move_left()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'a'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_right()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'a'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'right', 'right')
    check_eq(Editor_state.cursor1.pos, 2, 'check')
    end
    function test_move_left_to_previous_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'left', 'left')
    check_eq(Editor_state.cursor1.line, 1, 'line')
    check_eq(Editor_state.cursor1.pos, 4, 'pos') -- past end of line
    end
    function test_move_right_to_next_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- past end of line
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'right', 'right')
    check_eq(Editor_state.cursor1.line, 2, 'line')
    check_eq(Editor_state.cursor1.pos, 1, 'pos')
    end
    function test_move_to_start_of_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_to_start_of_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_skip_to_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=5} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_skip_past_tab_to_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def\tghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=10} -- within third word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 9, 'check')
    end
    function test_skip_multiple_spaces_to_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=6} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_to_start_of_word_on_previous_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.line, 1, 'line')
    check_eq(Editor_state.cursor1.pos, 5, 'pos')
    end
    function test_move_past_end_of_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 4, 'check')
    end
    function test_skip_to_next_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 8, 'check')
    end
    function test_skip_past_tab_to_next_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc\tdef'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 4, 'check')
    end
    function test_skip_multiple_spaces_to_next_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 9, 'check')
    end
    function test_move_past_end_of_word_on_next_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=8}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.line, 2, 'line')
    check_eq(Editor_state.cursor1.pos, 4, 'pos')
    end
    function test_click_moves_cursor()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.startpos for each line
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is empty to avoid perturbing future edits
    check_nil(Editor_state.selection1.line, 'selection:line')
    check_nil(Editor_state.selection1.pos, 'selection:pos')
    end
    function test_click_to_left_of_line()
    -- display a line with the cursor in the middle
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click to the left of the line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1)
    -- cursor moves to start of line
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_takes_margins_into_account()
    -- display two lines with cursor on one of them
    App.screen.init{width=100, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.left = 50 -- occupy only right side of screen
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_on_empty_line()
    -- display two lines with the first one empty
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click on the empty line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    -- selection remains empty
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_below_final_line_of_file()
    -- display one line
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click below first line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+50, 1)
    -- cursor goes to bottom
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    -- selection remains empty
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_draw_text()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_draw_wrapping_text()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'defgh', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'de', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'fgh', 'screen:3')
    end
    function test_draw_word_wrapping_text()
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_click_on_wrapping_line()
    -- display two screen lines with cursor on one of them
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=20}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_on_wrapping_line_takes_margins_into_account()
    -- display two screen lines with cursor on one of them
    App.screen.init{width=100, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.left = 50 -- occupy only right side of screen
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=20}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_draw_text_wrapping_within_word()
    -- arrange a screen line that needs to be split within a word
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abcd e fghijk', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abcd ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'e fgh', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ijk', 'screen:3')
    end
    function test_draw_wrapping_text_containing_non_ascii()
    -- draw a long line containing non-ASCII
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'madam I’m adam', 'xyz'} -- notice the non-ASCII apostrophe
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'mad', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'am I', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, '’m a', 'screen:3')
    end
    function test_click_past_end_of_screen_line()
    -- display a wrapping line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, "I'm ad", 'baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line (one more than final character shown)
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 13, 'cursor:pos')
    end
    function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()
    -- display a wrapping line from its second screen line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=8}
    Editor_state.screen_top1 = {line=1, pos=7}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, "I'm ad", 'baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line (one more than final character shown)
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 13, 'cursor:pos')
    end
    function test_click_past_end_of_wrapping_line()
    -- display a wrapping line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, "I'm ad", 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'am', 'baseline/screen:3')
    y = y + Editor_state.line_height
    -- click past the end of it
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of line
    check_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_wrapping_line_containing_non_ascii()
    -- display a wrapping line containing non-ASCII
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{'madam I’m adam'} -- notice the non-ASCII apostrophe
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'I’m ad', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'am', 'baseline/screen:3')
    y = y + Editor_state.line_height
    -- click past the end of it
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of line
    check_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_word_wrapping_line()
    -- display a long line wrapping at a word boundary on a screen of more realistic length
    App.screen.init{width=160, height=80}
    Editor_state = edit.initialize_test_state()
    -- 0 1 2
    -- 123456789012345678901
    Editor_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.top
    App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    -- click past the end of the screen line
    edit.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')
    end
    function test_select_text()
    -- display a line of text
    App.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 letter
    App.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 released
    check_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')
    end
    function test_cursor_movement_without_shift_resets_selection()
    -- display a line of text with some part selected
    App.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 shift
    edit.run_after_keychord(Editor_state, 'right', 'right')
    -- no change to data, selection is reset
    check_nil(Editor_state.selection1.line, 'check')
    check_eq(Editor_state.lines[1].data, 'abc', 'data')
    end
    function test_edit_deletes_selection()
    -- display a line of text with some part selected
    App.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 key
    edit.run_after_text_input(Editor_state, 'x')
    -- selected text is deleted and replaced with the key
    check_eq(Editor_state.lines[1].data, 'xbc', 'check')
    end
    function test_edit_with_shift_key_deletes_selection()
    -- display a line of text with some part selected
    App.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 letter
    App.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 key
    check_nil(Editor_state.selection1.line, 'check')
    check_eq(Editor_state.lines[1].data, 'Dbc', 'data')
    end
    function test_copy_does_not_reset_selection()
    -- display a line of text with a selection
    App.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 selection
    edit.run_after_keychord(Editor_state, 'C-c', 'c')
    check_eq(App.clipboard, 'a', 'clipboard')
    -- selection is reset since shift key is not pressed
    check(Editor_state.selection1.line, 'check')
    end
    function test_cut()
    -- display a line of text with some part selected
    App.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 key
    edit.run_after_keychord(Editor_state, 'C-x', 'x')
    check_eq(App.clipboard, 'a', 'clipboard')
    -- selected text is deleted
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    end
    function test_paste_replaces_selection()
    -- display a line of text with a selection
    App.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 clipboard
    App.clipboard = 'xyz'
    -- paste selection
    edit.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 deleted
    check_eq(Editor_state.lines[1].data, 'xyzdef', 'check')
    end
    function test_deleting_selection_may_scroll()
    -- display lines 2/3/4
    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=3, pos=2}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- set up a selection starting above the currently displayed page
    Editor_state.selection1 = {line=1, pos=2}
    -- delete selection
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    -- page scrolls up
    check_eq(Editor_state.screen_top1.line, 1, 'check')
    check_eq(Editor_state.lines[1].data, 'ahi', 'data')
    end
    function 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.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'de', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'fg', 'screen:3')
    end
    function test_insert_newline()
    -- display a few lines
    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'}
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- hitting the enter key splits the line
    edit.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.top
    App.screen.check(y, 'a', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'bc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function test_insert_newline_at_start_of_line()
    -- display a line
    App.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 line
    edit.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')
    end
    function test_insert_from_clipboard()
    -- display a few lines
    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'}
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- paste some text including a newline, check that new line is created
    App.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.top
    App.screen.check(y, 'axy', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'zbc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function 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 location
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- drag and release somewhere else
    edit.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')
    end
    function 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 text
    edit.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')
    end
    function test_select_text_using_mouse_starting_above_text_wrapping_line()
    -- first screen line starts in the middle of a line
    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=2, pos=5}
    Editor_state.screen_top1 = {line=2, pos=3}
    -- press mouse above first line of text
    edit.draw(Editor_state)
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
    -- selection is at screen top
    check(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')
    end
    function 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 screen
    App.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.top
    App.screen.check(y, 'ab', 'baseline:screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'cde', 'baseline:screen:2')
    -- press mouse above first line of text
    edit.run_after_mouse_press(Editor_state, 5,App.screen.height-5, 1)
    -- selection is past bottom-most text in screen
    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, 6, 'selection:pos')
    end
    function 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 location
    edit.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 else
    App.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')
    end
    function 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 location
    edit.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 location
    App.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 location
    App.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')
    end
    function test_select_all_text()
    -- display a single line of text
    App.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 all
    App.fake_key_press('lctrl')
    edit.run_after_keychord(Editor_state, 'C-a', 'a')
    App.fake_key_release('lctrl')
    edit.key_release(Editor_state, 'lctrl')
    -- selection
    check_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')
    end
    function test_cut_without_selection()
    -- display a few lines
    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'}
    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 text
    edit.run_after_keychord(Editor_state, 'C-x', 'x')
    -- no crash
    check_nil(Editor_state.selection1.line, 'check')
    end
    function 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 displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    -- after pagedown the bottom line becomes the top
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    end
    function test_pagedown_skips_drawings()
    -- some lines of text with a drawing intermixed
    local drawing_width = 50
    App.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 15
    Text.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 80px
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.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 80px
    edit.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_height
    App.screen.check(y, 'def', 'screen:1')
    end
    function test_pagedown_can_start_from_middle_of_long_wrapping_line()
    -- draw a few lines starting from a very long wrapping line
    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 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.top
    App.screen.check(y, 'abc ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def ', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'baseline/screen:3')
    -- after pagedown we scroll down the very long wrapping line
    edit.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.top
    App.screen.check(y, 'ghi ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl ', 'screen:2')
    y = y + Editor_state.line_height
    if 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')
    else
    App.screen.check(y, 'mn', 'screen:3')
    end
    end
    function test_pagedown_never_moves_up()
    -- draw the final screen line of a wrapping line
    App.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 change
    edit.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')
    end
    function 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 displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the down arrow, the cursor moves down by 1 line
    edit.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 unchanged
    y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_down_arrow_skips_drawing()
    -- some lines of text with a drawing intermixed
    local drawing_width = 50
    App.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    local drawing_height = Drawing_padding_height + drawing_width/2 -- default
    y = y + drawing_height
    App.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 drawing
    edit.run_after_keychord(Editor_state, 'down', 'down')
    check_eq(Editor_state.cursor1.line, 3, 'cursor')
    end
    function test_down_arrow_scrolls_down_by_one_line()
    -- display the first three lines with the cursor on the bottom line
    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=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line()
    -- display the first three lines with the cursor on the bottom line
    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=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the down arrow the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()
    -- display the first three lines with the cursor on the bottom line
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij', 'baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:3')
    end
    function 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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij', 'baseline/screen:3')
    -- after hitting pagedown the screen scrolls down to start of a long line
    edit.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 up
    edit.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.top
    App.screen.check(y, 'ghij', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'screen:3')
    end
    function test_up_arrow_moves_cursor()
    -- display the first 3 lines with the cursor on the bottom line
    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=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the up arrow the cursor moves up by 1 line
    edit.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 unchanged
    y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_up_arrow_skips_drawing()
    -- some lines of text with a drawing intermixed
    local drawing_width = 50
    App.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    local drawing_height = Drawing_padding_height + drawing_width/2 -- default
    y = y + drawing_height
    App.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 drawing
    edit.run_after_keychord(Editor_state, 'up', 'up')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    end
    function test_up_arrow_scrolls_up_by_one_line()
    -- display the lines 2/3/4 with the cursor on line 2
    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=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    edit.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
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_up_arrow_scrolls_up_by_one_line_skipping_drawing()
    -- display lines 3/4/5 with a drawing just off screen at line 2
    App.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.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to previous text line
    edit.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')
    end
    function test_up_arrow_scrolls_up_by_one_screen_line()
    -- display lines starting from second screen line of a line
    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=3, pos=6}
    Editor_state.screen_top1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting the up arrow the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'up', 'up')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_up_arrow_scrolls_up_to_final_screen_line()
    -- display lines starting just after a long line
    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=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to final screen line of previous line
    edit.run_after_keychord(Editor_state, 'up', 'up')
    y = Editor_state.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_up_arrow_scrolls_up_to_empty_line()
    -- display a screenful of text with an empty line just above it outside the screen
    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=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    edit.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 line
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function 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 displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    -- after pageup the cursor goes to first line
    edit.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.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    end
    function test_pageup_scrolls_up_by_screen_line()
    -- display the first three lines with the cursor on the bottom line
    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=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    edit.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.top
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_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.top
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    edit.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.top
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:3')
    end
    function test_enter_on_bottom_line_scrolls_down()
    -- display a few lines with cursor on bottom line
    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'}
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the enter key the screen scrolls down
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'g', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'hi', 'screen:3')
    end
    function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    -- display just the bottom line on screen
    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'}
    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.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    -- after hitting the enter key the screen does not scroll down
    edit.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.top
    App.screen.check(y, 'j', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    end
    function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    -- display just an empty bottom line on screen
    App.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 down
    edit.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.top
    App.screen.check(y, 'a', 'screen:1')
    end
    function test_typing_on_bottom_line_scrolls_down()
    -- display a few lines with cursor on bottom line
    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'}
    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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after typing something the line wraps and the screen scrolls down
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:3')
    end
    [10.3537]
    [10.64545]
    --
    -- 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 line
    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.screen_top1 = {line=3, pos=5}
    -- cursor is at top of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting the left arrow the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'left', 'left')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    [10.64546]
    [10.66257]
    Version, Major_version = App.love_version()
    if Major_version == 11 then
    load_file_from_source_or_save_directory('source_text_tests_love11.lua')
    elseif Major_version == 12 then
    -- not released/stable yet
    load_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 line
    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.screen_top1 = {line=1, pos=1}
    -- cursor is at bottom right of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the right arrow the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_home_scrolls_up_in_wrapped_line()
    -- display lines starting from second screen line of a line
    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.screen_top1 = {line=3, pos=5}
    -- cursor is at top of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting home the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'home', 'home')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_end_scrolls_down_in_wrapped_line()
    -- display the first three lines with the cursor on the bottom line
    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.screen_top1 = {line=1, pos=1}
    -- cursor is at bottom right of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting end the screen scrolls down by one line
    edit.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.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_position_cursor_on_recently_edited_wrapping_line()
    -- draw a line wrapping over 2 screen lines
    App.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.top
    App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl mno pqr ', 'baseline1/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'baseline1/screen:3')
    -- add to the line until it's wrapping over 3 screen lines
    edit.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.top
    App.screen.check(y, 'abc def ghi ', 'baseline2/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl mno pqr ', 'baseline2/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'stu', 'baseline2/screen:3')
    -- try to move the cursor earlier in the third screen line by clicking the mouse
    edit.run_after_mouse_release(Editor_state, Editor_state.left+2,Editor_state.top+Editor_state.line_height*2+5, 1)
    -- cursor should move
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 25, 'cursor:pos')
    end
    function test_backspace_can_scroll_up()
    -- display the lines 2/3/4 with the cursor on line 2
    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=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting backspace the screen scrolls up by one line
    edit.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.top
    App.screen.check(y, 'abcdef', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_backspace_can_scroll_up_screen_line()
    -- display lines starting from second screen line of a line
    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=3, pos=5}
    Editor_state.screen_top1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting backspace the screen scrolls up by one screen line
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    y = Editor_state.top
    App.screen.check(y, 'ghij', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    y = y + Editor_state.line_height
    App.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')
    end
    function test_backspace_past_line_boundary()
    -- position cursor at start of a (non-first) line
    App.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 line
    edit.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 keys
    function test_backspace_over_selection()
    -- select just one character within a line with cursor before selection
    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=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    -- backspace deletes the selected character, even though it's after the cursor
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    -- cursor (remains) at start of selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_over_selection_reverse()
    -- select just one character within a line with cursor after selection
    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=1, pos=2}
    Editor_state.selection1 = {line=1, pos=1}
    -- backspace deletes the selected character
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    -- cursor moves to start of selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_over_multiple_lines()
    -- select just one character within a line with cursor after selection
    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=1, pos=2}
    Editor_state.selection1 = {line=4, pos=2}
    -- backspace deletes the region and joins the remaining portions of lines on either side
    edit.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 selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_to_end_of_line()
    -- select region from cursor to end of line
    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=1, pos=2}
    Editor_state.selection1 = {line=1, pos=4}
    -- backspace deletes rest of line without joining to any other line
    edit.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 selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_to_start_of_line()
    -- select region from cursor to start of line
    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=1}
    Editor_state.selection1 = {line=2, pos=3}
    -- backspace deletes beginning of line without joining to any other line
    edit.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 selection
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function 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 character
    edit.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'defg', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'baseline/screen:3')
    -- undo
    edit.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.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'screen:3')
    end
    function 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 character
    edit.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.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'baseline/screen:3')
    -- undo
    --? -- after undo, the backspaced key is selected
    edit.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.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'defg', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'screen:3')
    end
    function test_undo_restores_selection()
    -- display a line of text with some part selected
    App.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 text
    edit.run_after_text_input(Editor_state, 'x')
    check_eq(Editor_state.lines[1].data, 'xbc', 'baseline')
    check_nil(Editor_state.selection1.line, 'baseline:selection')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z', 'z')
    edit.run_after_keychord(Editor_state, 'C-z', 'z')
    -- selection is restored
    check_eq(Editor_state.selection1.line, 1, 'line')
    check_eq(Editor_state.selection1.pos, 2, 'pos')
    end
    function 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 line
    Text.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 string
    edit.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 cursor
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- search for second occurrence
    edit.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')
    end
    function 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 quote
    Text.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 string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_text_input(Editor_state, 'a')
    -- search for previous occurrence
    edit.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')
    end
    function 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 line
    Text.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 string
    edit.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 wraps
    check_eq(Editor_state.cursor1.line, 1, '1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, '1/cursor:pos')
    end
    function 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 quote
    Text.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 string
    edit.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 wraps
    check_eq(Editor_state.cursor1.line, 1, '1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 6, '1/cursor:pos')
    end
    function 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 string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_keychord(Editor_state, 'down', 'down')
    -- no crash
    end
    function 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 string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_keychord(Editor_state, 'down', 'down')
    -- no crash
    end
  • edit in source_text.lua at line 71
    [38.366]
    [38.366]
    --? -- 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*') do
    local 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 then
    assert(false, ("Infinite loop while line-wrapping. Editor is %dpx wide; window is %dpx wide"):format(State.width, App.screen.width))
    end
    pos = pos + bpos
    local boffset = Text.offset(frag, bpos+1) -- byte _after_ bpos
    frag = string.sub(frag, boffset)
    --? if bpos > 0 then
    --? print('after chop:', frag)
    --? end
    frag_width = State.font:getWidth(frag)
    end
    --? print('screen line:', pos)
    [10.2428]
    [10.91984]
    for pos,char in utf8chars(line.data) do
    local w = State.font:getWidth(char)
    if Text.should_word_wrap(State, line.data, pos, char, x)
    or x+w > State.width -- truncate within a word
    then
  • replacement in source_text.lua at line 128
    [10.92045][10.3265:3297]()
    x = 0 -- new screen line
    [10.92045]
    [10.92045]
    x = 0
  • replacement in source_text.lua at line 130
    [10.92053][10.92053:92076](),[10.92076][10.3298:3329]()
    x = x + frag_width
    pos = pos + utf8.len(frag)
    [10.92053]
    [10.92137]
    x = x + w
    end
    end
    -- 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 end
    if pos == 1 then return false end
    if Text.match(line, pos-1, '%S') then return false end
    local offset = Text.offset(line, pos)
    -- most of the time a word is printable chars + whitespace
    local s = line:match('%S+%s*', offset)
    assert(s)
    local w = editor.font:getWidth(s)
    if x+w < editor.width then return false end
    if w > editor.width then return false end -- we're going to need to truncate the next word anyway
    if x < 0.8*editor.width then
    local 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 line
    return false
    end
  • edit in source_text.lua at line 169
    [10.92143]
    [10.92143]
    return true
  • replacement in source_text.lua at line 252
    [10.21212][10.98809:98843](),[10.98809][10.98809:98843]()
    --== shortcuts that mutate text
    [10.21212]
    [10.98843]
    --== 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 codepoint
    function utf8chars(s, startpos)
    local next_pos = startpos or 1 -- in code points
    local next_offset = utf8.offset(s, next_pos) -- in bytes
    return function()
    assert(next_offset) -- never call the iterator after it returns nil
    local curr_pos = next_pos
    next_pos = next_pos+1
    local curr_offset = next_offset
    next_offset = utf8.offset(s, 2, next_offset)
    if next_offset == nil then return end
    local curr_char = s:sub(curr_offset, next_offset-1)
    return curr_pos, curr_char
    end
  • replacement in source_edit.lua at line 545
    [10.163903][10.163903:163958]()
    function edit.update_font_settings(State, font_height)
    [10.163903]
    [10.163958]
    function edit.update_font_settings(State, font_height, font)
  • replacement in source_edit.lua at line 547
    [10.163992][10.238:294]()
    State.font = love.graphics.newFont(State.font_height)
    [10.163992]
    [10.164065]
    State.font = font or love.graphics.newFont(State.font_height)