split up editor tests between LÖVE 11 and LÖVE 12

akkartik
Aug 16, 2025, 5:23 PM
F4RUTONDM6GET6RT6ZBJKHJWJZWHJLCI2NM5ZMOFJGWAOEUCBDQQC

Dependencies

  • [2] TUN6TDO4 give a test a unique name
  • [3] KVJTEXMS fix all tests in LÖVE v12
  • [4] GJ4LBCIE streamline button.lua
  • [5] J7A5ROM3 bugfix in cursor positioning
  • [6] 2O4OXP5X comment
  • [7] 4FTOQOPZ bugfix #2 in search UI
  • [8] 26V2B2QH clarify a misleading test
  • [9] VG75U7IM bugfix: typing should delete highlighted text
  • [10] QAMVLUK2 fix a crash involving mouse and drawings
  • [11] T3B4NLV3 include a unit test
  • [12] DSLD74DK lots more tests
  • [13] 3TCZ7ADH move
  • [14] 4WAFGF4Z selection bugfix
  • [15] LK4ZW4BB bugfix
  • [16] 7NQCCB34 .
  • [17] LXTTOB33 extract a couple of files
  • [18] JFFUF5AL override mouse state lookups in tests
  • [19] 5DOTWNVM right margin
  • [20] AJP4OSTJ new test
  • [21] YFW4MNNP handle wrapping lines
  • [22] MYC7XR5Q bugfix: lines that aren't drawn from the start
  • [23] VSBSWTE4 bugfix: where cursor is drawn
  • [24] CIQN2MDE bugfix: typing a capital letter deletes selection
  • [25] FYS7TCDW bugfix
  • [26] GJLOKCYK bugfix: clicking past end of screen line
  • [27] QKAMUWSB another bugfix in scrolling while inserting text
  • [28] UHB4GARJ left/right margin -> left/right coordinates
  • [29] 7XERS4UF more decoupling editor tests from App
  • [30] EKKFWP4D bugfix: couple of margin-relative computations
  • [31] M6TH7VSZ rip out notion of Line_width
  • [32] LF7BWEG4 group all editor globals
  • [33] AQQQNDTL yet another bugfix in selection management
  • [34] XI5OALQX some redundant calls
  • [35] DGK5BPVI bugfix: UTF-8 in compute_fragments
  • [36] APYPFFS3 call edit rather than App callbacks in tests
  • [37] EETIR4GX bugfix: skip over drawings when searching
  • [38] 5BMR5HRT click to the left of a line
  • [39] GDAWPFAV more streamlined test names
  • [40] CUIV2LE5 some typos
  • [41] KMRJOSLY bugfix: delete selection before pasting
  • [42] GL4Q5WCV keep text from overflowing right margin
  • [43] LLAOOMUL bugfix: search upwards
  • [44] CVSRHMJ2 experiment: slightly adaptive scrolling
  • [45] RMKMPFT5 fix a corner case when selecting text
  • [46] WAR3HXHT test both ways of selecting text with mouse
  • [47] C45WCXJ2 keep drawings within the line width slider as well
  • [48] 4J2L6JMR bugfix: deleting a selection spanning pages
  • [49] 2JLVAYHB start decoupling editor tests from App
  • [50] IFTYOERM line.y -> line_cache.starty in a few more places
  • [51] 62JEPVQ3 bugfix: backspace from start of final line
  • [52] HPVT467W initialize contains test state
  • [53] AMOPICKV bugfix: check after cursor on same line when searching upwards
  • [54] 4VKEE43Z bugfix
  • [55] DFGPHG5T overzealous search-and-replace
  • [56] ORRSP7FV deduce test names on failures
  • [57] 3OC7AIC7 exclude left margin from my word-split heuristic
  • [58] 2CK5QI7W make love event names consistent
  • [59] WPNRIC7D more decoupling editor tests from App
  • [60] FZBXBUFF bugfix: search
  • [61] SWZAQHGR bugfix: up arrow when line above is a drawing
  • [62] LNUHQOGH start passing in Editor_state explicitly
  • [63] AOIRVVJA revert selection logic to before commit 3ffc2ed8f
  • [64] Y4SPXCM3 bugfix: pagedown was sometimes bouncing up
  • [65] MSOQI3A5 bugfix: check before cursor on same line
  • [66] 7EQLPB3O bugfix: don't delete selection when moving cursor
  • [67] HTWAM4NZ bugfix: scrolling in left/right movements
  • [68] 2LC3BM2N support other whitespace chars in word movements
  • [69] TGZAJUEF bring back a set of constants
  • [70] ZLJGZYQG select text with shift + mouseclick
  • [71] IWYLK45K clicking to the right of a line within line width
  • [72] NUZFHX6I flesh out some tests for word movements
  • [73] CNCYMM6A make test initializations a little more obvious
  • [74] 356GY7IQ unify two similar functions
  • [75] I64IPGJX avoid saving fragments in lines
  • [76] 6XCJX4DZ bugfix: inscript's bug
  • [77] 7RKFA3VA failing test now looks realistic
  • [78] GNKUD23I get rid of recent_mouse
  • [79] FHNPQBLK more carefully pass the 'key' arg around
  • [80] LAW2O3NW extract variable Margin_left
  • [81] 656FM555 bugfix: clear selection when clicking above or below lines
  • [82] 2TCIWW6Z stop caching starty
  • [83] FQZ3U3YA streamline one more test name
  • [84] EMHRPJ3R no, that's not right
  • [85] V3EABA35 skip multiple consecutive whitespace
  • [86] EXGM6MIE delete some duplicate initialization
  • [87] 2MPQO2ST .
  • [88] ZZ2B5RPQ extract variables for drawing padding
  • [89] DRFE3B3Z mouse buttons are integers, not strings
  • [90] P6SYWBLB remove a duplicate test
  • [91] KOTI3MFG bugfix in previous commit
  • [92] 2H76FV5S bugfix: searching files containing unicode
  • [93] ILOA5BYF separate data structure for each line's cache data
  • [94] S2YQBEYC snapshot: test for a new regression
  • [95] NZKYPBSK check for scroll when just typing
  • [96] 6RYLD5ON change how we handle clicks above top margin
  • [97] ZS5IYZH5 stop caching screen_bottom1
  • [98] H3ECRBXF bugfix: clicking on empty lines
  • [99] JLU2RMC4 allow Text.nearest_pos_less_than to return 0
  • [100] O7YTBRQY bugfix: restart search on backspace
  • [101] Y2ZIPXEM new test
  • [*] R5QXEHUI somebody stop me

Change contents

  • file addition: text_tests_love12.lua (----------)
    [103.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, '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
    App.screen.check(y, 'mno ', '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', '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
    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')
    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=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
  • file addition: text_tests_love11.lua (----------)
    [103.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, '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
    App.screen.check(y, 'mn', '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', '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
    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')
    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=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
  • replacement in text_tests.lua at line 2
    [9.41][9.2:107](),[9.107][9.2:3](),[9.92][9.2:3](),[9.3][9.2:32](),[9.67][9.67:107](),[9.107][9.2:48](),[9.48][9.1104:1140](),[9.106][9.1104:1140](),[9.107][9.1104:1140](),[9.1140][9.315:347](),[9.347][9.2:28](),[9.1140][9.2:28](),[9.28][9.2:283](),[9.283][9.474:519](),[9.1623][9.474:519](),[9.474][9.474:519](),[9.519][9.0:41](),[9.41][9.49:95](),[9.604][9.49:95](),[9.95][9.1624:1660](),[9.211][9.1624:1660](),[9.604][9.1624:1660](),[9.1660][9.348:380](),[9.380][9.29:55](),[9.1660][9.29:55](),[9.55][9.212:280](),[9.77][9.687:738](),[9.280][9.687:738](),[9.1807][9.687:738](),[9.687][9.687:738](),[9.738][9.284:380](),[9.380][9.874:923](),[9.1970][9.874:923](),[9.874][9.874:923](),[9.972][9.972:1115](),[9.1115][9.96:142](),[9.142][9.1971:2028](),[9.385][9.1971:2028](),[9.1115][9.1971:2028](),[9.2028][9.381:413](),[9.145][9.1159:1244](),[9.413][9.1159:1244](),[9.2114][9.1159:1244](),[9.1159][9.1159:1244](),[9.1244][9.2115:2147](),[9.2147][9.1263:1300](),[9.1263][9.1263:1300](),[9.1300][9.2:68](),[9.68][9.381:477](),[9.131][9.381:477](),[9.477][9.2:58](),[9.2318][9.2:58](),[9.114][9.114:451](),[9.451][9.69:135](),[9.135][9.478:633](),[9.504][9.478:633](),[9.633][9.1482:1487](),[9.791][9.1482:1487](),[9.2318][9.1482:1487](),[9.1482][9.1482:1487](),[9.1487][9.3:42](),[9.3][9.3:42](),[9.86][9.86:126](),[9.126][9.143:189](),[9.189][9.2319:2355](),[9.490][9.2319:2355](),[9.126][9.2319:2355](),[9.2355][9.414:446](),[9.446][9.56:82](),[9.2355][9.56:82](),[9.82][9.2:49](),[9.49][9.491:520](),[9.178][9.491:520](),[9.520][9.634:673](),[9.673][9.2:34](),[9.287][9.2:34](),[9.66][9.66:149](),[9.149][9.190:236](),[9.236][9.2479:2517](),[9.625][9.2479:2517](),[9.149][9.2479:2517](),[9.2517][9.447:479](),[9.479][9.2603:2689](),[9.2603][9.2603:2689](),[9.2724][9.136:188](),[9.188][9.2:33](),[9.226][9.2:33](),[9.320][9.2:33](),[9.64][9.83:123](),[9.123][9.237:283](),[9.123][9.2725:2764](),[9.283][9.2725:2764](),[9.730][9.2725:2764](),[9.104][9.2725:2764](),[9.2764][9.480:512](),[9.512][9.2764:2805](),[9.2764][9.2764:2805](),[9.2805][9.124:150](),[9.150][9.189:245](),[9.245][9.674:723](),[9.275][9.674:723](),[9.723][9.300:332](),[9.2954][9.300:332](),[9.300][9.300:332](),[9.364][9.364:404](),[9.404][9.284:330](),[9.330][9.2955:2994](),[9.835][9.2955:2994](),[9.404][9.2955:2994](),[9.2994][9.513:545](),[9.545][9.2994:3035](),[9.2994][9.2994:3035](),[9.3035][9.151:177](),[9.177][9.246:304](),[9.304][9.724:773](),[9.325][9.724:773](),[9.320][9.287:291](),[9.602][9.287:291](),[9.773][9.287:291](),[9.3185][9.287:291](),[9.287][9.287:291](),[9.291][9.92:93](),[9.92][9.92:93](),[9.93][9.603:646](),[9.694][9.694:734](),[9.734][9.331:377](),[9.377][9.3186:3234](),[9.940][9.3186:3234](),[9.734][9.3186:3234](),[9.3234][9.546:578](),[9.578][9.3234:3275](),[9.3234][9.3234:3275](),[9.3275][9.178:204](),[9.204][9.305:361](),[9.361][9.774:891](),[9.374][9.774:891](),[9.891][9.1053:1098](),[9.3551][9.1053:1098](),[9.1053][9.1053:1098](),[9.1143][9.1143:1183](),[9.1183][9.378:424](),[9.424][9.3552:3600](),[9.1045][9.3552:3600](),[9.1183][9.3552:3600](),[9.3600][9.579:611](),[9.611][9.3600:3662](),[9.3600][9.3600:3662](),[9.3662][9.205:231](),[9.231][9.362:420](),[9.420][9.892:988](),[9.424][9.892:988](),[9.988][9.1497:1540](),[9.3911][9.1497:1540](),[9.1497][9.1497:1540](),[9.1583][9.1583:1623](),[9.1623][9.425:471](),[9.471][9.3912:3953](),[9.1150][9.3912:3953](),[9.1623][9.3912:3953](),[9.3953][9.612:644](),[9.644][9.3953:3994](),[9.3953][9.3953:3994](),[9.3994][9.232:258](),[9.258][9.421:479](),[9.479][9.989:1038](),[9.475][9.989:1038](),[9.1038][9.1835:1887](),[9.4155][9.1835:1887](),[9.1835][9.1835:1887](),[9.1939][9.1939:1979](),[9.1979][9.472:518](),[9.518][9.4156:4201](),[9.1255][9.4156:4201](),[9.1979][9.4156:4201](),[9.4201][9.645:677](),[9.677][9.4201:4273](),[9.4201][9.4201:4273](),[9.4273][9.259:285](),[9.285][9.480:538](),[9.538][9.1039:1088](),[9.526][9.1039:1088](),[9.1088][9.2235:2278](),[9.4443][9.2235:2278](),[9.2235][9.2235:2278](),[9.2321][9.2321:2361](),[9.2361][9.519:565](),[9.565][9.4444:4489](),[9.1360][9.4444:4489](),[9.2361][9.4444:4489](),[9.4489][9.678:710](),[9.710][9.4489:4562](),[9.4489][9.4489:4562](),[9.4562][9.286:312](),[9.312][9.539:597](),[9.597][9.1089:1138](),[9.577][9.1089:1138](),[9.1138][9.2:54](),[9.4723][9.2:54](),[9.2609][9.2:54](),[9.106][9.106:146](),[9.146][9.566:612](),[9.612][9.4724:4774](),[9.1465][9.4724:4774](),[9.146][9.4724:4774](),[9.4774][9.711:743](),[9.743][9.4774:4838](),[9.4774][9.4774:4838](),[9.4838][9.313:339](),[9.339][9.598:656](),[9.656][9.1139:1188](),[9.628][9.1139:1188](),[9.1188][9.2:61](),[9.5008][9.2:61](),[9.399][9.2:61](),[9.120][9.120:160](),[9.160][9.613:659](),[9.659][9.5009:5055](),[9.1570][9.5009:5055](),[9.160][9.5009:5055](),[9.5055][9.744:776](),[9.776][9.5055:5128](),[9.5055][9.5055:5128](),[9.5128][9.340:366](),[9.366][9.657:715](),[9.715][9.1189:1238](),[9.679][9.1189:1238](),[9.399][9.2609:2669](),[9.425][9.2609:2669](),[9.1238][9.2609:2669](),[9.5305][9.2609:2669](),[9.2609][9.2609:2669](),[9.2729][9.2729:2769](),[9.2769][9.660:706](),[9.706][9.5306:5358](),[9.1675][9.5306:5358](),[9.2769][9.5306:5358](),[9.5358][9.777:809](),[9.809][9.5358:5399](),[9.5358][9.5358:5399](),[9.5399][9.367:393](),[9.393][9.716:774](),[9.774][9.1239:1335](),[9.730][9.1239:1335](),[9.1335][9.3097:3140](),[9.5678][9.3097:3140](),[9.3097][9.3097:3140](),[9.3183][9.3183:3223](),[9.3223][9.707:753](),[9.753][9.5679:5724](),[9.1780][9.5679:5724](),[9.3223][9.5679:5724](),[9.5724][9.810:842](),[9.842][9.5724:5765](),[9.5724][9.5724:5765](),[9.5765][9.394:420](),[9.420][9.775:835](),[9.835][9.1336:1385](),[9.782][9.1336:1385](),[9.1385][9.3440:3479](),[9.5926][9.3440:3479](),[9.3440][9.3440:3479](),[9.3518][9.3518:3558](),[9.3558][9.754:800](),[9.800][9.5927:5972](),[9.1885][9.5927:5972](),[9.3558][9.5927:5972](),[9.5972][9.843:875](),[9.875][9.5972:6044](),[9.5972][9.5972:6044](),[9.6044][9.421:447](),[9.447][9.836:896](),[9.896][9.1386:1435](),[9.834][9.1386:1435](),[9.1435][9.3802:3807](),[9.6201][9.3802:3807](),[9.3802][9.3802:3807](),[9.3807][9.400:443](),[9.491][9.491:531](),[9.531][9.801:847](),[9.847][9.6202:6248](),[9.1990][9.6202:6248](),[9.531][9.6202:6248](),[9.6248][9.876:908](),[9.908][9.6248:6320](),[9.6248][9.6248:6320](),[9.6320][9.448:474](),[9.474][9.897:957](),[9.957][9.1436:1485](),[9.886][9.1436:1485](),[9.1485][9.785:790](),[9.6486][9.785:790](),[9.785][9.785:790](),[9.790][9.426:476](),[9.531][9.531:571](),[9.571][9.848:894](),[9.894][9.6487:6533](),[9.2095][9.6487:6533](),[9.571][9.6487:6533](),[9.6533][9.909:941](),[9.941][9.6533:6606](),[9.6533][9.6533:6606](),[9.6606][9.475:501](),[9.501][9.958:1018](),[9.1018][9.1486:1535](),[9.938][9.1486:1535](),[9.1535][9.833:838](),[9.6779][9.833:838](),[9.833][9.833:838](),[9.790][9.3807:3858](),[9.838][9.3807:3858](),[9.3807][9.3807:3858](),[9.3914][9.3914:3954](),[9.3954][9.895:941](),[9.941][9.6780:6832](),[9.2200][9.6780:6832](),[9.3954][9.6780:6832](),[9.6832][9.942:974](),[9.974][9.6832:6873](),[9.6832][9.6832:6873](),[9.6873][9.502:528](),[9.528][9.1019:1079](),[9.1079][9.1536:1632](),[9.990][9.1536:1632](),[9.1632][9.4275:4280](),[9.7144][9.4275:4280](),[9.4275][9.4275:4280](),[9.4280][9.2:37](),[9.77][9.2:41](),[9.40][9.2:41](),[9.41][9.942:988](),[9.151][9.942:988](),[9.988][9.42:97](),[9.97][9.975:1007](),[9.7193][9.975:1007](),[9.1007][9.98:139](),[9.139][9.7320:7365](),[9.7320][9.7320:7365](),[9.7400][9.140:171](),[9.171][6.2:75](),[6.75][9.266:354](),[9.97][9.266:354](),[9.266][9.266:354](),[9.354][9.1633:1743](),[9.244][9.435:492](),[9.1743][9.435:492](),[9.435][9.435:492](),[9.492][9.1744:1862](),[9.112][9.2:7](),[9.419][9.2:7](),[9.662][9.2:7](),[9.1862][9.2:7](),[9.7687][9.2:7](),[9.105][9.2:7](),[9.7][9.420:458](),[9.501][9.105:399](),[9.105][9.105:399](),[9.434][9.2:33](),[9.33][9.434:616](),[9.434][9.434:616](),[9.616][9.1863:2070](),[9.802][9.949:954](),[9.2070][9.949:954](),[9.949][9.949:954](),[9.954][9.803:852](),[9.906][9.290:654](),[9.290][9.290:654](),[9.689][9.34:65](),[9.65][9.689:848](),[9.689][9.689:848](),[9.848][9.2071:2278](),[9.1240][9.1214:1219](),[9.2278][9.1214:1219](),[9.1214][9.1214:1219](),[9.1219][9.1241:1277](),[9.1318][9.106:193](),[9.106][9.106:193](),[9.193][9.989:1035](),[9.1035][9.7688:7733](),[9.2497][9.7688:7733](),[9.193][9.7688:7733](),[9.7733][9.1008:1040](),[9.1040][9.7819:7905](),[9.7819][9.7819:7905](),[9.7940][9.66:97](),[9.97][9.354:383](),[9.7940][9.354:383](),[9.354][9.354:383](),[9.383][9.556:582](),[9.582][9.2498:2584](),[9.1192][9.455:473](),[9.2584][9.455:473](),[9.8026][9.455:473](),[9.455][9.455:473](),[9.473][9.98:280](),[9.280][8.0:47](),[8.47][9.318:584](),[9.318][9.318:584](),[9.619][9.619:791](),[9.791][8.48:185](),[8.185][9.817:943](),[9.2330][9.817:943](),[9.105][9.524:529](),[9.551][9.524:529](),[9.943][9.524:529](),[9.1399][9.524:529](),[9.2330][9.524:529](),[9.8118][9.524:529](),[9.524][9.524:529](),[9.529][9.93:119](),[9.93][9.93:119](),[9.150][9.150:190](),[9.190][9.1036:1082](),[9.1082][9.8119:8174](),[9.2689][9.8119:8174](),[9.190][9.8119:8174](),[9.8174][9.1041:1073](),[9.1073][9.8260:8346](),[9.8260][9.8260:8346](),[9.8381][9.583:609](),[9.609][9.2690:2719](),[9.2719][9.2331:2372](),[9.2372][9.8419:8454](),[9.442][9.8419:8454](),[9.8454][9.2373:2414](),[9.2414][9.8455:8490](),[9.524][9.8455:8490](),[9.8490][9.2415:2456](),[9.2456][9.606:646](),[9.606][9.606:646](),[9.686][9.686:725](),[9.725][9.1083:1129](),[9.1129][9.8491:8548](),[9.2824][9.8491:8548](),[9.725][9.8491:8548](),[9.8548][9.1074:1106](),[9.1106][9.8634:8720](),[9.8634][9.8634:8720](),[9.8755][9.610:636](),[9.636][9.2825:2854](),[9.2854][9.2457:2498](),[9.2498][9.8793:8828](),[9.988][9.8793:8828](),[9.8828][9.2499:2539](),[9.70][9.8829:8864](),[9.2539][9.8829:8864](),[9.1079][9.8829:8864](),[9.8864][9.2540:2581](),[9.140][9.1169:1214](),[9.2581][9.1169:1214](),[9.1169][9.1169:1214](),[9.1259][9.1259:1298](),[9.1298][9.1130:1176](),[9.1176][9.8865:8921](),[9.2959][9.8865:8921](),[9.1298][9.8865:8921](),[9.8921][9.1107:1139](),[9.1139][9.9007:9093](),[9.9007][9.9007:9093](),[9.9128][9.637:663](),[9.663][9.2960:2989](),[9.2989][9.2582:2624](),[9.2624][9.9166:9201](),[9.1566][9.9166:9201](),[9.9201][9.2625:2667](),[9.2667][9.9202:9237](),[9.1663][9.9202:9237](),[9.9237][9.2668:2709](),[9.2709][9.1220:1225](),[9.1759][9.1220:1225](),[9.1225][9.1400:1439](),[9.1439][2.2:59](),[2.59][9.1380:1649](),[9.1380][9.1380:1649](),[9.1684][9.1684:1843](),[9.1843][9.2710:2917](),[9.1787][9.1759:1764](),[9.2179][9.1759:1764](),[9.2917][9.1759:1764](),[9.1759][9.1759:1764](),[9.1764][9.1788:1854](),[9.1854][2.60:117](),[2.117][9.2389:2721](),[9.2389][9.2389:2721](),[9.2756][9.2756:2915](),[9.2915][9.2918:3125](),[9.2310][9.3332:3337](),[9.3125][9.3332:3337](),[9.3332][9.3332:3337](),[9.3337][9.1764:1875](),[9.1764][9.1764:1875](),[9.1927][9.1927:1966](),[9.1966][9.1177:1223](),[9.1223][9.9238:9296](),[9.3094][9.9238:9296](),[9.1966][9.9238:9296](),[9.9296][9.1140:1172](),[9.1172][9.9382:9468](),[9.9382][9.9382:9468](),[9.9503][9.664:690](),[9.690][9.3095:3124](),[9.3124][9.3126:3169](),[9.3169][9.9541:9576](),[9.2244][9.9541:9576](),[9.9576][9.3170:3213](),[9.224][9.9577:9612](),[9.3213][9.9577:9612](),[9.2350][9.9577:9612](),[9.9612][9.3214:3255](),[9.306][9.2:106](),[9.3255][9.2:106](),[9.2452][9.2:106](),[9.167][9.167:206](),[9.206][9.1224:1270](),[9.1270][9.9613:9710](),[9.3229][9.9613:9710](),[9.206][9.9613:9710](),[9.9710][9.1173:1205](),[9.1205][9.9796:9882](),[9.9796][9.9796:9882](),[9.9917][9.691:717](),[9.717][9.3230:3259](),[9.3259][9.3256:3297](),[9.397][9.9955:9990](),[9.3297][9.9955:9990](),[9.531][9.9955:9990](),[9.9990][9.3298:3340](),[9.93][9.9991:10026](),[9.488][9.9991:10026](),[9.3340][9.9991:10026](),[9.646][9.9991:10026](),[9.10026][9.3341:3385](),[9.187][9.2452:2457](),[9.582][9.2452:2457](),[9.759][9.2452:2457](),[9.3385][9.2452:2457](),[9.2452][9.2452:2457](),[9.2457][2.118:164](),[2.164][9.85:114](),[9.85][9.85:114](),[9.114][9.2:41](),[9.41][9.1271:1317](),[9.1317][9.1206:1256](),[9.1256][9.10027:10079](),[9.190][9.10027:10079](),[9.10079][9.1257:1289](),[9.1289][9.10165:10251](),[9.10165][9.10165:10251](),[9.10286][9.718:744](),[9.744][9.3365:3394](),[9.3394][9.3386:3439](),[9.3439][9.10324:10359](),[9.450][9.10324:10359](),[9.10359][9.3440:3493](),[9.668][9.10360:10395](),[9.3493][9.10360:10395](),[9.558][9.10360:10395](),[9.10395][9.580:622](),[9.580][9.580:622](),[9.622][9.1193:1263](),[9.1263][5.0:78](),[5.78][9.3494:3550](),[9.730][9.3494:3550](),[9.3550][5.79:134](),[5.134][9.879:884](),[9.756][9.879:884](),[9.3605][9.879:884](),[9.10571][9.879:884](),[9.879][9.879:884](),[9.884][9.2:80](),[9.163][9.163:220](),[9.220][9.42:81](),[9.81][9.1318:1364](),[9.1364][9.1290:1340](),[9.1340][9.10572:10624](),[9.296][9.10572:10624](),[9.10624][9.1341:1373](),[9.1373][9.10710:10796](),[9.10710][9.10710:10796](),[9.10831][9.745:771](),[9.771][9.3500:3529](),[9.3529][9.3606:3659](),[9.881][9.10869:10904](),[9.3659][9.10869:10904](),[9.596][9.10869:10904](),[9.10904][9.618:660](),[9.618][9.618:660](),[9.660][9.1264:1334](),[9.1334][5.135:213](),[5.213][9.3660:3716](),[9.755][9.3660:3716](),[9.3716][5.214:269](),[5.269][9.982:987](),[9.1008][9.982:987](),[9.3771][9.982:987](),[9.11158][9.982:987](),[9.982][9.982:987](),[9.987][9.884:932](),[9.884][9.884:932](),[9.985][9.985:1014](),[9.1014][9.82:121](),[9.121][9.1365:1411](),[9.1411][9.1374:1424](),[9.1424][9.11159:11211](),[9.1090][9.11159:11211](),[9.11211][9.1425:1457](),[9.1457][9.11297:11383](),[9.11297][9.11297:11383](),[9.11418][9.772:798](),[9.798][9.3635:3664](),[9.3664][9.3772:3825](),[9.3825][9.11456:11491](),[9.1359][9.11456:11491](),[9.11491][9.3826:3879](),[9.1103][9.11492:11527](),[9.3879][9.11492:11527](),[9.1476][9.11492:11527](),[9.11527][9.3880:3929](),[9.1194][9.11528:11563](),[9.3929][9.11528:11563](),[9.1587][9.11528:11563](),[9.11563][9.1609:1639](),[9.1609][9.1609:1639](),[9.1639][9.1335:1405](),[9.1405][9.1707:1740](),[9.1707][9.1707:1740](),[9.1740][9.3930:4031](),[9.4031][9.1869:1874](),[9.11706][9.1869:1874](),[9.1869][9.1869:1874](),[9.1874][9.1195:1264](),[9.1338][9.127:177](),[9.127][9.127:177](),[9.177][9.122:161](),[9.161][9.1412:1458](),[9.1458][9.1458:1508](),[9.1508][9.11707:11797](),[9.253][9.11707:11797](),[9.11797][9.1509:1541](),[9.1541][9.11883:11969](),[9.11883][9.11883:11969](),[9.12004][9.799:825](),[9.825][9.3770:3799](),[9.3799][9.4032:4085](),[9.1454][9.12042:12077](),[9.4085][9.12042:12077](),[9.572][9.12042:12077](),[9.12077][9.4086:4141](),[9.1572][9.12078:12113](),[9.4141][9.12078:12113](),[9.703][9.12078:12113](),[9.12113][9.4142:4191](),[9.1684][9.12114:12149](),[9.4191][9.12114:12149](),[9.826][9.12114:12149](),[9.12149][9.848:878](),[9.848][9.848:878](),[9.878][9.1406:1476](),[9.124][9.948:981](),[9.1476][9.948:981](),[9.948][9.948:981](),[9.981][9.4192:4293](),[9.1848][9.2:60](),[9.4293][9.2:60](),[9.12304][9.2:60](),[9.1122][9.2:60](),[9.118][9.118:208](),[9.208][9.162:202](),[9.202][9.1459:1505](),[9.1505][9.1542:1655](),[9.1655][9.12305:12387](),[9.335][9.12305:12387](),[9.12387][9.1656:1688](),[9.1688][9.12473:12559](),[9.12473][9.12473:12559](),[9.12594][9.826:852](),[9.852][9.3905:3934](),[9.3934][9.4294:4361](),[9.4361][9.12632:12667](),[9.654][9.12632:12667](),[9.12667][9.203:246](),[9.676][9.203:246](),[9.246][9.1477:1547](),[9.1547][5.270:399](),[5.399][9.2:35](),[9.927][9.2:35](),[9.4413][9.2:35](),[9.12765][9.2:35](),[9.1122][9.2:35](),[9.68][9.68:96](),[9.96][9.302:341](),[9.341][9.1506:1552](),[9.1552][9.12766:12811](),[9.4039][9.12766:12811](),[9.341][9.12766:12811](),[9.12811][9.1689:1721](),[9.1721][9.12897:12983](),[9.12897][9.12897:12983](),[9.13018][9.853:879](),[9.466][9.280:332](),[9.879][9.280:332](),[9.280][9.280:332](),[9.332][9.1080:1140](),[9.1140][9.368:401](),[9.1599][9.368:401](),[9.368][9.368:401](),[9.401][9.50:93](),[9.93][9.2:55](),[9.55][9.4414:4646](),[9.4646][9.2:70](),[9.13335][9.2:70](),[9.746][9.2:70](),[9.138][9.138:190](),[9.190][9.342:381](),[9.381][9.1553:1599](),[9.1599][9.13336:13377](),[9.4144][9.13336:13377](),[9.381][9.13336:13377](),[9.13377][9.1722:1754](),[9.1754][9.13463:13593](),[9.13463][9.13463:13593](),[9.13628][9.880:906](),[9.481][9.401:439](),[9.906][9.401:439](),[9.401][9.401:439](),[9.439][9.1141:1199](),[9.1199][9.473:516](),[9.1649][9.473:516](),[9.473][9.473:516](),[9.516][9.4647:4752](),[9.701][9.2:7](),[9.746][9.2:7](),[9.4752][9.2:7](),[9.13840][9.2:7](),[9.1122][9.2:7](),[9.1127][9.2:41](),[9.85][9.85:137](),[9.137][9.422:461](),[9.461][9.1600:1646](),[9.1646][9.13841:13882](),[9.4249][9.13841:13882](),[9.461][9.13841:13882](),[9.13882][9.1755:1787](),[9.1787][9.13968:14098](),[9.13968][9.13968:14098](),[9.14133][9.907:933](),[9.496][9.348:365](),[9.933][9.348:365](),[9.348][9.348:365](),[9.365][9.146:193](),[9.193][9.396:452](),[9.1696][9.396:452](),[9.396][9.396:452](),[9.452][9.4753:4808](),[9.4808][9.2:61](),[9.14215][9.2:61](),[9.520][9.2:61](),[9.120][9.120:172](),[9.172][9.462:501](),[9.501][9.1647:1693](),[9.1693][9.14216:14257](),[9.4354][9.14216:14257](),[9.501][9.14216:14257](),[9.14257][9.1788:1820](),[9.1820][9.14343:14473](),[9.14343][9.14343:14473](),[9.14508][9.934:960](),[9.511][9.383:465](),[9.960][9.383:465](),[9.383][9.383:465](),[9.465][9.194:315](),[9.135][9.531:620](),[9.315][9.531:620](),[9.531][9.531:620](),[9.620][9.4809:4914](),[9.787][9.2:53](),[9.4914][9.2:53](),[9.14702][9.2:53](),[9.520][9.2:53](),[9.104][9.104:149](),[9.149][9.502:541](),[9.541][9.1694:1740](),[9.1740][9.14703:14744](),[9.4459][9.14703:14744](),[9.541][9.14703:14744](),[9.14744][9.1821:1853](),[9.1853][9.14830:14960](),[9.14830][9.14830:14960](),[9.14995][9.961:987](),[9.526][9.360:380](),[9.987][9.360:380](),[9.360][9.360:380](),[9.380][9.1200:1252](),[9.1252][9.4915:4959](),[9.1744][9.4915:4959](),[9.4959][9.495:550](),[9.495][9.495:550](),[9.550][9.4960:5007](),[9.5007][9.2:27](),[9.15076][9.2:27](),[9.617][9.2:27](),[9.52][9.52:104](),[9.104][9.542:581](),[9.581][9.1741:1787](),[9.1787][9.15077:15118](),[9.4564][9.15077:15118](),[9.581][9.15077:15118](),[9.15118][9.1854:1886](),[9.1886][9.15204:15334](),[9.15204][9.15204:15334](),[9.15369][9.988:1014](),[9.541][9.315:332](),[9.1014][9.315:332](),[9.315][9.315:332](),[9.332][9.1253:1305](),[9.1305][9.5008:5052](),[9.1792][9.5008:5052](),[9.5052][9.421:451](),[9.421][9.421:451](),[9.451][9.5053:5106](),[9.5106][9.504:550](),[9.15436][9.504:550](),[9.504][9.504:550](),[9.596][9.596:641](),[9.641][9.582:621](),[9.621][9.1788:1834](),[9.1834][9.15437:15485](),[9.4669][9.15437:15485](),[9.621][9.15437:15485](),[9.15485][9.1887:1919](),[9.1919][9.15571:15701](),[9.15571][9.15571:15701](),[9.15736][9.1015:1041](),[9.556][9.859:923](),[9.1041][9.859:923](),[9.859][9.859:923](),[9.923][9.1306:1358](),[9.1358][9.955:1068](),[9.1840][9.955:1068](),[9.955][9.955:1068](),[9.1068][9.5107:5165](),[9.617][9.520:525](),[9.1141][9.520:525](),[9.5165][9.520:525](),[9.15823][9.520:525](),[9.520][9.520:525](),[9.525][9.2:48](),[9.99][9.99:164](),[9.164][9.1835:1881](),[9.1881][9.15824:15886](),[9.4774][9.15824:15886](),[9.164][9.15824:15886](),[9.15886][9.1920:1952](),[9.1952][9.15972:16058](),[9.15972][9.15972:16058](),[9.16093][9.1042:1068](),[9.1068][9.4775:4804](),[9.4804][9.5166:5216](),[9.5216][9.16131:16166](),[9.452][9.16131:16166](),[9.16166][9.5217:5267](),[9.5267][9.16167:16202](),[9.563][9.16167:16202](),[9.16202][9.5268:5318](),[9.5318][9.674:742](),[9.674][9.674:742](),[9.742][9.16203:16247](),[9.16247][9.773:795](),[9.773][9.773:795](),[9.795][9.1359:1425](),[9.1425][9.833:854](),[9.1894][9.833:854](),[9.833][9.833:854](),[9.854][9.5319:5427](),[9.5427][9.1008:1013](),[9.16428][9.1008:1013](),[9.1008][9.1008:1013](),[9.525][9.2457:2492](),[9.1013][9.2457:2492](),[9.1127][9.2457:2492](),[9.2457][9.2457:2492](),[9.2532][9.2532:2571](),[9.2571][9.1882:1928](),[9.1928][9.16429:16484](),[9.4909][9.16429:16484](),[9.2571][9.16429:16484](),[9.16484][9.1953:1985](),[9.1985][9.16570:16656](),[9.16570][9.16570:16656](),[9.16691][9.1069:1095](),[9.1095][9.316:363](),[9.363][9.4910:4939](),[9.2079][9.4910:4939](),[9.4939][9.5428:5469](),[9.5469][9.16729:16764](),[9.3005][9.16729:16764](),[9.16764][9.5470:5510](),[9.1917][9.16765:16800](),[9.5510][9.16765:16800](),[9.3096][9.16765:16800](),[9.16800][9.5511:5551](),[9.1986][9.3188:3224](),[9.5551][9.3188:3224](),[9.3188][9.3188:3224](),[9.3260][9.2:27](),[9.27][9.4940:4997](),[9.4997][9.1929:1975](),[9.1975][9.16865:16927](),[9.5101][9.16865:16927](),[9.16865][9.16865:16927](),[9.16927][9.1986:2018](),[9.2018][9.17013:17099](),[9.17013][9.17013:17099](),[9.17134][9.1096:1122](),[9.1122][9.5102:5131](),[9.5131][9.5552:5602](),[9.5602][9.17172:17207](),[9.3627][9.17172:17207](),[9.17207][9.5603:5653](),[9.5653][9.17208:17243](),[9.3723][9.17208:17243](),[9.17243][9.5654:5704](),[9.5704][9.28:71](),[9.3819][9.28:71](),[9.71][9.1426:1486](),[9.1486][9.5705:5874](),[9.2130][9.5705:5874](),[9.5874][9.5132:5155](),[9.17485][9.5132:5155](),[9.5155][9.5875:5914](),[9.5914][9.17516:17551](),[9.4193][9.17516:17551](),[9.17551][9.5915:5955](),[9.5955][9.17552:17587](),[9.4279][9.17552:17587](),[9.17587][9.5956:5997](),[9.5997][9.1488:1541](),[9.4366][9.1488:1541](),[9.1594][9.1594:1614](),[9.1614][9.5156:5213](),[9.5213][9.1976:2022](),[9.2022][9.17652:17693](),[9.5317][9.17652:17693](),[9.17652][9.17652:17693](),[9.17693][9.2019:2051](),[9.2051][9.17779:17865](),[9.17779][9.17779:17865](),[9.17900][9.1798:1841](),[9.1798][9.1798:1841](),[9.1841][9.1487:1547](),[9.1547][9.5998:6217](),[9.2181][9.5998:6217](),[9.2207][9.4366:4409](),[9.6217][9.4366:4409](),[9.18284][9.4366:4409](),[9.4366][9.4366:4409](),[9.4452][9.2:27](),[9.27][9.5318:5375](),[9.5375][9.2023:2069](),[9.2069][9.18349:18411](),[9.5479][9.18349:18411](),[9.18349][9.18349:18411](),[9.18411][9.2052:2084](),[9.2084][9.18497:18583](),[9.18497][9.18497:18583](),[9.18618][9.1123:1149](),[9.1149][9.5480:5509](),[9.5509][9.6218:6268](),[9.6268][9.18656:18691](),[9.4826][9.18656:18691](),[9.18691][9.6269:6319](),[9.6319][9.18692:18727](),[9.4929][9.18692:18727](),[9.18727][9.6320:6370](),[9.6370][9.28:101](),[9.5032][9.28:101](),[9.101][9.5089:5115](),[9.5089][9.5089:5115](),[9.5115][9.1548:1600](),[9.1600][9.6371:6540](),[9.2229][9.6371:6540](),[9.6540][9.5510:5533](),[9.18990][9.5510:5533](),[9.5533][9.6541:6582](),[9.6582][9.19021:19056](),[9.5459][9.19021:19056](),[9.19056][9.6583:6624](),[9.6624][9.19057:19092](),[9.5553][9.19057:19092](),[9.19092][9.6625:6666](),[9.6666][9.5647:5652](),[9.5647][9.5647:5652](),[9.184][9.184:224](),[9.269][9.269:308](),[9.308][9.2117:2163](),[9.2163][9.19904:19959](),[9.5832][9.19904:19959](),[9.308][9.19904:19959](),[9.19959][9.2118:2150](),[9.2150][9.20045:20131](),[9.20045][9.20045:20131](),[9.20166][9.20166:20197](),[9.20197][6.76:149](),[6.149][9.2:40](),[9.195][9.2:40](),[9.432][9.2:40](),[9.732][9.2:40](),[9.1315][9.2:40](),[9.20267][9.2:40](),[9.581][9.2:40](),[9.40][9.5833:5919](),[9.141][9.107:144](),[9.260][9.107:144](),[9.339][9.107:144](),[9.2433][9.107:144](),[9.5919][9.107:144](),[9.20353][9.107:144](),[9.107][9.107:144](),[9.144][9.5920:6034](),[9.6034][9.6667:6899](),[9.6899][9.108:431](),[9.466][9.466:497](),[9.497][9.196:269](),[9.269][9.592:896](),[9.592][9.592:896](),[9.152][9.538:543](),[9.896][9.538:543](),[9.6899][9.538:543](),[9.20831][9.538:543](),[9.538][9.538:543](),[9.543][9.2:390](),[9.425][9.425:467](),[9.467][4.0:26](),[4.26][9.467:766](),[9.467][9.467:766](),[9.766][9.2:685](),[9.720][9.720:1268](),[9.766][9.543:593](),[9.1268][9.543:593](),[9.543][9.543:593](),[9.648][9.648:687](),[9.687][9.2164:2210](),[9.2210][9.20832:20887](),[9.6139][9.20832:20887](),[9.687][9.20832:20887](),[9.20887][9.2151:2183](),[9.2183][9.20973:21059](),[9.20973][9.20973:21059](),[9.21094][9.21094:21125](),[9.21125][9.270:343](),[9.343][9.581:610](),[9.528][9.581:610](),[9.790][9.581:610](),[9.960][9.581:610](),[9.1398][9.581:610](),[9.21195][9.581:610](),[9.581][9.581:610](),[9.610][9.6140:6314](),[9.364][9.746:792](),[9.477][9.746:792](),[9.535][9.746:792](),[9.2765][9.746:792](),[9.6314][9.746:792](),[9.21368][9.746:792](),[9.746][9.746:792](),[9.792][9.365:396](),[9.396][9.6315:6516](),[9.626][9.548:581](),[9.670][9.548:581](),[9.2995][9.548:581](),[9.6516][9.548:581](),[9.21568][9.548:581](),[9.548][9.548:581](),[9.581][9.6900:7132](),[9.7132][9.2:68](),[9.21973][9.2:68](),[9.323][9.2:68](),[9.134][9.134:173](),[9.173][9.2211:2257](),[9.2257][9.21974:22029](),[9.6621][9.21974:22029](),[9.173][9.21974:22029](),[9.22029][9.2184:2248](),[9.2248][9.22115:22201](),[9.22115][9.22115:22201](),[9.22236][9.22236:22267](),[9.22267][9.344:417](),[9.417][9.446:475](),[9.624][9.446:475](),[9.848][9.446:475](),[9.1481][9.446:475](),[9.22337][9.446:475](),[9.446][9.446:475](),[9.475][9.6622:6796](),[9.720][9.611:663](),[9.761][9.611:663](),[9.791][9.611:663](),[9.3198][9.611:663](),[9.6796][9.611:663](),[9.22510][9.611:663](),[9.611][9.611:663](),[9.663][9.721:752](),[9.752][9.6797:6998](),[9.904][9.838:889](),[9.910][9.838:889](),[9.926][9.838:889](),[9.3428][9.838:889](),[9.6998][9.838:889](),[9.22710][9.838:889](),[9.838][9.838:889](),[9.889][9.905:936](),[9.936][9.6999:7199](),[9.1058][9.1087:1120](),[9.1060][9.1087:1120](),[9.3657][9.1087:1120](),[9.7199][9.1087:1120](),[9.22909][9.1087:1120](),[9.1087][9.1087:1120](),[9.1120][9.1090:1185](),[9.1090][9.1090:1185](),[9.1185][9.7133:7365](),[9.7365][9.2:322](),[9.357][9.357:429](),[9.429][9.1601:1653](),[9.1653][9.476:802](),[9.476][9.476:802](),[9.6255][9.2:40](),[9.83][9.83:108](),[9.108][9.7200:7257](),[9.7257][9.2258:2304](),[9.2304][9.23423:23485](),[9.7361][9.23423:23485](),[9.23423][9.23423:23485](),[9.23485][9.2249:2281](),[9.2281][9.23571:23657](),[9.23571][9.23571:23657](),[9.23692][9.23692:23723](),[9.23723][9.1482:1508](),[9.863][9.344:383](),[9.1508][9.344:383](),[9.344][9.344:383](),[9.383][9.1654:1706](),[9.1706][9.415:429](),[9.3705][9.415:429](),[9.415][9.415:429](),[9.429][9.7366:7417](),[9.7417][9.492:497](),[9.23800][9.492:497](),[9.492][9.492:497](),[9.497][9.6255:6280](),[9.6255][9.6255:6280](),[9.6310][9.6310:6350](),[9.6350][9.2305:2351](),[9.2351][9.23801:23856](),[9.7466][9.23801:23856](),[9.6350][9.23801:23856](),[9.23856][9.2282:2314](),[9.2314][9.23942:24028](),[9.23942][9.23942:24028](),[9.24063][9.6506:6555](),[9.6506][9.6506:6555](),[9.6555][9.1509:1535](),[9.1535][9.7467:7496](),[9.7496][9.7418:7468](),[9.7468][9.24101:24136](),[9.6659][9.24101:24136](),[9.24136][9.7469:7519](),[9.7519][9.6749:6801](),[9.6749][9.6749:6801](),[9.6801][9.1707:1771](),[9.1771][9.7520:7630](),[9.3758][9.7520:7630](),[9.7630][9.7497:7520](),[9.24283][9.7497:7520](),[9.7520][9.7631:7672](),[9.7672][9.24314:24349](),[9.7034][9.24314:24349](),[9.24349][9.7673:7714](),[9.7714][9.7115:7160](),[9.7115][9.7115:7160](),[9.7205][9.7205:7255](),[9.7255][9.2:29](),[9.29][9.7521:7589](),[9.7589][9.2352:2398](),[9.2398][9.24425:24493](),[9.7693][9.24425:24493](),[9.24425][9.24425:24493](),[9.24493][9.2315:2551](),[9.2551][9.7715:7783](),[9.7783][9.24681:24767](),[9.24681][9.24681:24767](),[9.24802][9.2:80](),[9.80][9.7779:7946](),[9.85][9.7779:7946](),[9.170][9.7779:7946](),[9.24893][9.7779:7946](),[9.7779][9.7779:7946](),[9.7946][9.1536:1562](),[9.1562][9.7694:7723](),[9.7723][9.7784:7834](),[9.7834][9.8065:8222](),[9.8065][9.8065:8222](),[9.8222][9.1772:1836](),[9.1836][9.7835:7945](),[9.3811][9.7835:7945](),[9.7945][9.7724:7764](),[9.25107][9.7724:7764](),[9.7764][9.7946:7987](),[9.8507][9.1265:1339](),[9.1265][9.1265:1339](),[9.1413][9.1413:1475](),[9.1475][9.7924:7981](),[9.7981][9.2446:2492](),[9.2492][9.26150:26242](),[9.8085][9.26150:26242](),[9.26150][9.26150:26242](),[9.26242][9.2585:2617](),[9.2617][9.26328:26414](),[9.26328][9.26328:26414](),[9.26449][9.1590:1616](),[9.1616][9.8086:8115](),[9.8115][9.8508:8559](),[9.8559][9.26487:26522](),[9.1859][9.26487:26522](),[9.26522][9.8560:8611](),[9.8611][9.26523:26558](),[9.1994][9.26523:26558](),[9.26558][9.8612:8663](),[9.8663][9.2129:2192](),[9.2129][9.2129:2192](),[9.2192][9.1902:1966](),[9.1966][9.8664:8790](),[9.3917][9.8664:8790](),[9.8790][9.8116:8139](),[9.26809][9.8116:8139](),[9.8139][9.8791:8833](),[9.8833][9.26840:26875](),[9.2574][9.26840:26875](),[9.26875][9.8834:8876](),[9.106][9.26876:26911](),[9.8876][9.26876:26911](),[9.2701][9.26876:26911](),[9.26911][3.2:320](),[9.42][9.10090:10095](),[9.211][9.10090:10095](),[3.320][9.10090:10095](),[9.2826][9.10090:10095](),[9.8919][9.10090:10095](),[9.10090][9.10090:10095](),[9.10095][9.2:42](),[9.87][9.87:408](),[9.443][9.443:499](),[9.499][9.1967:2031](),[9.2031][9.8920:9046](),[9.551][9.8920:9046](),[9.9046][9.743:748](),[9.743][9.743:748](),[9.748][9.10095:10135](),[9.10095][9.10095:10135](),[9.10180][9.10180:10220](),[9.10220][9.2493:2539](),[9.2539][9.26912:26974](),[9.8244][9.26912:26974](),[9.10220][9.26912:26974](),[9.26974][9.2618:2650](),[9.2650][9.27060:27146](),[9.27060][9.27060:27146](),[9.27181][9.10383:10434](),[9.10383][9.10383:10434](),[9.10434][9.1617:1643](),[9.1643][9.8245:8274](),[9.8274][9.9047:9097](),[9.9097][9.27219:27254](),[9.10553][9.27219:27254](),[9.27254][9.9098:9148](),[9.9148][9.27255:27290](),[9.10658][9.27255:27290](),[9.27290][9.9149:9199](),[9.9199][9.10763:10830](),[9.10763][9.10763:10830](),[9.10830][9.2032:2088](),[9.2088][9.9200:9310](),[9.3966][9.9200:9310](),[9.9310][9.11013:11042](),[9.27467][9.11013:11042](),[9.11013][9.11013:11042](),[9.11042][9.8275:8298](),[9.8298][9.9311:9352](),[9.9352][9.27499:27534](),[9.11133][9.27499:27534](),[9.27534][9.9353:9394](),[9.9394][9.27535:27570](),[9.11229][9.27535:27570](),[9.27570][9.9395:9436](),[9.9436][9.2:535](),[9.570][9.570:1004](),[9.1004][9.2089:2145](),[9.2145][9.1052:1103](),[9.1052][9.1052:1103](),[9.1103][9.11325:11382](),[9.9436][9.11325:11382](),[9.11325][9.11325:11382](),[9.11439][9.11439:11549](),[9.11549][9.2540:2586](),[9.2586][9.27571:27633](),[9.8403][9.27571:27633](),[9.11549][9.27571:27633](),[9.27633][9.2651:2683](),[9.2683][9.27719:27805](),[9.27719][9.27719:27805](),[9.27840][9.1644:1670](),[9.1670][9.8404:8433](),[9.8433][9.9437:9487](),[9.9487][9.27878:27913](),[9.11843][9.27878:27913](),[9.27913][9.9488:9538](),[9.9538][9.27914:27949](),[9.11960][9.27914:27949](),[9.27949][9.9539:9589](),[9.9589][9.12077:12147](),[9.12077][9.12077:12147](),[9.12147][9.2146:2202](),[9.2202][9.9590:9700](),[9.4015][9.9590:9700](),[9.9700][9.8434:8457](),[9.28150][9.8434:8457](),[9.8457][9.9701:9742](),[9.9742][9.28181:28216](),[9.12457][9.28181:28216](),[9.28216][9.9743:9784](),[9.9784][9.28217:28252](),[9.12565][9.28217:28252](),[9.28252][9.9785:9826](),[9.9826][9.12673:12737](),[9.12673][9.12673:12737](),[9.12801][9.12801:12871](),[9.12871][9.8458:8515](),[9.8515][9.2587:2633](),[9.2633][9.28317:28383](),[9.8619][9.28317:28383](),[9.28317][9.28317:28383](),[9.28383][9.2684:2716](),[9.2716][9.28469:28555](),[9.28469][9.28469:28555](),[9.28590][9.1671:1697](),[9.1697][9.8620:8649](),[9.8649][9.9827:9877](),[9.9877][9.28628:28663](),[9.13218][9.28628:28663](),[9.28663][9.9878:9928](),[9.9928][9.28664:28699](),[9.13342][9.28664:28699](),[9.28699][9.9929:10027](),[9.10027][9.13514:13584](),[9.13514][9.13514:13584](),[9.13584][9.2203:2259](),[9.2259][9.10028:10197](),[9.4064][9.10028:10197](),[9.949][9.8650:8673](),[9.949][9.8650:8673](),[9.10197][9.8650:8673](),[9.29025][9.8650:8673](),[9.8673][9.10198:10239](),[9.10239][9.29056:29091](),[9.14013][9.29056:29091](),[9.29091][9.10240:10282](),[9.10282][9.29092:29127](),[9.14129][9.29092:29127](),[9.29127][9.10283:10324](),[9.10324][9.14244:14336](),[9.14244][9.14244:14336](),[9.14428][9.14428:14498](),[9.14498][9.8674:8731](),[9.8731][9.2634:2680](),[9.2680][9.29192:29257](),[9.8835][9.29192:29257](),[9.29192][9.29192:29257](),[9.29257][9.2717:2749](),[9.2749][9.29343:29429](),[9.29343][9.29343:29429](),[9.29464][9.1698:1724](),[9.1724][9.8836:8865](),[9.8865][9.10325:10375](),[9.10375][9.29502:29537](),[9.14872][9.29502:29537](),[9.29537][9.10376:10426](),[9.10426][9.29538:29573](),[9.15024][9.29538:29573](),[9.29573][9.10427:10478](),[9.2118][9.15178:15248](),[9.10478][9.15178:15248](),[9.15178][9.15178:15248](),[9.15248][9.2260:2316](),[9.2316][9.10479:10648](),[9.4113][9.10479:10648](),[9.2253][9.8866:8889](),[9.10648][9.8866:8889](),[9.29983][9.8866:8889](),[9.8889][9.10649:10690](),[9.10690][9.30014:30049](),[9.15789][9.30014:30049](),[9.30049][9.10691:10733](),[9.2376][9.30050:30085](),[9.10733][9.30050:30085](),[9.15934][9.30050:30085](),[9.30085][9.10734:10774](),[9.2497][9.16075:16080](),[9.10774][9.16075:16080](),[9.16075][9.16075:16080](),[9.16080][9.2:76](),[9.155][9.8890:8947](),[9.16235][9.8890:8947](),[9.8947][9.2681:2727](),[9.2727][9.30150:30215](),[9.9051][9.30150:30215](),[9.30150][9.30150:30215](),[9.30215][9.2750:2782](),[9.2782][9.30301:30387](),[9.30301][9.30301:30387](),[9.30422][9.1725:1751](),[9.1751][9.9052:9081](),[9.9081][9.10775:10825](),[9.273][9.30460:30495](),[9.10825][9.30460:30495](),[9.16597][9.30460:30495](),[9.30495][9.10826:10876](),[9.391][9.30496:30531](),[9.10876][9.30496:30531](),[9.16737][9.30496:30531](),[9.30531][9.10877:10928](),[9.510][9.16879:16955](),[9.2617][9.16879:16955](),[9.10928][9.16879:16955](),[9.16879][9.16879:16955](),[9.16955][9.2317:2381](),[9.2381][9.10929:11128](),[9.4166][9.10929:11128](),[9.911][9.17356:17458](),[9.11128][9.17356:17458](),[9.30935][9.17356:17458](),[9.17356][9.17356:17458](),[9.17458][9.2382:2438](),[9.2438][9.11129:11298](),[9.4215][9.11129:11298](),[9.1282][9.9082:9105](),[9.2740][9.9082:9105](),[9.11298][9.9082:9105](),[9.31309][9.9082:9105](),[9.9105][9.11299:11341](),[9.1392][9.31340:31375](),[9.2851][9.31340:31375](),[9.11341][9.31340:31375](),[9.17953][9.31340:31375](),[9.31375][9.11342:11382](),[9.1500][9.31376:31411](),[9.2960][9.31376:31411](),[9.11382][9.31376:31411](),[9.18082][9.31376:31411](),[9.31411][9.11383:11424](),[9.1609][9.18213:18256](),[9.11424][9.18213:18256](),[9.18213][9.18213:18256](),[9.18299][9.18299:18405](),[9.18405][9.2728:2774](),[9.2774][9.31412:31474](),[9.9210][9.31412:31474](),[9.18405][9.31412:31474](),[9.31474][9.2783:2815](),[9.2815][9.31560:31646](),[9.31560][9.31560:31646](),[9.31681][9.1752:1778](),[9.1778][9.9211:9240](),[9.9240][9.11425:11475](),[9.11475][9.31719:31754](),[9.18672][9.31719:31754](),[9.31754][9.11476:11526](),[9.11526][9.31755:31790](),[9.18775][9.31755:31790](),[9.31790][9.11527:11577](),[9.11577][9.18878:18940](),[9.18878][9.18878:18940](),[9.18940][9.2439:2491](),[9.2491][9.11578:11688](),[9.4262][9.11578:11688](),[9.11688][9.19117:19146](),[9.31963][9.19117:19146](),[9.19117][9.19117:19146](),[9.19146][9.9241:9264](),[9.9264][9.11689:11730](),[9.11730][9.31995:32030](),[9.19235][9.31995:32030](),[9.32030][9.11731:11772](),[9.11772][9.32031:32066](),[9.19329][9.32031:32066](),[9.32066][9.11773:11814](),[9.11814][9.19423:19428](),[9.19423][9.19423:19428](),[9.19428][9.1104:1630](),[9.1665][9.1665:2095](),[9.2095][9.2492:2544](),[9.2544][9.2141:2197](),[9.2141][9.2141:2197](),[9.2197][9.19428:19476](),[9.19428][9.19428:19476](),[9.19529][9.19529:19624](),[9.19624][9.2775:2821](),[9.2821][9.32067:32129](),[9.9369][9.32067:32129](),[9.19624][9.32067:32129](),[9.32129][9.2816:2848](),[9.2848][9.32215:32301](),[9.32215][9.32215:32301](),[9.32336][9.1779:1805](),[9.1805][9.9370:9399](),[9.9399][9.11815:11865](),[9.11865][9.32374:32409](),[9.19901][9.32374:32409](),[9.32409][9.11866:11916](),[9.11916][9.32410:32445](),[9.20014][9.32410:32445](),[9.32445][9.11917:11967](),[9.11967][9.20127:20193](),[9.20127][9.20127:20193](),[9.20193][9.2545:2597](),[9.2597][9.11968:12078](),[9.4309][9.11968:12078](),[9.12078][9.9400:9423](),[9.32638][9.9400:9423](),[9.9423][9.12079:12120](),[9.12120][9.32669:32704](),[9.20489][9.32669:32704](),[9.32704][9.12121:12162](),[9.12162][9.32705:32740](),[9.20593][9.32705:32740](),[9.32740][9.12163:12204](),[9.12204][9.2:423](),[9.458][9.458:809](),[9.809][9.2598:2650](),[9.2650][9.855:965](),[9.855][9.855:965](),[9.965][9.20697:20757](),[9.12204][9.20697:20757](),[9.20697][9.20697:20757](),[9.20817][9.20817:20879](),[9.20879][9.9424:9481](),[9.9481][9.2822:2868](),[9.2868][9.32805:32871](),[9.9585][9.32805:32871](),[9.32805][9.32805:32871](),[9.32871][9.2849:2881](),[9.2881][9.32957:33043](),[9.32957][9.32957:33043](),[9.33078][9.1806:1832](),[9.1832][9.9586:9615](),[9.9615][9.12205:12255](),[9.12255][9.33116:33151](),[9.21222][9.33116:33151](),[9.33151][9.12256:12306](),[9.12306][9.21342:21417](),[9.21342][9.21342:21417](),[9.21417][9.2651:2703](),[9.2703][9.9616:9639](),[9.4356][9.9616:9639](),[9.9639][9.12307:12349](),[9.12349][9.33183:33218](),[9.21555][9.33183:33218](),[9.33218][9.12350:12391](),[9.12391][9.33219:33254](),[9.21666][9.33219:33254](),[9.33254][9.12392:12669](),[9.12669][9.22144:22206](),[9.33674][9.22144:22206](),[9.22144][9.22144:22206](),[9.22268][9.22268:22319](),[9.22319][9.9640:9697](),[9.9697][9.2869:2915](),[9.2915][9.33739:33805](),[9.9801][9.33739:33805](),[9.33739][9.33739:33805](),[9.33805][9.2882:2914](),[9.2914][9.33891:33977](),[9.33891][9.33891:33977](),[9.34012][9.1833:1859](),[9.1859][9.9802:9831](),[9.9831][9.12670:12720](),[9.12720][9.34050:34085](),[9.22664][9.34050:34085](),[9.34085][9.12721:12771](),[9.12771][9.34086:34121](),[9.22786][9.34086:34121](),[9.34121][9.12772:12822](),[9.12822][9.22908:23000](),[9.22908][9.22908:23000](),[9.23000][9.2704:2756](),[9.2756][9.9832:9855](),[9.4403][9.9832:9855](),[9.9855][9.12823:12864](),[9.12864][9.34153:34188](),[9.23139][9.34153:34188](),[9.34188][9.12865:12906](),[9.12906][9.34189:34224](),[9.23252][9.34189:34224](),[9.34224][9.12907:13184](),[9.13184][9.23740:23795](),[9.34652][9.23740:23795](),[9.23740][9.23740:23795](),[9.23850][9.23850:23975](),[9.23975][9.2916:2962](),[9.2962][9.34653:34719](),[9.9960][9.34653:34719](),[9.23975][9.34653:34719](),[9.34719][9.2915:2947](),[9.2947][9.34805:34891](),[9.34805][9.34805:34891](),[9.34926][9.1860:1886](),[9.1886][9.9961:9990](),[9.9990][9.13185:13235](),[9.13235][9.34964:34999](),[9.24258][9.34964:34999](),[9.34999][9.13236:13286](),[9.13286][9.35000:35035](),[9.24373][9.35000:35035](),[9.35035][9.13287:13337](),[9.13337][9.24488:24554](),[9.24488][9.24488:24554](),[9.24554][9.2757:2809](),[9.2809][9.13338:13448](),[9.4450][9.13338:13448](),[9.13448][9.9991:10014](),[9.35232][9.9991:10014](),[9.10014][9.24772:24794](),[9.35262][9.24772:24794](),[9.24772][9.24772:24794](),[9.24794][9.35263:35298](),[9.35298][9.13449:13490](),[9.13490][9.35299:35334](),[9.24900][9.35299:35334](),[9.35334][9.13491:13532](),[9.13532][9.25006:25034](),[9.25006][9.25006:25034](),[9.25062][9.25062:25102](),[9.25102][9.2963:3009](),[9.3009][9.35335:35390](),[9.10119][9.35335:35390](),[9.25102][9.35335:35390](),[9.35390][9.2948:2980](),[9.2980][9.35476:35562](),[9.35476][9.35476:35562](),[9.35597][9.25258:25306](),[9.25258][9.25258:25306](),[9.25306][9.1887:1913](),[9.1913][9.10120:10149](),[9.10149][9.13533:13583](),[9.13583][9.35635:35670](),[9.25408][9.35635:35670](),[9.35670][9.13584:13634](),[9.13634][9.25496:25544](),[9.25496][9.25496:25544](),[9.25544][9.2810:2870](),[9.2870][9.13635:13745](),[9.4501][9.13635:13745](),[9.13745][9.10150:10173](),[9.35813][9.10150:10173](),[9.10173][9.13746:13787](),[9.13787][9.35844:35879](),[9.25769][9.35844:35879](),[9.35879][9.13788:13829](),[9.13829][9.25848:25902](),[9.25848][9.25848:25902](),[9.25956][9.25956:26026](),[9.26026][9.10174:10231](),[9.10231][9.3010:3056](),[9.3056][9.35944:36010](),[9.10335][9.35944:36010](),[9.35944][9.35944:36010](),[9.36010][9.2981:3013](),[9.3013][9.36096:36182](),[9.36096][9.36096:36182](),[9.36217][9.1914:1940](),[9.1940][9.10336:10365](),[9.10365][9.13830:13880](),[9.13880][9.36255:36290](),[9.26363][9.36255:36290](),[9.36290][9.13881:13931](),[9.13931][9.36291:36326](),[9.26477][9.36291:36326](),[9.36326][9.13932:14029](),[9.14029][9.26638:26702](),[9.26638][9.26638:26702](),[9.26702][9.2871:2931](),[9.2931][9.14030:14199](),[9.4552][9.14030:14199](),[9.14199][9.10366:10389](),[9.36622][9.10366:10389](),[9.10389][9.14200:14242](),[9.14242][9.36653:36688](),[9.27094][9.36653:36688](),[9.36688][9.14243:14284](),[9.14284][9.36689:36724](),[9.27199][9.36689:36724](),[9.36724][9.14285:14326](),[9.14326][9.27304:27367](),[9.27304][9.27304:27367](),[9.27430][9.36725:36816](),[9.36816][9.10390:10447](),[9.10447][9.3057:3103](),[9.3103][9.36880:36943](),[9.10551][9.36880:36943](),[9.36880][9.36880:36943](),[9.36943][9.3014:3046](),[9.3046][9.37029:37115](),[9.37029][9.37029:37115](),[9.37150][9.1941:1967](),[9.1967][9.10552:10581](),[9.10581][9.14327:14377](),[9.14377][9.37188:37223](),[9.27851][9.37188:37223](),[9.37223][9.14378:14475](),[9.14475][9.28021:28085](),[9.28021][9.28021:28085](),[9.28085][9.2932:2992](),[9.2992][9.14476:14645](),[9.4603][9.14476:14645](),[9.14645][9.10582:10605](),[9.37546][9.10582:10605](),[9.10605][9.14646:14688](),[9.14688][9.37577:37612](),[9.28513][9.37577:37612](),[9.37612][9.14689:14730](),[9.14730][9.37613:37648](),[9.28627][9.37613:37648](),[9.37648][9.14731:14773](),[9.14773][9.28742:28797](),[9.28742][9.28742:28797](),[9.28852][9.28852:28904](),[9.28904][9.10606:10663](),[9.10663][9.3104:3150](),[9.3150][9.37713:37775](),[9.10767][9.37713:37775](),[9.37713][9.37713:37775](),[9.37775][9.3047:3079](),[9.3079][9.37861:37947](),[9.37861][9.37861:37947](),[9.37982][9.1968:1994](),[9.1994][9.10768:10797](),[9.10797][9.14774:14824](),[9.14824][9.38020:38055](),[9.29238][9.38020:38055](),[9.38055][9.14825:14875](),[9.14875][9.38056:38091](),[9.29353][9.38056:38091](),[9.38091][9.14876:14926](),[9.14926][9.29468:29525](),[9.29468][9.29468:29525](),[9.29525][9.2993:3053](),[9.3053][9.14927:15096](),[9.4654][9.14927:15096](),[9.15096][9.10798:10821](),[9.38390][9.10798:10821](),[9.10821][9.15097:15138](),[9.15138][9.38421:38456](),[9.29920][9.38421:38456](),[9.38456][9.15139:15178](),[9.15178][9.38457:38492](),[9.30024][9.38457:38492](),[9.38492][9.15179:15219](),[9.15219][9.30129:30211](),[9.30129][9.30129:30211](),[9.30293][9.30293:30337](),[9.30337][9.10822:10879](),[9.10879][9.3151:3197](),[9.3197][9.38557:38619](),[9.10983][9.38557:38619](),[9.38557][9.38557:38619](),[9.38619][9.3080:3112](),[9.3112][9.38705:38791](),[9.38705][9.38705:38791](),[9.38826][9.1995:2021](),[9.2021][9.10984:11013](),[9.11013][9.15220:15270](),[9.15270][9.30698:30763](),[9.30698][9.30698:30763](),[9.30763][9.3054:3114](),[9.3114][9.15271:15440](),[9.4705][9.15271:15440](),[9.15440][9.11014:11037](),[9.39243][9.11014:11037](),[9.11037][9.15441:15480](),[9.15480][9.39274:39309](),[9.31264][9.39274:39309](),[9.39309][9.15481:15521](),[9.15521][9.31396:31401](),[9.31396][9.31396:31401](),[9.31401][9.102:188](),[9.279][9.279:328](),[9.328][9.11038:11095](),[9.11095][9.3198:3244](),[9.3244][9.39374:39419](),[9.11199][9.39374:39419](),[9.39374][9.39374:39419](),[9.39419][9.3113:3145](),[9.3145][9.39505:39591](),[9.39505][9.39505:39591](),[9.39626][9.2022:2048](),[9.1163][9.529:603](),[9.2048][9.529:603](),[9.529][9.529:603](),[9.603][9.364:411](),[9.411][9.15522:15691](),[9.15691][9.11200:11229](),[9.40033][9.11200:11229](),[9.11229][9.15692:15731](),[9.15731][9.1142:1147](),[9.1142][9.1142:1147](),[9.1147][9.16:67](),[9.31401][9.16:67](),[9.123][9.123:175](),[9.175][9.11230:11287](),[9.11287][9.3245:3291](),[9.3291][9.40134:40196](),[9.11391][9.40134:40196](),[9.40134][9.40134:40196](),[9.40196][9.3146:3178](),[9.3178][9.40282:40368](),[9.40282][9.40282:40368](),[9.40403][9.2049:2075](),[9.2075][9.11392:11421](),[9.11421][9.15732:15782](),[9.15782][9.40441:40476](),[9.510][9.40441:40476](),[9.40476][9.15783:15833](),[9.15833][9.40477:40512](),[9.626][9.40477:40512](),[9.40512][9.15834:15884](),[9.15884][9.742:813](),[9.742][9.742:813](),[9.813][9.412:553](),[9.553][9.15885:16054](),[9.16054][9.11422:11445](),[9.40814][9.11422:11445](),[9.11445][9.16055:16096](),[9.16096][9.40845:40880](),[9.1270][9.40845:40880](),[9.40880][9.16097:16139](),[9.3047][9.40881:40916](),[9.16139][9.40881:40916](),[9.1379][9.40881:40916](),[9.40916][9.16140:16180](),[9.3132][9.2:6](),[9.16180][9.2:6](),[9.1484][9.2:6]()
    -- Arguably this should be called edit_tests.lua,
    -- but that would mess up the git blame at this point.
    function test_initial_state()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{}
    Text.redraw_all(Editor_state)
    edit.draw(Editor_state)
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
    end
    function test_click_to_create_drawing()
    App.screen.init{width=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
    [9.41]
    [9.6]
    --
    -- 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 text_tests.lua at line 12
    [9.7][9.7:61](),[9.120][9.120:182](),[9.182][9.11446:11503](),[9.11503][9.3292:3338](),[9.3338][9.40981:41047](),[9.11607][9.40981:41047](),[9.40981][9.40981:41047](),[9.41047][9.3179:3211](),[9.3211][9.41133:41178](),[9.41133][9.41133:41178](),[9.41213][9.372:404](),[9.372][9.372:404](),[9.404][9.41214:41255](),[9.41255][9.2076:2102](),[9.2102][9.11608:11637](),[9.11637][9.16181:16231](),[9.16231][9.41293:41328](),[9.565][9.41293:41328](),[9.41328][9.16232:16282](),[9.16282][9.684:761](),[9.684][9.684:761](),[9.761][9.3115:3171](),[9.3171][9.11638:11661](),[9.4940][9.11638:11661](),[9.11661][9.16283:16325](),[9.16325][9.41360:41395](),[9.900][9.41360:41395](),[9.41395][9.16326:16367](),[9.16367][9.41396:41431](),[9.1010][9.41396:41431](),[9.41431][9.16368:16645]()
    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')
    [9.7]
    [9.1483]
    Version, Major_version = App.love_version()
    if Major_version == 11 then
    load_file_from_source_or_save_directory('text_tests_love11.lua')
    elseif Major_version == 12 then
    -- not released/stable yet
    load_file_from_source_or_save_directory('text_tests_love12.lua')
  • edit in text_tests.lua at line 19
    [9.1487][9.1487:1545](),[9.1607][9.1607:1677](),[9.1677][9.11662:11719](),[9.11719][9.3339:3385](),[9.3385][9.41912:41978](),[9.11823][9.41912:41978](),[9.41912][9.41912:41978](),[9.41978][9.3212:3244](),[9.3244][9.42064:42109](),[9.42064][9.42064:42109](),[9.42144][9.1867:1908](),[9.1867][9.1867:1908](),[9.1908][9.42145:42186](),[9.42186][9.2103:2129](),[9.2129][9.11824:11853](),[9.11853][9.16646:16696](),[9.16696][9.42224:42259](),[9.2072][9.42224:42259](),[9.42259][9.16697:16747](),[9.16747][9.42260:42295](),[9.2194][9.42260:42295](),[9.42295][9.16748:16846](),[9.16846][9.2364:2435](),[9.2364][9.2364:2435](),[9.2435][9.3172:3230](),[9.3230][9.16847:17016](),[9.4990][9.16847:17016](),[9.17016][9.11854:11877](),[9.42615][9.11854:11877](),[9.11877][9.17017:17058](),[9.17058][9.42646:42681](),[9.2857][9.42646:42681](),[9.42681][9.17059:17101](),[9.17101][9.42682:42717](),[9.2971][9.42682:42717](),[9.42717][9.17102:17143](),[9.17143][9.3084:3137](),[9.3084][9.3084:3137](),[9.3190][9.3190:3252](),[9.3252][9.11878:11935](),[9.11935][9.3386:3432](),[9.3432][9.42782:42848](),[9.12039][9.42782:42848](),[9.42782][9.42782:42848](),[9.42848][9.3245:3277](),[9.3277][9.42934:42979](),[9.42934][9.42934:42979](),[9.43014][9.3442:3474](),[9.3442][9.3442:3474](),[9.3474][9.43015:43056](),[9.43056][9.2130:2156](),[9.2156][9.12040:12069](),[9.12069][9.17144:17194](),[9.17194][9.43094:43129](),[9.3629][9.43094:43129](),[9.43129][9.17195:17245](),[9.17245][9.3742:3809](),[9.3742][9.3742:3809](),[9.3809][9.3231:3287](),[9.3287][9.12070:12093](),[9.5039][9.12070:12093](),[9.12093][9.17246:17288](),[9.17288][9.43161:43196](),[9.3942][9.43161:43196](),[9.43196][9.17289:17330](),[9.17330][9.43197:43232](),[9.4046][9.43197:43232](),[9.43232][9.17331:17608](),[9.4489][9.1484:1489](),[9.17608][9.1484:1489](),[9.43624][9.1484:1489](),[9.1484][9.1484:1489](),[9.1489][9.4490:4539](),[9.4593][9.4593:4663](),[9.4663][9.12094:12151](),[9.12151][9.3433:3479](),[9.3479][9.43689:43755](),[9.12255][9.43689:43755](),[9.43689][9.43689:43755](),[9.43755][9.3278:3310](),[9.3310][9.43841:43886](),[9.43841][9.43841:43886](),[9.43921][9.4853:4894](),[9.4853][9.4853:4894](),[9.4894][9.43922:43963](),[9.43963][9.2157:2183](),[9.2183][9.12256:12285](),[9.12285][9.17609:17659](),[9.17659][9.44001:44036](),[9.5050][9.44001:44036](),[9.44036][9.17660:17710](),[9.17710][9.44037:44072](),[9.5164][9.44037:44072](),[9.44072][9.17711:17809](),[9.17809][9.5326:5385](),[9.5326][9.5326:5385](),[9.5385][9.3288:3342](),[9.3342][9.17810:17979](),[9.5087][9.17810:17979](),[9.17979][9.12286:12309](),[9.44368][9.12286:12309](),[9.12309][9.17980:18021](),[9.18021][9.44399:44434](),[9.5773][9.44399:44434](),[9.44434][9.18022:18064](),[9.18064][9.44435:44470](),[9.5879][9.44435:44470](),[9.44470][9.18065:18106](),[9.18106][9.5984:5989](),[9.5984][9.5984:5989](),[9.1489][9.31401:31512](),[9.5989][9.31401:31512](),[9.31401][9.31401:31512](),[9.31582][9.622:663](),[9.663][9.3480:3526](),[9.3526][9.44471:44540](),[9.12414][9.44471:44540](),[9.663][9.44471:44540](),[9.44540][9.3311:3343](),[9.3343][9.44626:44713](),[9.44626][9.44626:44713](),[9.44748][9.2184:2210](),[9.2210][9.12415:12444](),[9.12444][9.18107:18167](),[9.18167][9.44786:44821](),[9.32015][9.44786:44821](),[9.44821][9.18168:18228](),[9.18228][9.44822:44857](),[9.32155][9.44822:44857](),[9.44857][9.18229:18280](),[9.18280][9.32286:32347](),[9.32286][9.32286:32347](),[9.32347][9.554:695](),[9.695][9.18281:18336](),[9.1063][9.12445:12468](),[9.18336][9.12445:12468](),[9.44946][9.12445:12468](),[9.12468][9.18337:18397](),[9.18397][9.44977:45012](),[9.32650][9.44977:45012](),[9.45012][9.18398:18458](),[9.18458][9.45013:45048](),[9.32790][9.45013:45048](),[9.45048][9.18459:18510](),[9.18510][9.32921:33004](),[9.32921][9.32921:33004](),[9.33004][3.321:436](),[3.436][9.33130:33154](),[9.1141][9.33130:33154](),[9.1205][9.33130:33154](),[9.1812][9.33130:33154](),[9.5356][9.33130:33154](),[9.12584][9.33130:33154](),[9.45163][9.33130:33154](),[9.33130][9.33130:33154](),[9.33154][9.18511:18567](),[9.18567][3.437:492](),[3.492][9.33305:33350](),[9.1063][9.33305:33350](),[9.1291][9.33305:33350](),[9.18622][9.33305:33350](),[9.45341][9.33305:33350](),[9.33305][9.33305:33350](),[9.33395][9.33395:33490](),[9.33490][9.3527:3573](),[9.3573][9.45342:45404](),[9.12689][9.45342:45404](),[9.33490][9.45342:45404](),[9.45404][9.3344:3376](),[9.3376][9.45490:45576](),[9.45490][9.45490:45576](),[9.45611][9.2211:2237](),[9.2237][9.12690:12719](),[9.12719][9.18623:18673](),[9.18673][9.45649:45684](),[9.33759][9.45649:45684](),[9.45684][9.18674:18724](),[9.18724][9.45685:45720](),[9.33864][9.45685:45720](),[9.45720][9.18725:18775](),[9.18775][9.33969:34032](),[9.33969][9.33969:34032](),[9.34032][9.3343:3409](),[9.3409][9.18776:18886](),[9.5410][9.18776:18886](),[9.18886][9.12720:12743](),[9.45897][9.12720:12743](),[9.12743][9.18887:18931](),[9.18931][9.45928:45963](),[9.34314][9.45928:45963](),[9.45963][9.18932:18973](),[9.18973][9.45964:45999](),[9.34410][9.45964:45999](),[9.45999][9.18974:19015](),[9.19015][9.34506:34563](),[9.34506][9.34506:34563](),[9.34620][9.34620:34682](),[9.34682][9.12744:12801](),[9.3620][9.3620:3666](),[9.3666][9.46064:46130](),[9.13009][9.46064:46130](),[9.46064][9.46064:46130](),[9.46130][9.3377:3409](),[9.3409][9.46216:46302](),[9.46216][9.46216:46302](),[9.46337][9.2238:2264](),[9.2264][9.13010:13039](),[9.13039][9.19016:19066](),[9.19066][9.46375:46410](),[9.35022][9.46375:46410](),[9.46410][9.19067:19117](),[9.19117][9.35139:35209](),[9.35139][9.35139:35209](),[9.35209][9.3410:3476](),[9.3476][9.13040:13063](),[9.5464][9.13040:13063](),[9.13063][9.19118:19160](),[9.3220][9.46442:46477](),[9.19160][9.46442:46477](),[9.35352][9.46442:46477](),[9.46477][9.19161:19201](),[9.3306][9.46478:46513](),[9.19201][9.46478:46513](),[9.35458][9.46478:46513](),[9.46513][9.19202:19479](),[9.19479][9.2208:2258](),[9.46921][9.2208:2258](),[9.35921][9.2208:2258](),[9.2308][9.2308:2360](),[9.2360][9.13064:13121](),[9.3713][9.3713:3759](),[9.3759][9.46986:47034](),[9.13329][9.46986:47034](),[9.46986][9.46986:47034](),[9.47034][9.3410:3442](),[9.3442][9.47120:47161](),[9.47120][9.47120:47161](),[9.47161][9.2497:2537](),[9.2497][9.2497:2537](),[9.2537][9.3477:3543](),[9.3543][9.19480:19538](),[9.5518][9.19480:19538](),[9.2652][9.35921:36115](),[9.19538][9.35921:36115](),[9.47252][9.35921:36115](),[9.35921][9.35921:36115](),[9.36161][9.36161:36235](),[9.36235][9.13330:13387](),[9.3806][9.3806:3852](),[9.3852][9.47317:47386](),[9.13595][9.47317:47386](),[9.47317][9.47317:47386](),[9.47386][9.3443:3475](),[9.3475][9.47472:47557](),[9.47472][9.47472:47557](),[9.47557][9.36424:36505](),[9.36424][9.36424:36505](),[9.36505][9.3544:3610](),[9.3610][9.19539:19592](),[9.5572][9.19539:19592](),[9.19592][9.36617:36661](),[9.47645][9.36617:36661](),[9.36617][9.36617:36661](),[9.36661][9.19593:19703](),[9.19703][9.36813:36839](),[9.47824][9.36813:36839](),[9.36813][9.36813:36839](),[9.36839][9.19704:19759](),[9.19759][9.36915:36969](),[9.47914][9.36915:36969](),[9.36915][9.36915:36969](),[9.37023][9.37023:37096](),[9.37096][9.13596:13653](),[9.13653][9.3853:3899](),[9.3945][9.47979:48048](),[9.13861][9.47979:48048](),[9.47979][9.47979:48048](),[9.48048][9.3476:3508](),[9.3508][9.48134:48219](),[9.48134][9.48134:48219](),[9.48219][9.37285:37331](),[9.37285][9.37285:37331](),[9.37331][9.3611:3677](),[9.3677][9.19760:19813](),[9.5626][9.19760:19813](),[9.19813][9.37451:37491](),[9.48315][9.37451:37491](),[9.37451][9.37451:37491](),[9.37491][9.19814:19924](),[9.19924][9.37659:37685](),[9.48510][9.37659:37685](),[9.37659][9.37659:37685](),[9.37685][9.19925:19980](),[9.19980][9.37769:37820](),[9.48608][9.37769:37820](),[9.37769][9.37769:37820](),[9.37871][9.37871:37944](),[9.37944][9.13862:13919](),[9.13919][9.3946:3992](),[9.4038][9.48673:48742](),[9.14127][9.48673:48742](),[9.48673][9.48673:48742](),[9.48742][9.3509:3541](),[9.3541][9.48828:48913](),[9.48828][9.48828:48913](),[9.48913][9.38133:38224](),[9.38133][9.38133:38224](),[9.38224][9.3678:3744](),[9.3744][9.19981:20093](),[9.5680][9.19981:20093](),[9.20093][9.38426:38468](),[9.49104][9.38426:38468](),[9.38426][9.38426:38468](),[9.38468][9.20094:20204](),[9.20204][9.38630:38656](),[9.49293][9.38630:38656](),[9.38630][9.38630:38656](),[9.38656][9.20205:20260](),[9.20260][9.38737:38783](),[9.49388][9.38737:38783](),[9.38737][9.38737:38783](),[9.38829][9.38829:38875](),[9.38875][9.14128:14185](),[9.14185][9.4039:4085](),[9.4131][9.49453:49522](),[9.14393][9.49453:49522](),[9.49453][9.49453:49522](),[9.49522][9.3542:3574](),[9.3574][9.49608:49693](),[9.49608][9.49608:49693](),[9.49693][9.39064:39134](),[9.39064][9.39064:39134](),[9.39134][9.3745:3811](),[9.3811][9.20261:20371](),[9.5734][9.20261:20371](),[9.20371][9.39328:39370](),[9.49876][9.39328:39370](),[9.39328][9.39328:39370](),[9.39370][9.20372:20482](),[9.20482][9.39526:39552](),[9.50059][9.39526:39552](),[9.39526][9.39526:39552](),[9.39552][9.20483:20538](),[9.20538][9.39630:39678](),[9.50151][9.39630:39678](),[9.39630][9.39630:39678](),[9.39726][9.39726:39774](),[9.39774][9.14394:14451](),[9.14451][9.4132:4178](),[9.4224][9.50216:50285](),[9.14659][9.50216:50285](),[9.50216][9.50216:50285](),[9.50285][9.3575:3607](),[9.3607][9.50371:50456](),[9.50371][9.50371:50456](),[9.50456][9.39963:40038](),[9.39963][9.39963:40038](),[9.40038][9.3812:3878](),[9.3878][9.20539:20649](),[9.5788][9.20539:20649](),[9.20649][9.40232:40274](),[9.50639][9.40232:40274](),[9.40232][9.40232:40274](),[9.40274][9.20650:20760](),[9.20760][9.40430:40456](),[9.50822][9.40430:40456](),[9.40430][9.40430:40456](),[9.40456][9.20761:20816](),[9.20816][9.40534:40572](),[9.50914][9.40534:40572](),[9.40534][9.40534:40572](),[9.40610][9.40610:40650](),[9.40650][9.4225:4271](),[9.4271][9.50915:50970](),[9.14764][9.50915:50970](),[9.40650][9.50915:50970](),[9.50970][9.3608:3640](),[9.3640][9.51056:51142](),[9.51056][9.51056:51142](),[9.51177][9.40806:40830](),[9.40806][9.40806:40830](),[9.40830][9.2265:2291](),[9.2291][9.696:743](),[9.743][9.20817:21081](),[9.21081][9.14765:14794](),[9.51546][9.14765:14794](),[9.14794][9.21082:21132](),[9.21132][9.51583:51618](),[9.41276][9.51583:51618](),[9.51618][9.21133:21184](),[9.21184][9.51619:51654](),[9.41375][9.51619:51654](),[9.51654][9.21185:21235](),[9.21235][9.41473:41483](),[9.41473][9.41473:41483](),[9.41483][9.3879:3931](),[9.3931][9.21236:21464](),[9.5883][9.21236:21464](),[9.21464][9.14795:14818](),[9.51987][9.14795:14818](),[9.14818][9.21465:21506](),[9.21506][9.52018:52053](),[9.41879][9.52018:52053](),[9.52053][9.21507:21548](),[9.21548][9.52054:52089](),[9.41968][9.52054:52089](),[9.52089][9.21549:21590](),[9.21590][9.42057:42095](),[9.42057][9.42057:42095](),[9.42133][9.42133:42173](),[9.42173][9.4272:4318](),[9.4318][9.52090:52146](),[9.14923][9.52090:52146](),[9.42173][9.52090:52146](),[9.52146][9.3641:3673](),[9.3673][9.52232:52318](),[9.52232][9.52232:52318](),[9.52353][9.42330:42354](),[9.42330][9.42330:42354](),[9.42354][9.3932:3998](),[9.3998][9.21591:21855](),[9.5937][9.21591:21855](),[9.21855][9.14924:14953](),[9.52722][9.14924:14953](),[9.14953][9.21856:21906](),[9.21906][9.52759:52794](),[9.42807][9.52759:52794](),[9.52794][9.21907:21957](),[9.21957][9.52795:52830](),[9.42905][9.52795:52830](),[9.52830][9.21958:22008](),[9.22008][9.43003:43065](),[9.43003][9.43003:43065](),[9.43065][9.3999:4051](),[9.4051][9.22009:22367](),[9.5985][9.22009:22367](),[9.22367][9.14954:14977](),[9.53345][9.14954:14977](),[9.14977][9.22368:22409](),[9.22409][9.53376:53411](),[9.43617][9.53376:53411](),[9.53411][9.22410:22452](),[9.22452][9.53412:53447](),[9.43707][9.53412:53447](),[9.53447][9.22453:22494](),[9.22494][9.2:47](),[9.43796][9.2:47](),[9.92][9.92:144](),[9.144][9.664:703](),[9.703][9.4319:4365](),[9.4365][9.53448:53489](),[9.15082][9.53448:53489](),[9.703][9.53448:53489](),[9.53489][9.3674:3706](),[9.3706][9.53575:53705](),[9.53575][9.53575:53705](),[9.53740][9.2292:2318](),[9.1313][9.355:381](),[9.2318][9.355:381](),[9.355][9.355:381](),[9.381][9.744:791](),[9.791][9.22495:22617](),[9.22617][9.574:584](),[9.53929][9.574:584](),[9.574][9.574:584](),[9.584][9.4052:4156](),[9.34][9.616:643](),[9.4156][9.616:643](),[9.6127][9.616:643](),[9.616][9.616:643](),[9.643][9.22618:22720](),[9.785][9.43796:43800](),[9.22720][9.43796:43800](),[9.54098][9.43796:43800](),[9.43796][9.43796:43800](),[9.43800][9.2:26](),[9.54][9.54:140](),[9.140][9.2:120](),[9.76][9.202:320](),[9.120][9.202:320](),[9.202][9.202:320](),[9.355][9.355:381](),[9.678][9.678:703](),[9.703][9.4157:4209](),[9.4209][9.792:839](),[9.750][9.792:839](),[9.839][9.4210:4270](),[9.4270][9.22721:22835](),[9.846][9.22721:22835](),[9.22835][9.992:1051](),[9.992][9.992:1051](),[9.1051][9.77:122](),[9.122][9.1051:1085](),[9.1051][9.1051:1085](),[9.1085][9.4271:4323](),[9.4323][9.840:888](),[9.1132][9.840:888](),[9.888][9.4324:4440](),[9.4440][9.22836:22894](),[9.1277][9.22836:22894](),[9.22894][9.121:177](),[9.177][9.1423:1427](),[9.22950][9.1423:1427](),[9.1423][9.1423:1427](),[9.1427][9.2:3](),[9.3][9.2:33](),[9.69][9.69:155](),[9.155][9.178:256](),[9.256][9.200:232](),[9.200][9.200:232](),[9.232][9.257:298](),[9.298][9.273:318](),[9.273][9.273:318](),[9.353][9.353:404](),[9.404][9.4441:4493](),[9.4493][9.889:936](),[9.451][9.889:936](),[9.936][9.497:533](),[9.497][9.497:533](),[9.533][9.4494:4546](),[9.4546][9.22951:23009](),[9.579][9.22951:23009](),[9.23009][9.299:355](),[9.355][9.741:746](),[9.23065][9.741:746](),[9.741][9.741:746](),[9.746][9.3:31](),[9.3][9.3:31](),[9.64][9.64:150](),[9.150][9.356:448](),[9.448][9.191:223](),[9.191][9.191:223](),[9.223][9.449:490](),[9.490][9.264:309](),[9.264][9.264:309](),[9.344][9.344:395](),[9.395][9.4547:4599](),[9.4599][9.937:984](),[9.442][9.937:984](),[9.984][9.4600:4660](),[9.4660][9.538:556](),[9.538][9.538:556](),[9.556][9.23066:23124](),[9.23124][9.491:547](),[9.547][9.712:716](),[9.23180][9.712:716](),[9.712][9.712:716](),[9.716][9.2:39](),[9.80][9.80:166](),[9.166][9.548:623](),[9.623][9.211:329](),[9.211][9.211:329](),[9.364][9.364:423](),[9.423][9.4661:4713](),[9.4713][9.985:1032](),[9.470][9.985:1032](),[9.1032][9.4714:4766](),[9.4766][9.562:580](),[9.562][9.562:580](),[9.580][9.23181:23239](),[9.23239][9.624:680](),[9.680][9.2:271](),[9.271][7.2:499](),[7.499][9.271:493](),[9.271][9.271:493](),[9.493][9.752:756](),[9.680][9.752:756](),[9.23295][9.752:756](),[9.752][9.752:756]()
    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