resolve conflicts

akkartik
Aug 16, 2025, 9:42 PM
FYJXSWXVSHXNII6UEG77UKQ6OSOMY2FRTAIROVJHUGAXN2HK5PPQC

Dependencies

  • [2] QIR3VBYI resolve conflicts
  • [3] 2EELKVO2 resolve conflicts
  • [4] JZG2P7IW resolve conflicts
  • [5] B4USRORM disable DPI scaling
  • [6] VZPH3XJK update source editor
  • [7] BLWAYPKV extract a module
  • [8] BJ5X5O4A let's prevent the text cursor from ever getting on a drawing
  • [9] JOPVPUSA editing source code from within the app
  • [10] BULPIBEG beginnings of a module for the text editor
  • [11] JJDUDMVX Merge lines.love
  • [12] QMRQL2FO resolve conflicts
  • [13] 4CTZOJPC stop pretending globals are local
  • [14] 6LJZN727 handle chords
  • [15] NDHQN23G done passing left/right margins everywhere
  • [16] KWHC65JI Merge lines.love
  • [17] CE4LZV4T drop last couple of manual tests
  • [18] NBTKJTI5 resolve conflicts
  • [19] CRBLAWBO resolve conflicts
  • [20] DSLD74DK lots more tests
  • [21] HGC5RGJP switch to line index in a function
  • [22] R5QXEHUI somebody stop me
  • [23] VP5KC4XZ Merge lines.love
  • [24] UBA2ZUCP remove a duplicate print to screen
  • [25] XGHCLIKB Merge lines.love
  • [26] 2H76FV5S bugfix: searching files containing unicode
  • [27] KKQKPGCI resolve conflicts
  • [28] D4B52CQ2 Merge lines.love
  • [29] RSZD5A7G forgot to add json.lua
  • [30] TYFAGQWS repeat bugfix on source editor
  • [31] LWPFEZBI Merge lines.love
  • [32] TFUNIT6M resolve conflicts
  • [33] RAXUQQ6Z Merge lines.love
  • [34] OYVFFWBK move
  • [35] SGMA5JLE save the list of tests in repo
  • [36] A4BSGS2C Merge lines.love
  • [37] ONHKBLLC Merge lines.love
  • [38] VXORMHME delete experimental REPL
  • [39] A42EMHOQ plumb through all supported args in LÖVE handlers
  • [40] 3PSFWAIL Merge lines.love
  • [41] T4FRZSYL delete an ancient, unused file
  • [42] AVTNUQYR basic test-enabled framework
  • [43] ZLJYLPOT Merge lines.love
  • [44] S2YQBEYC snapshot: test for a new regression
  • [45] LSYLEVBD drop some redundant args when clearing the cache
  • [46] GZ5WULJV switch source side to new screen-line-based render
  • [47] N2NUGNN4 include a brief reference enabling many useful apps
  • [48] KWIVKQQ7 Merge lines.love
  • [49] 2TQUKHBC Merge lines.love
  • [50] MBAJPTDJ resolve conflicts
  • [51] FS2ITYYH record a known issue
  • [52] ORRSP7FV deduce test names on failures
  • [53] 2L5MEZV3 experiment: new edit namespace
  • [54] KKMFQDR4 editing source code from within the app
  • [55] LNUHQOGH start passing in Editor_state explicitly
  • [56] LDFXFRUO bring a few things in sync between run and source
  • [57] 5SM6DRHK port inscript's bugfix to source editor
  • [58] F4RUTOND split up editor tests between LÖVE 11 and LÖVE 12
  • [59] 4SR3Z4Y3 document the version of LÖVE I've been using
  • [60] 2CTN2IEF Merge lines.love
  • [61] UN7GKYV5 support hyperlinks in the source editor
  • [62] ORKN6EOB Merge lines.love
  • [63] 73OCE2MC after much struggle, a brute-force undo
  • [64] RLCO2SNK wrap lines like lines2 forks
  • [65] S2QMLRXL stop creating a singleton table for every word
  • [66] MD3W5IRA new fork: rip out drawing support
  • [67] S7CSVBHZ resolve conflicts
  • [68] YF2ATH2Q Merge lines.love
  • [69] ISOFHXB2 App.width can no longer take a Text
  • [70] 6K5PFF6X helper: trimming whitespace from strings
  • [71] NQM25OZV reduce use of rfind
  • [72] OGUV4HSA remove some memory leaks from rendered fragments
  • [73] FZBXBUFF bugfix: search
  • [74] 4YDBYBA4 clean up memory leak experiments
  • [75] VHQCNMAR several more modules
  • [76] TGHAJBES use line cache for drawings as well
  • [77] Q7XPSKII Merge lines.love
  • [78] 6VJTQKW7 start supporting LÖVE v12
  • [79] AQMZJXUR use editor state font for width calculations
  • [80] QZUFJMD5 resolve conflicts
  • [81] YXQOITYS Merge lines.love
  • [82] VHUNJHXB Merge lines.love
  • [83] TOXPJJYY resolve conflicts
  • [84] K2X6G75Z start writing some tests for drawings
  • [85] LF7BWEG4 group all editor globals
  • [86] LAW2O3NW extract variable Margin_left
  • [87] 2JLVAYHB start decoupling editor tests from App
  • [88] 3QNOKBFM beginnings of a test harness
  • [89] 5XA7TKWY pull font into editor
  • [90] ILOA5BYF separate data structure for each line's cache data
  • [91] KMSL74GA support selections in the source editor
  • [92] XX7G2FFJ intermingle freehand line drawings with text
  • [93] QYIFOHW3 first test!
  • [94] MUJTM6RE bring back a level of wrapping
  • [95] D2GCFTTT clean up repl functionality
  • [96] EETIR4GX bugfix: skip over drawings when searching
  • [97] T42Y5MLO explicitly specify app name
  • [98] PLKNHYZ4 extract a function
  • [99] B4FAIVRA Merge lines.love
  • [100] ED4Z6ORC cleaner API for file-system access
  • [101] TVCPXAAU rename
  • [102] PFT5Y2ZY move
  • [103] RUB7L6GY resolve conflicts
  • [104] HTWAM4NZ bugfix: scrolling in left/right movements
  • [105] TLOAPLBJ add a license
  • [106] I64IPGJX avoid saving fragments in lines
  • [107] CFJ4FLCQ clean up an unnecessary mutation
  • [108] H4R5BHVY no more Text allocations
  • [109] 6Z6WH62W resolve conflicts
  • [110] 66X36NZN a little more prose describing manual_tests
  • [111] LXTTOB33 extract a couple of files
  • [112] OTIBCAUJ love2d scaffold
  • [113] 2CK5QI7W make love event names consistent
  • [114] APYPFFS3 call edit rather than App callbacks in tests
  • [*] JYZKEDDG Merge lines.love

Change contents

  • file deletion: source_text_tests.lua (----------)source_text_tests.lua (----------)
    [7.2][7.83676:83721](),[7.2][7.83676:83721](),[7.83721][7.3498:3498]()
    --
    -- 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.
    Version, Major_version = App.love_version()
    if Major_version == 11 then
    load_file_from_source_or_save_directory('source_text_tests_love11.lua')
    elseif Major_version == 12 then
    -- not released/stable yet
    load_file_from_source_or_save_directory('source_text_tests_love12.lua')
    end
  • file deletion: source_text.lua (----------)source_text.lua (----------)
    [7.2][7.147062:147101](),[7.2][7.147062:147101](),[7.147101][7.83723:83723]()
    --? -- render screen line
    --? App.color(Text_color)
    --? App.screen.print(screen_line, State.left,y)
    for pos,char in utf8chars(line.data) do
    local w = State.font:getWidth(char)
    if Text.should_word_wrap(State, line.data, pos, char, x)
    or x+w > State.width -- truncate within a word
    then
    x = 0
    x = x + w
    end
    end
    -- Check whether to word-wrap line at pos which will be positioned at x.
    --
    -- We wrap at the start of a word (non-space just after space) if the word
    -- (non-spaces followed by spaces) wouldn't fit in the rest of the line.
    --
    -- x lies between 0 and editor.width.
    --
    -- Postcondition:
    -- Current line is not wider than editor.width
    --
    -- Desired properties in priority order:
    -- Next line doesn't start with whitespace
    -- Current line ends with whitespace (a.k.a. word wrap)
    -- Current line is close to full
    -- None of these is guaranteed. But we should never satisfy a lower priority
    -- before a higher one.
    function Text.should_word_wrap(editor, line, pos, char, x)
    if char:match('%s') then return false end
    if pos == 1 then return false end
    if Text.match(line, pos-1, '%S') then return false end
    local offset = Text.offset(line, pos)
    -- most of the time a word is printable chars + whitespace
    local s = line:match('%S+%s*', offset)
    assert(s)
    local w = editor.font:getWidth(s)
    if x+w < editor.width then return false end
    if w > editor.width then return false end -- we're going to need to truncate the next word anyway
    if x < 0.8*editor.width then
    local s2 = line:match('%S+', offset)
    local w2 = editor.font:getWidth(s2)
    if x+w2 > editor.width then
    -- there'll be some non-whitespace left over for the next line
    return false
    end
    return true
    --== shortcuts that mutate text (must schedule_save)
    end
    -- create a new iterator for s which provides the index and UTF-8 bytes corresponding to each codepoint
    function utf8chars(s, startpos)
    local next_pos = startpos or 1 -- in code points
    local next_offset = utf8.offset(s, next_pos) -- in bytes
    return function()
    assert(next_offset) -- never call the iterator after it returns nil
    local curr_pos = next_pos
    next_pos = next_pos+1
    local curr_offset = next_offset
    next_offset = utf8.offset(s, 2, next_offset)
    if next_offset == nil then return end
    local curr_char = s:sub(curr_offset, next_offset-1)
    return curr_pos, curr_char
    end
    end
    if chord == 'return' then
    local before_line = State.cursor1.line
    local before = snapshot(State, before_line)
    Text.insert_return(State)
    end
    end
    end
    table.insert(line_cache.screen_line_starting_pos, pos)
    -- render colorized text
    local x = State.left
    for frag in screen_line:gmatch('%S*%s*') do
    select_color(frag)
    App.screen.print(frag, x,y)
  • file deletion: source_edit.lua (----------)source_edit.lua (----------)
    [7.2][7.165725:165764](),[7.2][7.165725:165764](),[7.165764][7.152440:152440]()
    function edit.update_font_settings(State, font_height, font)
    State.font = font or love.graphics.newFont(State.font_height)
    State.line_height = math.floor(font_height*1.3)
    State.font_height = font_height
  • edit in text_tests_love12.lua at line 12
    [7.538][7.538:979]()
    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')
  • edit in text_tests_love12.lua at line 14
    [7.984][7.984:1627]()
    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
  • edit in text_tests_love12.lua at line 966
    [7.40034][7.40034:41490]()
    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')
  • edit in text_tests_love12.lua at line 1038
    [7.44421][7.44421:45495]()
    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
  • edit in text_tests_love12.lua at line 1181
    [7.51748][7.51748:52812]()
    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
  • edit in text_tests_love12.lua at line 1206
    [7.53900][7.53900:54834]()
    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')
  • edit in text_tests_love11.lua at line 12
    [7.88147][7.88147:89231]()
    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')
  • edit in text_tests_love11.lua at line 968
    [7.127648][7.127648:129104]()
    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
  • edit in text_tests_love11.lua at line 1038
    [7.132028][7.132028:133102]()
    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
  • edit in text_tests_love11.lua at line 1179
    [7.139350][7.139350:140414]()
    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')
  • edit in text_tests_love11.lua at line 1208
    [7.141512][7.141512:142446]()
    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
  • replacement in text_tests_love11.lua at line 1870
    [7.171192][7.171192:171310]()
    Editor_state.lines = load_array{'```lines', '```', 'def', 'ghi', '’deg'} -- contains unicode quote in final line
    [7.171192]
    [7.171310]
    Editor_state.lines = load_array{'abc', 'def', 'ghi', '’deg'} -- contains unicode quote in final line
  • edit in text_tests.lua at line 2
    [7.28][7.2:283](),[7.28][7.2:283](),[7.24349][7.7673:7714](),[7.24349][7.7673:7714]()
    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')
    App.screen.check(y, 'ghi', 'screen:2')
  • edit in text_tests.lua at line 19
    [7.381][7.2:49](),[7.381][7.2:49](),[7.49][7.3:3](),[7.49][7.3:3](),[7.3][7.22495:22617](),[7.791][7.22495:22617](),[7.791][7.22495:22617](),[7.140][7.2:108](),[7.140][7.2:108]()
    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')
    Editor_state.lines = load_array{'abc', 'def', 'ghi', '’deg'} -- contains unicode quote in final line
  • edit in text.lua at line 93
    [7.124][7.1894:1938](),[7.1938][7.5:5](),[7.1938][7.5:5](),[7.5][7.222:271](),[7.168][7.222:271](),[7.168][7.222:271]()
    for frag in line.data:gmatch('%S*%s*') do
    local frag_width = State.font:getWidth(frag)
  • edit in text.lua at line 1014
    [7.442][7.167:167]()
  • replacement in source_text_tests.lua at line 2
    [7.3600][7.7:119](),[7.119][7.3600:3631](),[7.3600][7.3600:3631](),[7.3666][7.3666:3846](),[7.3846][7.8:289](),[7.289][7.812:857](),[7.4242][7.812:857](),[7.857][7.1251:1292](),[7.1292][7.942:1201](),[7.942][7.942:1201](),[7.1201][7.290:386](),[7.386][7.1363:1412](),[7.1363][7.1363:1412](),[7.1461][7.1461:1893](),[7.1893][7.7:73](),[7.73][7.387:483](),[7.1946][7.387:483](),[7.483][7.4242:4298](),[7.2116][7.4242:4298](),[7.4242][7.4242:4298](),[7.4354][7.4354:4691](),[7.4691][7.74:140](),[7.140][7.484:639](),[7.4744][7.484:639](),[7.639][7.5031:5075](),[7.5031][7.5031:5075](),[7.5119][7.5119:5299](),[7.5299][7.56:103](),[7.103][7.5345:5374](),[7.5345][7.5345:5374](),[7.5374][7.640:679](),[7.679][7.5445:5477](),[7.5445][7.5445:5477](),[7.5509][7.5509:5794](),[7.5829][7.141:193](),[7.193][7.5876:5907](),[7.5876][7.5876:5907](),[7.5938][7.5938:6162](),[7.6162][7.194:250](),[7.250][7.680:729](),[7.6210][7.680:729](),[7.729][7.6272:6304](),[7.6272][7.6272:6304](),[7.6336][7.6336:6560](),[7.6560][7.251:309](),[7.309][7.730:779](),[7.6609][7.730:779](),[7.779][7.6672:6720](),[7.6672][7.6672:6720](),[7.6768][7.6768:7001](),[7.7001][7.310:366](),[7.366][7.780:897](),[7.7049][7.780:897](),[7.897][7.7238:7283](),[7.7238][7.7238:7283](),[7.7328][7.7328:7582](),[7.7582][7.367:425](),[7.425][7.898:994](),[7.7631][7.898:994](),[7.994][7.7793:7836](),[7.7793][7.7793:7836](),[7.7879][7.7879:8105](),[7.8105][7.426:484](),[7.484][7.995:1044](),[7.8155][7.995:1044](),[7.1044][7.8229:8281](),[7.8229][7.8229:8281](),[7.8333][7.8333:8594](),[7.8594][7.485:543](),[7.543][7.1045:1094](),[7.8644][7.1045:1094](),[7.1094][7.8727:8770](),[7.8727][7.8727:8770](),[7.8813][7.8813:9075](),[7.9075][7.544:602](),[7.602][7.1095:1144](),[7.9125][7.1095:1144](),[7.1144][7.9199:9251](),[7.9199][7.9199:9251](),[7.9303][7.9303:9561](),[7.9561][7.603:661](),[7.661][7.1145:1194](),[7.9611][7.1145:1194](),[7.1194][7.9694:9753](),[7.9694][7.9694:9753](),[7.9812][7.9812:10075](),[7.10075][7.662:720](),[7.720][7.1195:1244](),[7.10125][7.1195:1244](),[7.1244][7.10215:10275](),[7.10215][7.10215:10275](),[7.10335][7.10335:10572](),[7.10572][7.721:779](),[7.779][7.1245:1341](),[7.10622][7.1245:1341](),[7.1341][7.10814:10857](),[7.10814][7.10814:10857](),[7.10900][7.10900:11130](),[7.11130][7.780:840](),[7.840][7.1342:1391](),[7.11181][7.1342:1391](),[7.1391][7.11255:11294](),[7.11255][7.11255:11294](),[7.11333][7.11333:11594](),[7.11594][7.841:901](),[7.901][7.1392:1441](),[7.11645][7.1392:1441](),[7.1441][7.11715:11763](),[7.11715][7.11715:11763](),[7.11811][7.11811:12073](),[7.12073][7.902:962](),[7.962][7.1442:1491](),[7.12124][7.1442:1491](),[7.1491][7.12203:12258](),[7.12203][7.12203:12258](),[7.12313][7.12313:12576](),[7.12576][7.1293:1353](),[7.1024][7.1492:1541](),[7.1353][7.1492:1541](),[7.12627][7.1492:1541](),[7.1541][7.12713:12769](),[7.12713][7.12713:12769](),[7.12825][7.12825:13062](),[7.13062][7.1354:1414](),[7.1086][7.1542:1638](),[7.1414][7.1542:1638](),[7.13113][7.1542:1638](),[7.1638][7.13297:13302](),[7.13297][7.13297:13302](),[7.13302][7.104:139](),[7.179][7.179:218](),[7.218][7.13462:13508](),[7.13462][7.13462:13508](),[7.13508][7.219:274](),[7.274][7.13556:13588](),[7.13556][7.13556:13588](),[7.13588][7.275:316](),[7.316][7.13629:13674](),[7.13629][7.13629:13674](),[7.13709][7.317:348](),[7.348][7.1415:1488](),[7.1488][7.443:531](),[7.443][7.443:531](),[7.531][7.1639:1749](),[7.1749][7.697:754](),[7.697][7.697:754](),[7.754][7.1750:1868](),[7.129][7.13950:13955](),[7.928][7.13950:13955](),[7.1868][7.13950:13955](),[7.13950][7.13950:13955](),[7.13955][7.929:967](),[7.1010][7.14058:14352](),[7.14058][7.14058:14352](),[7.14387][7.7:38](),[7.38][7.14387:14569](),[7.14387][7.14387:14569](),[7.14569][7.1869:2076](),[7.269][7.14763:14768](),[7.1311][7.14763:14768](),[7.2076][7.14763:14768](),[7.14763][7.14763:14768](),[7.14768][7.1312:1361](),[7.1415][7.14893:15257](),[7.14893][7.14893:15257](),[7.15292][7.39:70](),[7.70][7.15292:15451](),[7.15292][7.15292:15451](),[7.15451][7.2077:2284](),[7.420][7.15667:15672](),[7.1749][7.15667:15672](),[7.2284][7.15667:15672](),[7.15667][7.15667:15672](),[7.15672][7.1750:1786](),[7.1827][7.15771:16067](),[7.15771][7.15771:16067](),[7.16102][7.71:102](),[7.102][7.16102:16261](),[7.16102][7.16102:16261](),[7.16261][7.103:285](),[7.285][2.7:54](),[2.54][7.323:589](),[7.323][7.323:589](),[7.624][7.624:796](),[7.796][2.55:192](),[2.192][7.822:948](),[7.2336][7.822:948](),[7.948][7.16352:16383](),[7.1908][7.16352:16383](),[7.2336][7.16352:16383](),[7.16352][7.16352:16383](),[7.16414][7.16414:16673](),[7.16708][7.16708:16763](),[7.16763][7.2337:2378](),[7.2378][7.16823:16858](),[7.16823][7.16823:16858](),[7.16858][7.2379:2420](),[7.2420][7.16918:16953](),[7.16918][7.16918:16953](),[7.16953][7.2421:2462](),[7.2462][7.17013:17053](),[7.17013][7.17013:17053](),[7.17093][7.17093:17353](),[7.17388][7.17388:17443](),[7.17443][7.2463:2504](),[7.2504][7.17512:17547](),[7.17512][7.17512:17547](),[7.17547][7.2505:2545](),[7.2545][7.17615:17650](),[7.17615][7.17615:17650](),[7.17650][7.2546:2587](),[7.2587][7.17719:17764](),[7.17719][7.17719:17764](),[7.17809][7.17809:18068](),[7.18103][7.18103:18158](),[7.18158][7.2588:2630](),[7.2630][7.18233:18268](),[7.18233][7.18233:18268](),[7.18268][7.2631:2673](),[7.2673][7.18343:18378](),[7.18343][7.18343:18378](),[7.18378][7.2674:2715](),[7.2715][7.18452:18457](),[7.18452][7.18452:18457](),[7.18457][7.1909:1948](),[7.1948][7.6:63](),[7.63][7.18612:18881](),[7.18612][7.18612:18881](),[7.18916][7.18916:19075](),[7.19075][7.2716:2923](),[7.561][7.19271:19276](),[7.2296][7.19271:19276](),[7.2923][7.19271:19276](),[7.19271][7.19271:19276](),[7.19276][7.2297:2363](),[7.2363][7.64:121](),[7.121][7.19485:19817](),[7.19485][7.19485:19817](),[7.19852][7.19852:20011](),[7.20011][7.2924:3131](),[7.729][7.20261:20377](),[7.2819][7.20261:20377](),[7.3131][7.20261:20377](),[7.20261][7.20261:20377](),[7.20429][7.20429:20690](),[7.20725][7.20725:20780](),[7.20780][7.3132:3175](),[7.3175][7.20863:20898](),[7.20863][7.20863:20898](),[7.20898][7.3176:3219](),[7.3219][7.20981:21016](),[7.20981][7.20981:21016](),[7.21016][7.3220:3261](),[7.3261][7.21097:21201](),[7.21097][7.21097:21201](),[7.21262][7.21262:21562](),[7.21597][7.21597:21652](),[7.21652][7.3262:3303](),[7.3303][7.21742:21777](),[7.21742][7.21742:21777](),[7.21777][7.3304:3346](),[7.3346][7.21868:21903](),[7.21868][7.21868:21903](),[7.21903][7.3347:3391](),[7.3391][7.21996:22001](),[7.21996][7.21996:22001](),[7.22001][7.122:168](),[7.168][7.22084:22418](),[7.22084][7.22084:22418](),[7.22453][7.22453:22508](),[7.22508][7.3392:3445](),[7.3445][7.22593:22628](),[7.22593][7.22593:22628](),[7.22628][7.3446:3499](),[7.3499][7.22713:22860](),[7.22713][7.22713:22860](),[7.22860][7.7:85](),[7.85][7.3500:3556](),[7.22900][7.3500:3556](),[7.3556][7.86:141](),[7.141][7.23075:23158](),[7.3611][7.23075:23158](),[7.23075][7.23075:23158](),[7.23241][7.23241:23603](),[7.23638][7.23638:23693](),[7.23693][7.3612:3665](),[7.3665][7.23817:23964](),[7.23817][7.23817:23964](),[7.23964][7.142:220](),[7.220][7.3666:3722](),[7.24004][7.3666:3722](),[7.3722][7.221:276](),[7.276][7.24257:24310](),[7.3777][7.24257:24310](),[7.24257][7.24257:24310](),[7.24363][7.24363:24697](),[7.24732][7.24732:24787](),[7.24787][7.3778:3831](),[7.3831][7.24881:24916](),[7.24881][7.24881:24916](),[7.24916][7.3832:3885](),[7.3885][7.25010:25045](),[7.25010][7.25010:25045](),[7.25045][7.3886:3935](),[7.3935][7.25135:25303](),[7.25135][7.25135:25303](),[7.25303][7.3936:4037](),[7.4037][7.25445:25519](),[7.25445][7.25445:25519](),[7.25593][7.25593:25986](),[7.26021][7.26021:26076](),[7.26076][7.4038:4091](),[7.4091][7.26191:26226](),[7.26191][7.26191:26226](),[7.26226][7.4092:4147](),[7.4147][7.26343:26378](),[7.26343][7.26343:26378](),[7.26378][7.4148:4197](),[7.4197][7.26489:26657](),[7.26489][7.26489:26657](),[7.26657][7.4198:4299](),[7.4299][7.26820:26878](),[7.26820][7.26820:26878](),[7.26936][7.26936:27425](),[7.27460][7.27460:27515](),[7.27515][7.4300:4367](),[7.4367][7.27628:27776](),[7.27628][7.27628:27776](),[7.27776][7.277:406](),[7.406][7.730:763](),[7.4419][7.730:763](),[7.27913][7.730:763](),[7.796][7.796:1072](),[7.1107][7.1107:1185](),[7.1185][7.1489:1549](),[7.1148][7.1236:1269](),[7.1549][7.1236:1269](),[7.1236][7.1236:1269](),[7.1269][7.2820:2863](),[7.2863][7.6:59](),[7.59][7.4420:4652](),[7.4652][7.1682:1750](),[7.1682][7.1682:1750](),[7.1818][7.1818:2158](),[7.2193][7.2193:2257](),[7.2257][7.1550:1608](),[7.1208][7.2306:2349](),[7.1608][7.2306:2349](),[7.2306][7.2306:2349](),[7.2349][7.4653:4758](),[7.2560][7.27913:27918](),[7.4758][7.27913:27918](),[7.27913][7.27913:27918](),[7.27918][7.2561:2600](),[7.2644][7.2644:2984](),[7.3019][7.3019:3062](),[7.3062][7.2916:2963](),[7.2963][7.3108:3164](),[7.3108][7.3108:3164](),[7.3164][7.4759:4814](),[7.4814][7.3245:3304](),[7.3245][7.3245:3304](),[7.3363][7.3363:3703](),[7.3738][7.3738:3846](),[7.3846][7.2964:3085](),[7.3085][7.3969:4058](),[7.3969][7.3969:4058](),[7.4058][7.4815:4920](),[7.4920][7.4251:4302](),[7.4251][7.4251:4302](),[7.4353][7.4353:4686](),[7.4721][7.4721:4767](),[7.4767][7.1209:1261](),[7.1261][7.4921:4965](),[7.4814][7.4921:4965](),[7.4965][7.4897:4952](),[7.4897][7.4897:4952](),[7.4952][7.4966:5013](),[7.5013][7.5032:5057](),[7.5032][7.5032:5057](),[7.5082][7.5082:5422](),[7.5457][7.5457:5500](),[7.5500][7.1262:1314](),[7.1314][7.5014:5058](),[7.5547][7.5014:5058](),[7.5058][7.5604:5634](),[7.5604][7.5604:5634](),[7.5634][7.5059:5112](),[7.5112][7.5700:5746](),[7.5700][7.5700:5746](),[7.5792][7.5792:6132](),[7.6167][7.6167:6257](),[7.6257][7.1315:1367](),[7.1367][7.6304:6417](),[7.6304][7.6304:6417](),[7.6417][7.5113:5171](),[7.5171][7.6503:6554](),[7.6503][7.6503:6554](),[7.6605][7.6605:6896](),[7.6931][7.6931:6986](),[7.6986][7.5172:5222](),[7.5222][7.7075:7110](),[7.7075][7.7075:7110](),[7.7110][7.5223:5273](),[7.5273][7.7199:7234](),[7.7199][7.7199:7234](),[7.7234][7.5274:5324](),[7.5324][7.7323:7457](),[7.7323][7.7323:7457](),[7.7457][7.1368:1434](),[7.1434][7.7510:7531](),[7.7510][7.7510:7531](),[7.7531][7.5325:5433](),[7.5433][7.7711:7716](),[7.7711][7.7711:7716](),[7.7716][7.27918:27953](),[7.27918][7.27918:27953](),[7.27993][7.27993:28251](),[7.28286][7.28286:28312](),[7.28312][7.3086:3133](),[7.3133][7.28358:28387](),[7.28358][7.28358:28387](),[7.28387][7.5434:5475](),[7.5475][7.28456:28491](),[7.28456][7.28456:28491](),[7.28491][7.5476:5516](),[7.5516][7.28559:28594](),[7.28559][7.28559:28594](),[7.28594][7.5517:5557](),[7.5557][7.28662:28698](),[7.28662][7.28662:28698](),[7.28734][7.28734:29042](),[7.29077][7.29077:29132](),[7.29132][7.5558:5608](),[7.5608][7.29206:29241](),[7.29206][7.29206:29241](),[7.29241][7.5609:5659](),[7.5659][7.29315:29350](),[7.29315][7.29315:29350](),[7.29350][7.5660:5710](),[7.5710][7.29424:29467](),[7.29424][7.29424:29467](),[7.29467][7.1435:1495](),[7.1495][7.5711:5880](),[7.29517][7.5711:5880](),[7.5880][7.29758:29781](),[7.29758][7.29758:29781](),[7.29781][7.5881:5920](),[7.5920][7.29844:29879](),[7.29844][7.29844:29879](),[7.29879][7.5921:5961](),[7.5961][7.29943:29978](),[7.29943][7.29943:29978](),[7.29978][7.5962:6003](),[7.6003][7.30043:30096](),[7.30043][7.30043:30096](),[7.30149][7.30149:30431](),[7.30466][7.30466:30509](),[7.30509][7.1496:1556](),[7.1556][7.6004:6223](),[7.30559][7.6004:6223](),[7.6223][7.30942:30985](),[7.30942][7.30942:30985](),[7.31028][7.31028:31336](),[7.31371][7.31371:31426](),[7.31426][7.6224:6274](),[7.6274][7.31507:31542](),[7.31507][7.31507:31542](),[7.31542][7.6275:6325](),[7.6325][7.31623:31658](),[7.31623][7.31623:31658](),[7.31658][7.6326:6376](),[7.6376][7.31739:31838](),[7.31739][7.31739:31838](),[7.31838][7.1557:1609](),[7.1609][7.6377:6546](),[7.31885][7.6377:6546](),[7.6546][7.32147:32170](),[7.32147][7.32147:32170](),[7.32170][7.6547:6588](),[7.6588][7.32242:32277](),[7.32242][7.32242:32277](),[7.32277][7.6589:6630](),[7.6630][7.32349:32384](),[7.32349][7.32349:32384](),[7.32384][7.6631:6672](),[7.6672][7.8022:8067](),[7.8022][7.8022:8067](),[7.8112][7.8112:8370](),[7.8405][7.8405:8436](),[7.8436][7.1609:1682](),[7.1682][7.8531:8806](),[7.8531][7.8531:8806](),[7.8806][7.6673:6905](),[7.6905][7.120:443](),[7.478][7.478:509](),[7.509][7.1683:1756](),[7.1756][7.604:908](),[7.604][7.604:908](),[7.908][7.9170:9175](),[7.6905][7.9170:9175](),[7.9170][7.9170:9175](),[7.9175][7.909:1297](),[7.1332][7.1332:1374](),[7.1374][7.7:33](),[7.33][7.1374:2356](),[7.1374][7.1374:2356](),[7.2391][7.2391:2939](),[7.2939][7.9175:9225](),[7.9175][7.9175:9225](),[7.9280][7.9280:9538](),[7.9573][7.9573:9604](),[7.9604][7.1757:1830](),[7.1830][7.9699:10213](),[7.9699][7.9699:10213](),[7.10213][7.6906:7138](),[7.7138][7.10617:10683](),[7.10617][7.10617:10683](),[7.10749][7.10749:11039](),[7.11074][7.11074:11105](),[7.11105][7.1831:1904](),[7.1904][7.11200:12097](),[7.11200][7.11200:12097](),[7.12097][7.7139:7371](),[7.7371][7.2940:3260](),[7.3295][7.3295:3367](),[7.3367][7.1610:1662](),[7.1662][7.3414:3735](),[7.3414][7.3414:3735](),[7.3735][7.33196:33201](),[7.7371][7.33196:33201](),[7.12545][7.33196:33201](),[7.33196][7.33196:33201](),[7.33201][7.12546:12584](),[7.12627][7.12627:12935](),[7.12970][7.12970:13066](),[7.13066][7.1663:1715](),[7.1715][7.13113:13127](),[7.13113][7.13113:13127](),[7.13127][7.7372:7423](),[7.7423][7.13203:13208](),[7.13203][7.13203:13208](),[7.13208][7.33201:33226](),[7.33201][7.33201:33226](),[7.33256][7.33256:33515](),[7.33550][7.33550:33654](),[7.33654][7.7424:7474](),[7.7474][7.33722:33757](),[7.33722][7.33722:33757](),[7.33757][7.7475:7525](),[7.7525][7.33825:33877](),[7.33825][7.33825:33877](),[7.33877][7.1716:1780](),[7.1780][7.7526:7636](),[7.33929][7.7526:7636](),[7.7636][7.34075:34098](),[7.34075][7.34075:34098](),[7.34098][7.7637:7678](),[7.7678][7.34157:34192](),[7.34157][7.34157:34192](),[7.34192][7.7679:7720](),[7.7720][7.2117:2162](),[7.34251][7.2117:2162](),[7.2207][7.2207:2702](),[7.2702][7.7721:7789](),[7.7789][7.2803:2889](),[7.2803][7.2803:2889](),[7.2924][7.2924:3224](),[7.3224][7.7790:7840](),[7.7840][7.3307:3464](),[7.3307][7.3307:3464](),[7.3464][7.1781:1845](),[7.1845][7.7841:7951](),[7.3516][7.7841:7951](),[7.7951][7.3692:3732](),[7.3692][7.3692:3732](),[7.3732][7.7952:7993](),[7.3806][7.34251:34325](),[7.7993][7.34251:34325](),[7.34251][7.34251:34325](),[7.34399][7.34399:34774](),[7.34809][7.34809:34864](),[7.34864][7.7994:8045](),[7.8045][7.34977:35012](),[7.34977][7.34977:35012](),[7.35012][7.8046:8097](),[7.8097][7.35125:35160](),[7.35125][7.35125:35160](),[7.35160][7.8098:8149](),[7.8149][7.35273:35336](),[7.35273][7.35273:35336](),[7.35336][7.1846:1910](),[7.1910][7.8150:8276](),[7.35388][7.8150:8276](),[7.8276][7.35638:35661](),[7.35638][7.35638:35661](),[7.35661][7.8277:8319](),[7.8319][7.35765:35800](),[7.35765][7.35765:35800](),[7.35800][7.8320:8362](),[7.8362][7.35904:35939](),[7.35904][7.35904:35939](),[7.35939][7.7:325](),[7.46][7.36043:36088](),[7.325][7.36043:36088](),[7.8405][7.36043:36088](),[7.36043][7.36043:36088](),[7.36133][7.36133:36454](),[7.36489][7.36489:36545](),[7.36545][7.1911:1975](),[7.1975][7.8406:8532](),[7.36597][7.8406:8532](),[7.8532][7.36789:36834](),[7.36789][7.36789:36834](),[7.36879][7.36879:37145](),[7.37180][7.37180:37286](),[7.37286][7.8533:8583](),[7.8583][7.37369:37404](),[7.37369][7.37369:37404](),[7.37404][7.8584:8634](),[7.8634][7.37487:37522](),[7.37487][7.37487:37522](),[7.37522][7.8635:8685](),[7.8685][7.37605:37672](),[7.37605][7.37605:37672](),[7.37672][7.1976:2032](),[7.2032][7.8686:8796](),[7.37720][7.8686:8796](),[7.8796][7.37896:37948](),[7.37896][7.37896:37948](),[7.37948][7.8797:8838](),[7.8838][7.38022:38057](),[7.38022][7.38022:38057](),[7.38057][7.8839:8880](),[7.8880][7.38131:38166](),[7.38131][7.38131:38166](),[7.38166][7.8881:8922](),[7.8922][7.63:596](),[7.631][7.631:1065](),[7.1065][7.2033:2089](),[7.2089][7.1113:1164](),[7.1113][7.1113:1164](),[7.1164][7.38240:38297](),[7.8922][7.38240:38297](),[7.38240][7.38240:38297](),[7.38354][7.38354:38690](),[7.38725][7.38725:38780](),[7.38780][7.8923:8973](),[7.8973][7.38875:38910](),[7.38875][7.38875:38910](),[7.38910][7.8974:9024](),[7.9024][7.39005:39040](),[7.39005][7.39005:39040](),[7.39040][7.9025:9075](),[7.9075][7.39135:39205](),[7.39135][7.39135:39205](),[7.39205][7.2090:2146](),[7.2146][7.9076:9186](),[7.39253][7.9076:9186](),[7.9186][7.39453:39476](),[7.39453][7.39453:39476](),[7.39476][7.9187:9228](),[7.9228][7.39562:39597](),[7.39562][7.39562:39597](),[7.39597][7.9229:9270](),[7.9270][7.39683:39718](),[7.39683][7.39683:39718](),[7.39718][7.9271:9312](),[7.9312][7.39804:39868](),[7.39804][7.39804:39868](),[7.39932][7.39932:40289](),[7.40324][7.40324:40379](),[7.40379][7.9313:9363](),[7.9363][7.40481:40516](),[7.40481][7.40481:40516](),[7.40516][7.9364:9414](),[7.9414][7.40618:40653](),[7.40618][7.40618:40653](),[7.40653][7.9415:9513](),[7.9513][7.40803:40873](),[7.40803][7.40803:40873](),[7.40873][7.2147:2203](),[7.2203][7.9514:9683](),[7.40921][7.9514:9683](),[7.9683][7.41246:41269](),[7.41246][7.41246:41269](),[7.41269][7.9684:9725](),[7.9725][7.41362:41397](),[7.41362][7.41362:41397](),[7.41397][7.9726:9768](),[7.9768][7.41491:41526](),[7.41491][7.41491:41526](),[7.41526][7.9769:9810](),[7.9810][7.41619:41711](),[7.41619][7.41619:41711](),[7.41803][7.41803:42159](),[7.42194][7.42194:42249](),[7.42249][7.9811:9861](),[7.9861][7.42379:42414](),[7.42379][7.42379:42414](),[7.42414][7.9862:9912](),[7.9912][7.42544:42579](),[7.42544][7.42544:42579](),[7.42579][7.9913:9964](),[7.9964][7.42710:42780](),[7.42710][7.42710:42780](),[7.42780][7.2204:2260](),[7.2260][7.9965:10134](),[7.42828][7.9965:10134](),[7.10134][7.43237:43260](),[7.43237][7.43237:43260](),[7.43260][7.10135:10176](),[7.10176][7.43381:43416](),[7.43381][7.43381:43416](),[7.43416][7.10177:10219](),[7.10219][7.43538:43573](),[7.43538][7.43538:43573](),[7.43573][7.10220:10260](),[7.10260][7.43693:43698](),[7.43693][7.43693:43698](),[7.43698][7.3134:3208](),[7.3287][7.43853:44139](),[7.43853][7.43853:44139](),[7.44174][7.44174:44229](),[7.44229][7.10261:10311](),[7.3405][7.44347:44382](),[7.10311][7.44347:44382](),[7.44347][7.44347:44382](),[7.44382][7.10312:10362](),[7.3523][7.44500:44535](),[7.10362][7.44500:44535](),[7.44500][7.44500:44535](),[7.44535][7.10363:10414](),[7.3642][7.44654:44730](),[7.10414][7.44654:44730](),[7.44654][7.44654:44730](),[7.44730][7.2261:2325](),[7.2325][7.10415:10614](),[7.44782][7.10415:10614](),[7.4043][7.45185:45287](),[7.10614][7.45185:45287](),[7.45185][7.45185:45287](),[7.45287][7.2326:2382](),[7.2382][7.10615:10784](),[7.45335][7.10615:10784](),[7.4414][7.45708:45731](),[7.10784][7.45708:45731](),[7.45708][7.45708:45731](),[7.45731][7.10785:10827](),[7.4524][7.45841:45876](),[7.10827][7.45841:45876](),[7.45841][7.45841:45876](),[7.45876][7.10828:10868](),[7.4632][7.45984:46019](),[7.10868][7.45984:46019](),[7.45984][7.45984:46019](),[7.46019][7.10869:10910](),[7.4741][7.46128:46171](),[7.10910][7.46128:46171](),[7.46128][7.46128:46171](),[7.46214][7.46214:46546](),[7.46581][7.46581:46636](),[7.46636][7.10911:10961](),[7.10961][7.46717:46752](),[7.46717][7.46717:46752](),[7.46752][7.10962:11012](),[7.11012][7.46833:46868](),[7.46833][7.46833:46868](),[7.46868][7.11013:11063](),[7.11063][7.46949:47011](),[7.46949][7.46949:47011](),[7.47011][7.2383:2435](),[7.2435][7.11064:11174](),[7.47057][7.11064:11174](),[7.11174][7.47229:47281](),[7.47229][7.47229:47281](),[7.47281][7.11175:11216](),[7.11216][7.47353:47388](),[7.47353][7.47353:47388](),[7.47388][7.11217:11258](),[7.11258][7.47460:47495](),[7.47460][7.47460:47495](),[7.47495][7.11259:11300](),[7.11300][7.47567:47572](),[7.47567][7.47567:47572](),[7.47572][7.1165:1691](),[7.1726][7.1726:2156](),[7.2156][7.2436:2488](),[7.2488][7.2202:2258](),[7.2202][7.2202:2258](),[7.2258][7.47572:47620](),[7.47572][7.47572:47620](),[7.47673][7.47673:47994](),[7.48029][7.48029:48084](),[7.48084][7.11301:11351](),[7.11351][7.48175:48210](),[7.48175][7.48175:48210](),[7.48210][7.11352:11402](),[7.11402][7.48301:48336](),[7.48301][7.48301:48336](),[7.48336][7.11403:11453](),[7.11453][7.48427:48493](),[7.48427][7.48427:48493](),[7.48493][7.2489:2541](),[7.2541][7.11454:11564](),[7.48539][7.11454:11564](),[7.11564][7.48731:48754](),[7.48731][7.48731:48754](),[7.48754][7.11565:11606](),[7.11606][7.48836:48871](),[7.48836][7.48836:48871](),[7.48871][7.11607:11648](),[7.11648][7.48953:48988](),[7.48953][7.48953:48988](),[7.48988][7.11649:11690](),[7.11690][7.6:427](),[7.462][7.462:813](),[7.813][7.2542:2594](),[7.2594][7.859:969](),[7.859][7.859:969](),[7.969][7.49070:49130](),[7.11690][7.49070:49130](),[7.49070][7.49070:49130](),[7.49190][7.49190:49539](),[7.49574][7.49574:49629](),[7.49629][7.11691:11741](),[7.11741][7.49727:49762](),[7.49727][7.49727:49762](),[7.49762][7.11742:11792](),[7.11792][7.49860:49935](),[7.49860][7.49860:49935](),[7.49935][7.2595:2647](),[7.2647][7.49981:50004](),[7.49981][7.49981:50004](),[7.50004][7.11793:11835](),[7.11835][7.50094:50129](),[7.50094][7.50094:50129](),[7.50129][7.11836:11877](),[7.11877][7.50218:50253](),[7.50218][7.50218:50253](),[7.50253][7.11878:12155](),[7.12155][7.50761:50823](),[7.50761][7.50761:50823](),[7.50885][7.50885:51223](),[7.51258][7.51258:51313](),[7.51313][7.12156:12206](),[7.12206][7.51413:51448](),[7.51413][7.51413:51448](),[7.51448][7.12207:12257](),[7.12257][7.51548:51583](),[7.51548][7.51548:51583](),[7.51583][7.12258:12308](),[7.12308][7.51683:51775](),[7.51683][7.51683:51775](),[7.51775][7.2648:2700](),[7.2700][7.51821:51844](),[7.51821][7.51821:51844](),[7.51844][7.12309:12350](),[7.12350][7.51935:51970](),[7.51935][7.51935:51970](),[7.51970][7.12351:12392](),[7.12392][7.52061:52096](),[7.52061][7.52061:52096](),[7.52096][7.12393:12670](),[7.12670][7.52614:52669](),[7.52614][7.52614:52669](),[7.52724][7.52724:53079](),[7.53114][7.53114:53169](),[7.53169][7.12671:12721](),[7.12721][7.53262:53297](),[7.53262][7.53262:53297](),[7.53297][7.12722:12772](),[7.12772][7.53390:53425](),[7.53390][7.53390:53425](),[7.53425][7.12773:12823](),[7.12823][7.53518:53584](),[7.53518][7.53518:53584](),[7.53584][7.2701:2753](),[7.2753][7.12824:12934](),[7.53630][7.12824:12934](),[7.12934][7.53826:53906](),[7.53826][7.53826:53906](),[7.53906][7.12935:12976](),[7.12976][7.53990:54025](),[7.53990][7.53990:54025](),[7.54025][7.12977:13018](),[7.13018][7.54109:54137](),[7.54109][7.54109:54137](),[7.54165][7.54165:54424](),[7.54459][7.54459:54562](),[7.54562][7.13019:13069](),[7.13069][7.54628:54663](),[7.54628][7.54628:54663](),[7.54663][7.13070:13120](),[7.13120][7.54729:54777](),[7.54729][7.54729:54777](),[7.54777][7.2754:2814](),[7.2814][7.13121:13231](),[7.54827][7.13121:13231](),[7.13231][7.54969:54992](),[7.54969][7.54969:54992](),[7.54992][7.13232:13273](),[7.13273][7.55049:55084](),[7.55049][7.55049:55084](),[7.55084][7.13274:13315](),[7.13315][7.55141:55195](),[7.55141][7.55141:55195](),[7.55249][7.55249:55606](),[7.55641][7.55641:55696](),[7.55696][7.13316:13366](),[7.13366][7.55788:55823](),[7.55788][7.55788:55823](),[7.55823][7.13367:13417](),[7.13417][7.55915:55950](),[7.55915][7.55915:55950](),[7.55950][7.13418:13515](),[7.13515][7.56089:56153](),[7.56089][7.56089:56153](),[7.56153][7.2815:2875](),[7.2875][7.13516:13685](),[7.56203][7.13516:13685](),[7.13685][7.56498:56521](),[7.56498][7.56498:56521](),[7.56521][7.13686:13728](),[7.13728][7.56605:56640](),[7.56605][7.56605:56640](),[7.56640][7.13729:13770](),[7.13770][7.56723:56758](),[7.56723][7.56723:56758](),[7.56758][7.13771:13812](),[7.13812][7.56841:56904](),[7.56841][7.56841:56904](),[7.56967][7.56967:57342](),[7.57377][7.57377:57432](),[7.57432][7.13813:13863](),[7.13863][7.57533:57568](),[7.57533][7.57533:57568](),[7.57568][7.13864:13961](),[7.13961][7.57716:57780](),[7.57716][7.57716:57780](),[7.57780][7.2876:2936](),[7.2936][7.13962:14131](),[7.57830][7.13962:14131](),[7.14131][7.58152:58175](),[7.58152][7.58152:58175](),[7.58175][7.14132:14174](),[7.14174][7.58268:58303](),[7.58268][7.58268:58303](),[7.58303][7.14175:14216](),[7.14216][7.58395:58430](),[7.58395][7.58395:58430](),[7.58430][7.14217:14259](),[7.14259][7.58523:58578](),[7.58523][7.58523:58578](),[7.58633][7.58633:58968](),[7.59003][7.59003:59058](),[7.59058][7.14260:14310](),[7.14310][7.59151:59186](),[7.59151][7.59151:59186](),[7.59186][7.14311:14361](),[7.14361][7.59279:59314](),[7.59279][7.59279:59314](),[7.59314][7.14362:14412](),[7.14412][7.59407:59464](),[7.59407][7.59407:59464](),[7.59464][7.2937:2997](),[7.2997][7.14413:14582](),[7.59514][7.14413:14582](),[7.14582][7.59812:59835](),[7.59812][7.59812:59835](),[7.59835][7.14583:14624](),[7.14624][7.59919:59954](),[7.59919][7.59919:59954](),[7.59954][7.14625:14664](),[7.14664][7.60036:60071](),[7.60036][7.60036:60071](),[7.60071][7.14665:14705](),[7.14705][7.60154:60236](),[7.60154][7.60154:60236](),[7.60318][7.60318:60645](),[7.60680][7.60680:60735](),[7.60735][7.14706:14756](),[7.14756][7.60855:60920](),[7.60855][7.60855:60920](),[7.60920][7.2998:3058](),[7.3058][7.14757:14926](),[7.60970][7.14757:14926](),[7.14926][7.61349:61372](),[7.61349][7.61349:61372](),[7.61372][7.14927:14966](),[7.14966][7.61481:61516](),[7.61481][7.61481:61516](),[7.61516][7.14967:15007](),[7.15007][7.61626:61717](),[7.61626][7.61626:61717](),[7.61808][7.61808:62123](),[7.62158][7.62158:62258](),[7.62258][7.4742:4789](),[7.4789][7.15008:15177](),[7.15177][7.62710:62739](),[7.62710][7.62710:62739](),[7.62739][7.15178:15217](),[7.15217][7.62857:62913](),[7.62857][7.62857:62913](),[7.62969][7.62969:63304](),[7.63339][7.63339:63394](),[7.63394][7.15218:15268](),[7.15268][7.63488:63523](),[7.63488][7.63488:63523](),[7.63523][7.15269:15319](),[7.15319][7.63617:63652](),[7.63617][7.63617:63652](),[7.63652][7.15320:15370](),[7.15370][7.63746:63817](),[7.63746][7.63746:63817](),[7.63817][7.4790:4931](),[7.4931][7.15371:15540](),[7.15540][7.64256:64279](),[7.64256][7.64256:64279](),[7.64279][7.15541:15582](),[7.15582][7.64364:64399](),[7.64364][7.64364:64399](),[7.64399][7.15583:15625](),[7.15625][7.64485:64520](),[7.64485][7.64485:64520](),[7.64520][7.15626:15666](),[7.15666][7.64604:64608](),[7.64604][7.64604:64608]()
    -- Arguably this should be called source_edit_tests.lua,
    -- but that would mess up the git blame at this point.
    function test_initial_state()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{}
    Text.redraw_all(Editor_state)
    edit.draw(Editor_state)
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
    end
    function test_click_to_create_drawing()
    App.screen.init{width=800, height=600}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{}
    Text.redraw_all(Editor_state)
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, 8,Editor_state.top+8, 1)
    -- cursor skips drawing to always remain on text
    check_eq(#Editor_state.lines, 2, '#lines')
    check_eq(Editor_state.cursor1.line, 2, 'cursor')
    end
    function test_backspace_to_delete_drawing()
    -- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'```lines', '```', ''}
    Text.redraw_all(Editor_state)
    -- cursor is on text as always (outside tests this will get initialized correctly)
    Editor_state.cursor1.line = 2
    -- backspacing deletes the drawing
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    end
    function test_backspace_from_start_of_final_line()
    -- display final line of text with cursor at start of it
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Editor_state.screen_top1 = {line=2, pos=1}
    Editor_state.cursor1 = {line=2, pos=1}
    Text.redraw_all(Editor_state)
    -- backspace scrolls up
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(#Editor_state.lines, 1, '#lines')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    end
    function test_insert_first_character()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{}
    Text.redraw_all(Editor_state)
    edit.draw(Editor_state)
    edit.run_after_text_input(Editor_state, 'a')
    local y = Editor_state.top
    App.screen.check(y, 'a', 'screen:1')
    end
    function test_press_ctrl()
    -- press ctrl while the cursor is on text
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{''}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.run_after_keychord(Editor_state, 'C-m', 'm')
    end
    function test_move_left()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'a'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_right()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'a'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'right', 'right')
    check_eq(Editor_state.cursor1.pos, 2, 'check')
    end
    function test_move_left_to_previous_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'left', 'left')
    check_eq(Editor_state.cursor1.line, 1, 'line')
    check_eq(Editor_state.cursor1.pos, 4, 'pos') -- past end of line
    end
    function test_move_right_to_next_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- past end of line
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'right', 'right')
    check_eq(Editor_state.cursor1.line, 2, 'line')
    check_eq(Editor_state.cursor1.pos, 1, 'pos')
    end
    function test_move_to_start_of_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_to_start_of_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_skip_to_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=5} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_skip_past_tab_to_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def\tghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=10} -- within third word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 9, 'check')
    end
    function test_skip_multiple_spaces_to_previous_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=6} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'check')
    end
    function test_move_to_start_of_word_on_previous_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left', 'left')
    check_eq(Editor_state.cursor1.line, 1, 'line')
    check_eq(Editor_state.cursor1.pos, 5, 'pos')
    end
    function test_move_past_end_of_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 4, 'check')
    end
    function test_skip_to_next_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 8, 'check')
    end
    function test_skip_past_tab_to_next_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc\tdef'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 4, 'check')
    end
    function test_skip_multiple_spaces_to_next_word()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.pos, 9, 'check')
    end
    function test_move_past_end_of_word_on_next_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=8}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right', 'right')
    check_eq(Editor_state.cursor1.line, 2, 'line')
    check_eq(Editor_state.cursor1.pos, 4, 'pos')
    end
    function test_click_moves_cursor()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.startpos for each line
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is empty to avoid perturbing future edits
    check_nil(Editor_state.selection1.line, 'selection:line')
    check_nil(Editor_state.selection1.pos, 'selection:pos')
    end
    function test_click_to_left_of_line()
    -- display a line with the cursor in the middle
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click to the left of the line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1)
    -- cursor moves to start of line
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_takes_margins_into_account()
    -- display two lines with cursor on one of them
    App.screen.init{width=100, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.left = 50 -- occupy only right side of screen
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_on_empty_line()
    -- display two lines with the first one empty
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click on the empty line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    -- selection remains empty
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_below_final_line_of_file()
    -- display one line
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    -- click below first line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+50, 1)
    -- cursor goes to bottom
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    -- selection remains empty
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_draw_text()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_draw_wrapping_text()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'defgh', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'de', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'fgh', 'screen:3')
    end
    function test_draw_word_wrapping_text()
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_click_on_wrapping_line()
    -- display two screen lines with cursor on one of them
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=20}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_click_on_wrapping_line_takes_margins_into_account()
    -- display two screen lines with cursor on one of them
    App.screen.init{width=100, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.left = 50 -- occupy only right side of screen
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=20}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
    end
    function test_draw_text_wrapping_within_word()
    -- arrange a screen line that needs to be split within a word
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abcd e fghijk', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abcd ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'e fgh', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ijk', 'screen:3')
    end
    function test_draw_wrapping_text_containing_non_ascii()
    -- draw a long line containing non-ASCII
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'madam I’m adam', 'xyz'} -- notice the non-ASCII apostrophe
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'mad', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'am I', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, '’m a', 'screen:3')
    end
    function test_click_past_end_of_screen_line()
    -- display a wrapping line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, "I'm ad", 'baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line (one more than final character shown)
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 13, 'cursor:pos')
    end
    function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()
    -- display a wrapping line from its second screen line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=8}
    Editor_state.screen_top1 = {line=1, pos=7}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, "I'm ad", 'baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line (one more than final character shown)
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 13, 'cursor:pos')
    end
    function test_click_past_end_of_wrapping_line()
    -- display a wrapping line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, "I'm ad", 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'am', 'baseline/screen:3')
    y = y + Editor_state.line_height
    -- click past the end of it
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of line
    check_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_wrapping_line_containing_non_ascii()
    -- display a wrapping line containing non-ASCII
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{'madam I’m adam'} -- notice the non-ASCII apostrophe
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'I’m ad', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'am', 'baseline/screen:3')
    y = y + Editor_state.line_height
    -- click past the end of it
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of line
    check_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_word_wrapping_line()
    -- display a long line wrapping at a word boundary on a screen of more realistic length
    App.screen.init{width=160, height=80}
    Editor_state = edit.initialize_test_state()
    -- 0 1 2
    -- 123456789012345678901
    Editor_state.lines = load_array{'the quick brown fox jumped over the lazy dog'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    -- click past the end of the screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line (one more than final character shown)
    check_eq(Editor_state.cursor1.pos, 21, 'cursor')
    end
    function test_select_text()
    -- display a line of text
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- select a letter
    App.fake_key_press('lshift')
    edit.run_after_keychord(Editor_state, 'S-right', 'right')
    App.fake_key_release('lshift')
    edit.key_release(Editor_state, 'lshift')
    -- selection persists even after shift is released
    check_eq(Editor_state.selection1.line, 1, 'selection:line')
    check_eq(Editor_state.selection1.pos, 1, 'selection:pos')
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    end
    function test_cursor_movement_without_shift_resets_selection()
    -- display a line of text with some part selected
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- press an arrow key without shift
    edit.run_after_keychord(Editor_state, 'right', 'right')
    -- no change to data, selection is reset
    check_nil(Editor_state.selection1.line, 'check')
    check_eq(Editor_state.lines[1].data, 'abc', 'data')
    end
    function test_edit_deletes_selection()
    -- display a line of text with some part selected
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- press a key
    edit.run_after_text_input(Editor_state, 'x')
    -- selected text is deleted and replaced with the key
    check_eq(Editor_state.lines[1].data, 'xbc', 'check')
    end
    function test_edit_with_shift_key_deletes_selection()
    -- display a line of text with some part selected
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- mimic precise keypresses for a capital letter
    App.fake_key_press('lshift')
    edit.keychord_press(Editor_state, 'd', 'd')
    edit.text_input(Editor_state, 'D')
    edit.key_release(Editor_state, 'd')
    App.fake_key_release('lshift')
    -- selected text is deleted and replaced with the key
    check_nil(Editor_state.selection1.line, 'check')
    check_eq(Editor_state.lines[1].data, 'Dbc', 'data')
    end
    function test_copy_does_not_reset_selection()
    -- display a line of text with a selection
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- copy selection
    edit.run_after_keychord(Editor_state, 'C-c', 'c')
    check_eq(App.clipboard, 'a', 'clipboard')
    -- selection is reset since shift key is not pressed
    check(Editor_state.selection1.line, 'check')
    end
    function test_cut()
    -- display a line of text with some part selected
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- press a key
    edit.run_after_keychord(Editor_state, 'C-x', 'x')
    check_eq(App.clipboard, 'a', 'clipboard')
    -- selected text is deleted
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    end
    function test_paste_replaces_selection()
    -- display a line of text with a selection
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.selection1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- set clipboard
    App.clipboard = 'xyz'
    -- paste selection
    edit.run_after_keychord(Editor_state, 'C-v', 'v')
    -- selection is reset since shift key is not pressed
    -- selection includes the newline, so it's also deleted
    check_eq(Editor_state.lines[1].data, 'xyzdef', 'check')
    end
    function test_deleting_selection_may_scroll()
    -- display lines 2/3/4
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=2}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- set up a selection starting above the currently displayed page
    Editor_state.selection1 = {line=1, pos=2}
    -- delete selection
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    -- page scrolls up
    check_eq(Editor_state.screen_top1.line, 1, 'check')
    check_eq(Editor_state.lines[1].data, 'ahi', 'data')
    end
    function test_edit_wrapping_text()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=4}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    edit.run_after_text_input(Editor_state, 'g')
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'de', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'fg', 'screen:3')
    end
    function test_insert_newline()
    -- display a few lines
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- hitting the enter key splits the line
    edit.run_after_keychord(Editor_state, 'return', 'return')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'a', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'bc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function test_insert_newline_at_start_of_line()
    -- display a line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- hitting the enter key splits the line
    edit.run_after_keychord(Editor_state, 'return', 'return')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    check_eq(Editor_state.lines[1].data, '', 'data:1')
    check_eq(Editor_state.lines[2].data, 'abc', 'data:2')
    end
    function test_insert_from_clipboard()
    -- display a few lines
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- paste some text including a newline, check that new line is created
    App.clipboard = 'xy\nz'
    edit.run_after_keychord(Editor_state, 'C-v', 'v')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'axy', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'zbc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function test_select_text_using_mouse()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.startpos for each line
    -- press and hold on first location
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- drag and release somewhere else
    edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)
    check_eq(Editor_state.selection1.line, 1, 'selection:line')
    check_eq(Editor_state.selection1.pos, 2, 'selection:pos')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    end
    function test_select_text_using_mouse_starting_above_text()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.startpos for each line
    -- press mouse above first line of text
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
    check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')
    check_eq(Editor_state.selection1.line, 1, 'selection:line')
    check_eq(Editor_state.selection1.pos, 1, 'selection:pos')
    end
    function test_select_text_using_mouse_starting_above_text_wrapping_line()
    -- first screen line starts in the middle of a line
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'defgh', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=5}
    Editor_state.screen_top1 = {line=2, pos=3}
    -- press mouse above first line of text
    edit.draw(Editor_state)
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
    -- selection is at screen top
    check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')
    check_eq(Editor_state.selection1.line, 2, 'selection:line')
    check_eq(Editor_state.selection1.pos, 3, 'selection:pos')
    end
    function test_select_text_using_mouse_starting_below_text()
    -- I'd like to test what happens when a mouse click is below some page of
    -- text, potentially even in the middle of a line.
    -- However, it's brittle to set up a text line boundary just right.
    -- So I'm going to just check things below the bottom of the final line of
    -- text when it's in the middle of the screen.
    -- final screen line ends in the middle of screen
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abcde'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'ab', 'baseline:screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'cde', 'baseline:screen:2')
    -- press mouse above first line of text
    edit.run_after_mouse_press(Editor_state, 5,App.screen.height-5, 1)
    -- selection is past bottom-most text in screen
    check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')
    check_eq(Editor_state.selection1.line, 1, 'selection:line')
    check_eq(Editor_state.selection1.pos, 6, 'selection:pos')
    end
    function test_select_text_using_mouse_and_shift()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.startpos for each line
    -- click on first location
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- hold down shift and click somewhere else
    App.fake_key_press('lshift')
    edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)
    edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)
    App.fake_key_release('lshift')
    check_eq(Editor_state.selection1.line, 1, 'selection:line')
    check_eq(Editor_state.selection1.pos, 2, 'selection:pos')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    end
    function test_select_text_repeatedly_using_mouse_and_shift()
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
    Text.redraw_all(Editor_state)
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.startpos for each line
    -- click on first location
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- hold down shift and click on a second location
    App.fake_key_press('lshift')
    edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)
    edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)
    -- hold down shift and click at a third location
    App.fake_key_press('lshift')
    edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height+5, 1)
    App.fake_key_release('lshift')
    -- selection is between first and third location. forget the second location, not the first.
    check_eq(Editor_state.selection1.line, 1, 'selection:line')
    check_eq(Editor_state.selection1.pos, 2, 'selection:pos')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    end
    function test_select_all_text()
    -- display a single line of text
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- select all
    App.fake_key_press('lctrl')
    edit.run_after_keychord(Editor_state, 'C-a', 'a')
    App.fake_key_release('lctrl')
    edit.key_release(Editor_state, 'lctrl')
    -- selection
    check_eq(Editor_state.selection1.line, 1, 'selection:line')
    check_eq(Editor_state.selection1.pos, 1, 'selection:pos')
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 8, 'cursor:pos')
    end
    function test_cut_without_selection()
    -- display a few lines
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.selection1 = {}
    edit.draw(Editor_state)
    -- try to cut without selecting text
    edit.run_after_keychord(Editor_state, 'C-x', 'x')
    -- no crash
    check_nil(Editor_state.selection1.line, 'check')
    end
    function test_pagedown()
    App.screen.init{width=120, height=45}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- initially the first two lines are displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    -- after pagedown the bottom line becomes the top
    edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'cursor')
    y = Editor_state.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    end
    function test_pagedown_skips_drawings()
    -- some lines of text with a drawing intermixed
    local drawing_width = 50
    App.screen.init{width=Editor_state.left+drawing_width, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', -- height 15
    '```lines', '```', -- height 25
    'def', -- height 15
    'ghi'} -- height 15
    Text.redraw_all(Editor_state)
    check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines')
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    local drawing_height = Drawing_padding_height + drawing_width/2 -- default
    -- initially the screen displays the first line and the drawing
    -- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    -- after pagedown the screen draws the drawing up top
    -- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80px
    edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor')
    y = Editor_state.top + drawing_height
    App.screen.check(y, 'def', 'screen:1')
    end
    function test_pagedown_can_start_from_middle_of_long_wrapping_line()
    -- draw a few lines starting from a very long wrapping line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu vwx yza bcd efg hij', 'XYZ'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc ', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def ', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'baseline/screen:3')
    -- after pagedown we scroll down the very long wrapping line
    edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 9, 'screen_top:pos')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl ', 'screen:2')
    y = y + Editor_state.line_height
    if Version == '12.0' then
    -- HACK: Maybe v12.0 uses a different font? Strange that it only causes
    -- issues in a couple of places.
    -- We'll need to rethink our tests if issues like this start to multiply.
    App.screen.check(y, 'mno ', 'screen:3')
    else
    App.screen.check(y, 'mn', 'screen:3')
    end
    end
    function test_pagedown_never_moves_up()
    -- draw the final screen line of a wrapping line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=9}
    Editor_state.screen_top1 = {line=1, pos=9}
    edit.draw(Editor_state)
    -- pagedown makes no change
    edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 9, 'screen_top:pos')
    end
    function test_down_arrow_moves_cursor()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- initially the first three lines are displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the down arrow, the cursor moves down by 1 line
    edit.run_after_keychord(Editor_state, 'down', 'down')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'cursor')
    -- the screen is unchanged
    y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_down_arrow_skips_drawing()
    -- some lines of text with a drawing intermixed
    local drawing_width = 50
    App.screen.init{width=Editor_state.left+drawing_width, height=100}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', -- height 15
    '```lines', '```', -- height 25
    'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    local drawing_height = Drawing_padding_height + drawing_width/2 -- default
    y = y + drawing_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    check(Editor_state.cursor_x, 'baseline/cursor_x')
    -- after hitting the down arrow the cursor moves down by 2 lines, skipping the drawing
    edit.run_after_keychord(Editor_state, 'down', 'down')
    check_eq(Editor_state.cursor1.line, 3, 'cursor')
    end
    function test_down_arrow_scrolls_down_by_one_line()
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    edit.run_after_keychord(Editor_state, 'down', 'down')
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 4, 'cursor')
    y = Editor_state.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line()
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the down arrow the screen scrolls down by one line
    edit.run_after_keychord(Editor_state, 'down', 'down')
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghijkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij', 'baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    edit.run_after_keychord(Editor_state, 'down', 'down')
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:3')
    end
    function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghijkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij', 'baseline/screen:3')
    -- after hitting pagedown the screen scrolls down to start of a long line
    edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
    check_eq(Editor_state.screen_top1.line, 3, 'baseline2/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'baseline2/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'baseline2/cursor:pos')
    -- after hitting down arrow the screen doesn't scroll down further, and certainly doesn't scroll up
    edit.run_after_keychord(Editor_state, 'down', 'down')
    check_eq(Editor_state.screen_top1.line, 3, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'ghij', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'screen:3')
    end
    function test_up_arrow_moves_cursor()
    -- display the first 3 lines with the cursor on the bottom line
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the up arrow the cursor moves up by 1 line
    edit.run_after_keychord(Editor_state, 'up', 'up')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'cursor')
    -- the screen is unchanged
    y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_up_arrow_skips_drawing()
    -- some lines of text with a drawing intermixed
    local drawing_width = 50
    App.screen.init{width=Editor_state.left+drawing_width, height=100}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', -- height 15
    '```lines', '```', -- height 25
    'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    local drawing_height = Drawing_padding_height + drawing_width/2 -- default
    y = y + drawing_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    check(Editor_state.cursor_x, 'baseline/cursor_x')
    -- after hitting the up arrow the cursor moves up by 2 lines, skipping the drawing
    edit.run_after_keychord(Editor_state, 'up', 'up')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    end
    function test_up_arrow_scrolls_up_by_one_line()
    -- display the lines 2/3/4 with the cursor on line 2
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    edit.run_after_keychord(Editor_state, 'up', 'up')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_up_arrow_scrolls_up_by_one_line_skipping_drawing()
    -- display lines 3/4/5 with a drawing just off screen at line 2
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', '```lines', '```', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=1}
    Editor_state.screen_top1 = {line=3, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to previous text line
    edit.run_after_keychord(Editor_state, 'up', 'up')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    end
    function test_up_arrow_scrolls_up_by_one_screen_line()
    -- display lines starting from second screen line of a line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=6}
    Editor_state.screen_top1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting the up arrow the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'up', 'up')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'screen:3')
    check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    end
    function test_up_arrow_scrolls_up_to_final_screen_line()
    -- display lines starting just after a long line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to final screen line of previous line
    edit.run_after_keychord(Editor_state, 'up', 'up')
    y = Editor_state.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 5, 'screen_top:pos')
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
    end
    function test_up_arrow_scrolls_up_to_empty_line()
    -- display a screenful of text with an empty line just above it outside the screen
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'', 'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    edit.run_after_keychord(Editor_state, 'up', 'up')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    y = Editor_state.top
    -- empty first line
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:3')
    end
    function test_pageup()
    App.screen.init{width=120, height=45}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    -- initially the last two lines are displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    -- after pageup the cursor goes to first line
    edit.run_after_keychord(Editor_state, 'pageup', 'pageup')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    end
    function test_pageup_scrolls_up_by_screen_line()
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'ghi', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    edit.run_after_keychord(Editor_state, 'pageup', 'pageup')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:3')
    end
    function test_pageup_scrolls_up_from_middle_screen_line()
    -- display a few lines starting from the middle of a line (Editor_state.cursor1.pos > 1)
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=5}
    Editor_state.screen_top1 = {line=2, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    edit.run_after_keychord(Editor_state, 'pageup', 'pageup')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'abc ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:3')
    end
    function test_enter_on_bottom_line_scrolls_down()
    -- display a few lines with cursor on bottom line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after hitting the enter key the screen scrolls down
    edit.run_after_keychord(Editor_state, 'return', 'return')
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 4, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'g', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'hi', 'screen:3')
    end
    function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    -- display just the bottom line on screen
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=4, pos=2}
    Editor_state.screen_top1 = {line=4, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    -- after hitting the enter key the screen does not scroll down
    edit.run_after_keychord(Editor_state, 'return', 'return')
    check_eq(Editor_state.screen_top1.line, 4, 'screen_top')
    check_eq(Editor_state.cursor1.line, 5, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'j', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    end
    function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    -- display just an empty bottom line on screen
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', ''}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    -- after hitting the inserting_text key the screen does not scroll down
    edit.run_after_text_input(Editor_state, 'a')
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    local y = Editor_state.top
    App.screen.check(y, 'a', 'screen:1')
    end
    function test_typing_on_bottom_line_scrolls_down()
    -- display a few lines with cursor on bottom line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=4}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:3')
    -- after typing something the line wraps and the screen scrolls down
    edit.run_after_text_input(Editor_state, 'j')
    edit.run_after_text_input(Editor_state, 'k')
    edit.run_after_text_input(Editor_state, 'l')
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 7, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:3')
    end
    [7.3600]
    [7.64608]
    --
    -- I'm checking the precise state of the screen in this file, an inherently
    -- brittle approach that depends on details of the font and text shaping
    -- algorithms used by a particular release of LÖVE.
    --
    -- (This brittleness is one reason lines2 and its forks have no tests.)
    --
    -- To manage the brittleness, there'll be one version of this file for each
    -- distinct LÖVE version that introduces font changes.
  • replacement in source_text_tests.lua at line 12
    [7.64609][7.64609:64663](),[7.64722][7.64722:65030](),[7.65065][7.65065:65193](),[7.65193][7.15667:15717](),[7.15717][7.65290:65325](),[7.65290][7.65290:65325](),[7.65325][7.15718:15768](),[7.15768][7.65422:65499](),[7.65422][7.65422:65499](),[7.65499][7.3059:3115](),[7.3115][7.65547:65570](),[7.65547][7.65547:65570](),[7.65570][7.15769:15811](),[7.15811][7.65659:65694](),[7.65659][7.65659:65694](),[7.65694][7.15812:15853](),[7.15853][7.65782:65817](),[7.65782][7.65782:65817](),[7.65817][7.15854:16131]()
    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')
    [7.64609]
    [7.66320]
    Version, Major_version = App.love_version()
    if Major_version == 11 then
    load_file_from_source_or_save_directory('source_text_tests_love11.lua')
    elseif Major_version == 12 then
    -- not released/stable yet
    load_file_from_source_or_save_directory('source_text_tests_love12.lua')
  • edit in source_text_tests.lua at line 19
    [7.66324][7.66324:66382](),[7.66444][7.66444:66760](),[7.66795][7.66795:66932](),[7.66932][7.16132:16182](),[7.16182][7.67032:67067](),[7.67032][7.67032:67067](),[7.67067][7.16183:16233](),[7.16233][7.67167:67202](),[7.67167][7.67167:67202](),[7.67202][7.16234:16332](),[7.16332][7.67350:67421](),[7.67350][7.67350:67421](),[7.67421][7.1905:1963](),[7.1963][7.16333:16502](),[7.3175][7.16333:16502](),[7.67470][7.16333:16502](),[7.16502][7.67789:67812](),[7.67789][7.67789:67812](),[7.67812][7.16503:16544](),[7.16544][7.67903:67938](),[7.67903][7.67903:67938](),[7.67938][7.16545:16587](),[7.16587][7.68030:68065](),[7.68030][7.68030:68065](),[7.68065][7.16588:16629](),[7.16629][7.68156:68209](),[7.68156][7.68156:68209](),[7.68262][7.68262:68570](),[7.68605][7.68605:68733](),[7.68733][7.16630:16680](),[7.16680][7.68824:68859](),[7.68824][7.68824:68859](),[7.68859][7.16681:16731](),[7.16731][7.68950:69017](),[7.68950][7.68950:69017](),[7.69017][7.3176:3232](),[7.3232][7.69065:69088](),[7.69065][7.69065:69088](),[7.69088][7.16732:16774](),[7.16774][7.69171:69206](),[7.69171][7.69171:69206](),[7.69206][7.16775:16816](),[7.16816][7.69288:69323](),[7.69288][7.69288:69323](),[7.69323][7.16817:17094](),[7.17094][7.69796:69850](),[7.69796][7.69796:69850](),[7.69904][7.69904:70220](),[7.70255][7.70255:70392](),[7.70392][7.17095:17145](),[7.17145][7.70484:70519](),[7.70484][7.70484:70519](),[7.70519][7.17146:17196](),[7.17196][7.70611:70646](),[7.70611][7.70611:70646](),[7.70646][7.17197:17295](),[7.17295][7.70786:70845](),[7.70786][7.70786:70845](),[7.70845][7.3233:3287](),[7.3287][7.17296:17465](),[7.70892][7.17296:17465](),[7.17465][7.71187:71210](),[7.71187][7.71187:71210](),[7.71210][7.17466:17507](),[7.17507][7.71293:71328](),[7.71293][7.71293:71328](),[7.71328][7.17508:17550](),[7.17550][7.71412:71447](),[7.71412][7.71412:71447](),[7.71447][7.17551:17592](),[7.17592][7.71530:71646](),[7.71530][7.71530:71646](),[7.71716][7.71716:71991](),[7.72026][7.72026:72081](),[7.72081][7.17593:17653](),[7.17653][7.72199:72234](),[7.72199][7.72199:72234](),[7.72234][7.17654:17714](),[7.17714][7.72352:72387](),[7.72352][7.72352:72387](),[7.72387][7.17715:17766](),[7.17766][7.72496:72557](),[7.72496][7.72496:72557](),[7.72557][7.4932:5073](),[7.5073][7.17767:17822](),[7.17822][7.72808:72831](),[7.72808][7.72808:72831](),[7.72831][7.17823:17883](),[7.17883][7.72949:72984](),[7.72949][7.72949:72984](),[7.72984][7.17884:17944](),[7.17944][7.73102:73137](),[7.73102][7.73102:73137](),[7.73137][7.17945:17996](),[7.17996][7.73246:73329](),[7.73246][7.73246:73329](),[7.73329][7.326:441](),[7.441][7.73442:73466](),[7.13324][7.73442:73466](),[7.73442][7.73442:73466](),[7.73466][7.17997:18053](),[7.18053][7.442:497](),[7.497][7.73693:73738](),[7.18108][7.73693:73738](),[7.73693][7.73693:73738](),[7.73783][7.73783:74104](),[7.74139][7.74139:74194](),[7.74194][7.18109:18159](),[7.18159][7.74277:74312](),[7.74277][7.74277:74312](),[7.74312][7.18160:18210](),[7.18210][7.74395:74430](),[7.74395][7.74395:74430](),[7.74430][7.18211:18261](),[7.18261][7.74513:74576](),[7.74513][7.74513:74576](),[7.74576][7.3288:3354](),[7.3354][7.18262:18372](),[7.74629][7.18262:18372](),[7.18372][7.74805:74828](),[7.74805][7.74805:74828](),[7.74828][7.18373:18417](),[7.18417][7.74905:74940](),[7.74905][7.74905:74940](),[7.74940][7.18418:18459](),[7.18459][7.75014:75049](),[7.75014][7.75014:75049](),[7.75049][7.18460:18501](),[7.18501][7.75123:75180](),[7.75123][7.75123:75180](),[7.75237][7.75237:75586](),[7.75621][7.75621:75676](),[7.75676][7.18502:18552](),[7.18552][7.75771:75806](),[7.75771][7.75771:75806](),[7.75806][7.18553:18603](),[7.18603][7.75901:75971](),[7.75901][7.75901:75971](),[7.75971][7.3355:3421](),[7.3421][7.76024:76047](),[7.76024][7.76024:76047](),[7.76047][7.18604:18646](),[7.18646][7.76134:76169](),[7.76134][7.76134:76169](),[7.76169][7.18647:18687](),[7.18687][7.76254:76289](),[7.76254][7.76254:76289](),[7.76289][7.18688:18965](),[7.18965][7.76782:76832](),[7.76782][7.76782:76832](),[7.76882][7.76882:77198](),[7.77198][7.3422:3488](),[7.3488][7.18966:19024](),[7.77251][7.18966:19024](),[7.19024][7.77341:77346](),[7.77341][7.77341:77346](),[7.77346][7.13325:13514](),[7.13560][7.13560:14004](),[7.14004][7.3489:3555](),[7.3555][7.19025:19078](),[7.14057][7.19025:19078](),[7.19078][7.14144:14188](),[7.14144][7.14144:14188](),[7.14188][7.19079:19189](),[7.19189][7.14366:14392](),[7.14366][7.14366:14392](),[7.14392][7.19190:19245](),[7.19245][7.14481:14535](),[7.14481][7.14481:14535](),[7.14589][7.14589:14997](),[7.14997][7.3556:3622](),[7.3622][7.19246:19299](),[7.15050][7.19246:19299](),[7.19299][7.15145:15185](),[7.15145][7.15145:15185](),[7.15185][7.19300:19410](),[7.19410][7.15379:15405](),[7.15379][7.15379:15405](),[7.15405][7.19411:19466](),[7.19466][7.15502:15553](),[7.15502][7.15502:15553](),[7.15604][7.15604:16057](),[7.16057][7.3623:3689](),[7.3689][7.19467:19579](),[7.16110][7.19467:19579](),[7.19579][7.16300:16342](),[7.16300][7.16300:16342](),[7.16342][7.19580:19690](),[7.19690][7.16530:16556](),[7.16530][7.16530:16556](),[7.16556][7.19691:19746](),[7.19746][7.16650:16696](),[7.16650][7.16650:16696](),[7.16742][7.16742:17147](),[7.17147][7.3690:3756](),[7.3756][7.19747:19857](),[7.17200][7.19747:19857](),[7.19857][7.17382:17424](),[7.17382][7.17382:17424](),[7.17424][7.19858:19968](),[7.19968][7.17606:17632](),[7.17606][7.17606:17632](),[7.17632][7.19969:20024](),[7.20024][7.17723:17771](),[7.17723][7.17723:17771](),[7.17819][7.17819:18231](),[7.18231][7.3757:3823](),[7.3823][7.20025:20135](),[7.18284][7.20025:20135](),[7.20135][7.18466:18508](),[7.18466][7.18466:18508](),[7.18508][7.20136:20246](),[7.20246][7.18690:18716](),[7.18690][7.18690:18716](),[7.18716][7.20247:20302](),[7.20302][7.18807:18812](),[7.18807][7.18807:18812](),[7.18812][7.77346:77379](),[7.77346][7.77346:77379](),[7.77417][7.77417:77676](),[7.77711][7.77711:77761](),[7.77761][7.5074:5121](),[7.5121][7.20303:20567](),[7.19001][7.77987:78016](),[7.20567][7.77987:78016](),[7.77987][7.77987:78016](),[7.78016][7.20568:20618](),[7.20618][7.78092:78127](),[7.78092][7.78092:78127](),[7.78127][7.20619:20670](),[7.20670][7.78204:78239](),[7.78204][7.78204:78239](),[7.78239][7.20671:20721](),[7.20721][7.78315:78325](),[7.78315][7.78315:78325](),[7.78325][7.3824:3876](),[7.3876][7.20722:20950](),[7.78372][7.20722:20950](),[7.19172][7.78534:78557](),[7.20950][7.78534:78557](),[7.78534][7.78534:78557](),[7.78557][7.20951:20992](),[7.20992][7.78624:78659](),[7.78624][7.78624:78659](),[7.78659][7.20993:21034](),[7.21034][7.78726:78761](),[7.78726][7.78726:78761](),[7.78761][7.21035:21076](),[7.21076][7.78828:78866](),[7.78828][7.78828:78866](),[7.78904][7.78904:79164](),[7.79199][7.79199:79223](),[7.79223][7.3877:3943](),[7.3943][7.21077:21341](),[7.79276][7.21077:21341](),[7.19361][7.79456:79485](),[7.21341][7.79456:79485](),[7.79456][7.79456:79485](),[7.79485][7.21342:21392](),[7.21392][7.79561:79596](),[7.79561][7.79561:79596](),[7.79596][7.21393:21443](),[7.21443][7.79672:79707](),[7.79672][7.79672:79707](),[7.79707][7.21444:21494](),[7.21494][7.79783:79845](),[7.79783][7.79783:79845](),[7.79845][7.3944:3996](),[7.3996][7.21495:21853](),[7.79892][7.21495:21853](),[7.19714][7.80054:80077](),[7.21853][7.80054:80077](),[7.80054][7.80054:80077](),[7.80077][7.21854:21895](),[7.21895][7.80144:80179](),[7.80144][7.80144:80179](),[7.80179][7.21896:21938](),[7.21938][7.80247:80282](),[7.80247][7.80247:80282](),[7.80282][7.21939:21980](),[7.21980][7.80349:80354](),[7.80349][7.80349:80354](),[7.80354][7.19715:19755](),[7.19800][7.19800:20140](),[7.20175][7.20175:20227](),[7.20227][7.5122:5169](),[7.5169][7.21981:22103](),[7.22103][7.20461:20471](),[7.20461][7.20461:20471](),[7.20471][7.3997:4101](),[7.4101][7.20565:20592](),[7.20565][7.20565:20592](),[7.20592][7.22104:22206](),[7.22206][7.20760:20765](),[7.20760][7.20760:20765](),[7.20765][7.80354:80377](),[7.80354][7.80354:80377](),[7.80405][7.80405:80491](),[7.80491][7.113:231](),[7.231][7.80553:80671](),[7.3881][7.80553:80671](),[7.80553][7.80553:80671](),[7.80706][7.80706:80757](),[7.80757][7.4102:4154](),[7.4154][7.5170:5217](),[7.80804][7.5170:5217](),[7.5217][7.4155:4215](),[7.4215][7.22207:22321](),[7.80900][7.22207:22321](),[7.22321][7.81046:81184](),[7.81046][7.81046:81184](),[7.81184][7.4216:4268](),[7.4268][7.5218:5266](),[7.81231][7.5218:5266](),[7.5266][7.4269:4385](),[7.4385][7.22322:22380](),[7.81376][7.22322:22380](),[7.22380][7.232:288](),[7.288][7.81522:81558](),[7.22436][7.81522:81558](),[7.81522][7.81522:81558](),[7.81594][7.81594:81680](),[7.81680][7.289:367](),[7.367][7.81725:81757](),[7.81725][7.81725:81757](),[7.81757][7.368:409](),[7.409][7.81798:81843](),[7.81798][7.81798:81843](),[7.81878][7.81878:81929](),[7.81929][7.4386:4438](),[7.4438][7.5267:5314](),[7.81976][7.5267:5314](),[7.5314][7.82022:82058](),[7.82022][7.82022:82058](),[7.82058][7.4439:4491](),[7.4491][7.22437:22495](),[7.82104][7.22437:22495](),[7.22495][7.410:466](),[7.466][7.82266:82299](),[7.22551][7.82266:82299](),[7.82266][7.82266:82299](),[7.82332][7.82332:82418](),[7.82418][7.467:559](),[7.559][7.82459:82491](),[7.82459][7.82459:82491](),[7.82491][7.560:601](),[7.601][7.82532:82577](),[7.82532][7.82532:82577](),[7.82612][7.82612:82663](),[7.82663][7.4492:4544](),[7.4544][7.5315:5362](),[7.82710][7.5315:5362](),[7.5362][7.4545:4605](),[7.4605][7.82806:82824](),[7.82806][7.82806:82824](),[7.82824][7.22552:22610](),[7.22610][7.602:658](),[7.658][7.82980:83021](),[7.22666][7.82980:83021](),[7.82980][7.82980:83021](),[7.83062][7.83062:83148](),[7.83148][7.659:734](),[7.734][7.83193:83311](),[7.83193][7.83193:83311](),[7.83346][7.83346:83405](),[7.83405][7.4606:4658](),[7.4658][7.5363:5410](),[7.83452][7.5363:5410](),[7.5410][7.4659:4711](),[7.4711][7.83544:83562](),[7.83544][7.83544:83562](),[7.83562][7.22667:22725](),[7.22725][7.735:791](),[7.791][7.7:995](),[7.791][7.83734:83738](),[7.995][7.83734:83738](),[7.22781][7.83734:83738](),[7.83734][7.83734:83738]()
    function test_right_arrow_scrolls_down_in_wrapped_line()
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.screen_top1 = {line=1, pos=1}
    -- cursor is at bottom right of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the right arrow the screen scrolls down by one line
    edit.run_after_keychord(Editor_state, 'right', 'right')
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_home_scrolls_up_in_wrapped_line()
    -- display lines starting from second screen line of a line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.screen_top1 = {line=3, pos=5}
    -- cursor is at top of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting home the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'home', 'home')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'screen:3')
    check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    end
    function test_end_scrolls_down_in_wrapped_line()
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.screen_top1 = {line=1, pos=1}
    -- cursor is at bottom right of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting end the screen scrolls down by one line
    edit.run_after_keychord(Editor_state, 'end', 'end')
    check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 8, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'def', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_position_cursor_on_recently_edited_wrapping_line()
    -- draw a line wrapping over 2 screen lines
    App.screen.init{width=100, height=200}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr ', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=25}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl mno pqr ', 'baseline1/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'baseline1/screen:3')
    -- add to the line until it's wrapping over 3 screen lines
    edit.run_after_text_input(Editor_state, 's')
    edit.run_after_text_input(Editor_state, 't')
    edit.run_after_text_input(Editor_state, 'u')
    check_eq(Editor_state.cursor1.pos, 28, 'cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'abc def ghi ', 'baseline2/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl mno pqr ', 'baseline2/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'stu', 'baseline2/screen:3')
    -- try to move the cursor earlier in the third screen line by clicking the mouse
    edit.run_after_mouse_release(Editor_state, Editor_state.left+2,Editor_state.top+Editor_state.line_height*2+5, 1)
    -- cursor should move
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 25, 'cursor:pos')
    end
    function test_backspace_can_scroll_up()
    -- display the lines 2/3/4 with the cursor on line 2
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting backspace the screen scrolls up by one line
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    y = Editor_state.top
    App.screen.check(y, 'abcdef', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'screen:3')
    end
    function test_backspace_can_scroll_up_screen_line()
    -- display lines starting from second screen line of a line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=5}
    Editor_state.screen_top1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'baseline/screen:2')
    -- after hitting backspace the screen scrolls up by one screen line
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    y = Editor_state.top
    App.screen.check(y, 'ghij', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'screen:3')
    check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')
    check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
    check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    end
    function test_backspace_past_line_boundary()
    -- position cursor at start of a (non-first) line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    -- backspace joins with previous line
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.lines[1].data, 'abcdef', 'check')
    end
    -- some tests for operating over selections created using Shift- chords
    -- we're just testing delete_selection, and it works the same for all keys
    function test_backspace_over_selection()
    -- select just one character within a line with cursor before selection
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    -- backspace deletes the selected character, even though it's after the cursor
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    -- cursor (remains) at start of selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_over_selection_reverse()
    -- select just one character within a line with cursor after selection
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    Editor_state.selection1 = {line=1, pos=1}
    -- backspace deletes the selected character
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.lines[1].data, 'bc', 'data')
    -- cursor moves to start of selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_over_multiple_lines()
    -- select just one character within a line with cursor after selection
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    Editor_state.selection1 = {line=4, pos=2}
    -- backspace deletes the region and joins the remaining portions of lines on either side
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.lines[1].data, 'akl', 'data:1')
    check_eq(Editor_state.lines[2].data, 'mno', 'data:2')
    -- cursor remains at start of selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_to_end_of_line()
    -- select region from cursor to end of line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    Editor_state.selection1 = {line=1, pos=4}
    -- backspace deletes rest of line without joining to any other line
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.lines[1].data, 'a', 'data:1')
    check_eq(Editor_state.lines[2].data, 'def', 'data:2')
    -- cursor remains at start of selection
    check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_backspace_to_start_of_line()
    -- select region from cursor to start of line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.selection1 = {line=2, pos=3}
    -- backspace deletes beginning of line without joining to any other line
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.lines[1].data, 'abc', 'data:1')
    check_eq(Editor_state.lines[2].data, 'f', 'data:2')
    -- cursor remains at start of selection
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
    -- selection is cleared
    check_nil(Editor_state.selection1.line, 'selection')
    end
    function test_undo_insert_text()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=4}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- insert a character
    edit.draw(Editor_state)
    edit.run_after_text_input(Editor_state, 'g')
    check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'baseline/cursor:pos')
    check_nil(Editor_state.selection1.line, 'baseline/selection:line')
    check_nil(Editor_state.selection1.pos, 'baseline/selection:pos')
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'defg', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'baseline/screen:3')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z', 'z')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection:line')
    check_nil(Editor_state.selection1.pos, 'selection:pos')
    y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'screen:3')
    end
    function test_undo_delete_text()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'defg', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=5}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- delete a character
    edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
    check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'baseline/cursor:pos')
    check_nil(Editor_state.selection1.line, 'baseline/selection:line')
    check_nil(Editor_state.selection1.pos, 'baseline/selection:pos')
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'baseline/screen:3')
    -- undo
    --? -- after undo, the backspaced key is selected
    edit.run_after_keychord(Editor_state, 'C-z', 'z')
    check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
    check_nil(Editor_state.selection1.line, 'selection:line')
    check_nil(Editor_state.selection1.pos, 'selection:pos')
    --? check_eq(Editor_state.selection1.line, 2, 'selection:line')
    --? check_eq(Editor_state.selection1.pos, 4, 'selection:pos')
    y = Editor_state.top
    App.screen.check(y, 'abc', 'screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'defg', 'screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'screen:3')
    end
    function test_undo_restores_selection()
    -- display a line of text with some part selected
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- delete selected text
    edit.run_after_text_input(Editor_state, 'x')
    check_eq(Editor_state.lines[1].data, 'xbc', 'baseline')
    check_nil(Editor_state.selection1.line, 'baseline:selection')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z', 'z')
    edit.run_after_keychord(Editor_state, 'C-z', 'z')
    -- selection is restored
    check_eq(Editor_state.selection1.line, 1, 'line')
    check_eq(Editor_state.selection1.pos, 2, 'pos')
    end
    function test_search()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'```lines', '```', 'def', 'ghi', '’deg'} -- contains unicode quote in final line
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- search for a string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_text_input(Editor_state, 'd')
    edit.run_after_keychord(Editor_state, 'return', 'return')
    check_eq(Editor_state.cursor1.line, 2, '1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, '1/cursor:pos')
    -- reset cursor
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- search for second occurrence
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_text_input(Editor_state, 'de')
    edit.run_after_keychord(Editor_state, 'down', 'down')
    edit.run_after_keychord(Editor_state, 'return', 'return')
    check_eq(Editor_state.cursor1.line, 4, '2/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, '2/cursor:pos')
    end
    function test_search_upwards()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'’abc', 'abd'} -- contains unicode quote
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- search for a string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_text_input(Editor_state, 'a')
    -- search for previous occurrence
    edit.run_after_keychord(Editor_state, 'up', 'up')
    check_eq(Editor_state.cursor1.line, 1, '2/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, '2/cursor:pos')
    end
    function test_search_wrap()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'’abc', 'def'} -- contains unicode quote in first line
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- search for a string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_text_input(Editor_state, 'a')
    edit.run_after_keychord(Editor_state, 'return', 'return')
    -- cursor wraps
    check_eq(Editor_state.cursor1.line, 1, '1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, '1/cursor:pos')
    end
    function test_search_wrap_upwards()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc ’abd'} -- contains unicode quote
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- search upwards for a string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_text_input(Editor_state, 'a')
    edit.run_after_keychord(Editor_state, 'up', 'up')
    -- cursor wraps
    check_eq(Editor_state.cursor1.line, 1, '1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 6, '1/cursor:pos')
    end
    function test_search_downwards_from_end_of_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- search for empty string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_keychord(Editor_state, 'down', 'down')
    -- no crash
    end
    function test_search_downwards_from_final_pos_of_line()
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    Editor_state.screen_top1 = {line=1, pos=1}
    edit.draw(Editor_state)
    -- search for empty string
    edit.run_after_keychord(Editor_state, 'C-f', 'f')
    edit.run_after_keychord(Editor_state, 'down', 'down')
    -- no crash
    end
  • edit in source_text.lua at line 71
    [7.1470]
    [116.238]
    --? -- render screen line
    --? App.color(Text_color)
    --? App.screen.print(screen_line, State.left,y)
  • replacement in source_text.lua at line 122
    [7.2428][7.2428:2472](),[7.2472][7.729:778](),[7.778][7.2511:2815](),[7.2511][7.2511:2815](),[7.2815][7.779:862](),[7.862][7.8:200](),[7.2886][7.8:200](),[7.200][7.2956:3180](),[7.2956][7.2956:3180](),[7.3180][7.863:910](),[7.910][7.3217:3264](),[7.3217][7.3217:3264]()
    for frag in line.data:gmatch('%S*%s*') do
    local frag_width = State.font:getWidth(frag)
    --? print('-- frag:', frag, pos, x, frag_width, State.width)
    while x + frag_width > State.width do
    --? print('frag:', frag, pos, x, frag_width, State.width)
    if x < 0.8 * State.width then
    -- long word; chop it at some letter
    -- We're not going to reimplement TeX here.
    local bpos = Text.nearest_pos_less_than(State.font, frag, State.width - x)
    if x == 0 and bpos == 0 then
    assert(false, ("Infinite loop while line-wrapping. Editor is %dpx wide; window is %dpx wide"):format(State.width, App.screen.width))
    end
    pos = pos + bpos
    local boffset = Text.offset(frag, bpos+1) -- byte _after_ bpos
    frag = string.sub(frag, boffset)
    --? if bpos > 0 then
    --? print('after chop:', frag)
    --? end
    frag_width = State.font:getWidth(frag)
    end
    --? print('screen line:', pos)
    [7.2428]
    [7.92047]
    for pos,char in utf8chars(line.data) do
    local w = State.font:getWidth(char)
    if Text.should_word_wrap(State, line.data, pos, char, x)
    or x+w > State.width -- truncate within a word
    then
  • replacement in source_text.lua at line 128
    [7.92108][7.3265:3297]()
    x = 0 -- new screen line
    [7.92108]
    [7.92108]
    x = 0
  • replacement in source_text.lua at line 130
    [7.92116][7.92116:92139](),[7.92139][7.3298:3329]()
    x = x + frag_width
    pos = pos + utf8.len(frag)
    [7.92116]
    [7.92200]
    x = x + w
    end
    end
    -- Check whether to word-wrap line at pos which will be positioned at x.
    --
    -- We wrap at the start of a word (non-space just after space) if the word
    -- (non-spaces followed by spaces) wouldn't fit in the rest of the line.
    --
    -- x lies between 0 and editor.width.
    --
    -- Postcondition:
    -- Current line is not wider than editor.width
    --
    -- Desired properties in priority order:
    -- Next line doesn't start with whitespace
    -- Current line ends with whitespace (a.k.a. word wrap)
    -- Current line is close to full
    -- None of these is guaranteed. But we should never satisfy a lower priority
    -- before a higher one.
    function Text.should_word_wrap(editor, line, pos, char, x)
    if char:match('%s') then return false end
    if pos == 1 then return false end
    if Text.match(line, pos-1, '%S') then return false end
    local offset = Text.offset(line, pos)
    -- most of the time a word is printable chars + whitespace
    local s = line:match('%S+%s*', offset)
    assert(s)
    local w = editor.font:getWidth(s)
    if x+w < editor.width then return false end
    if w > editor.width then return false end -- we're going to need to truncate the next word anyway
    if x < 0.8*editor.width then
    local s2 = line:match('%S+', offset)
    local w2 = editor.font:getWidth(s2)
    if x+w2 > editor.width then
    -- there'll be some non-whitespace left over for the next line
    return false
    end
  • edit in source_text.lua at line 169
    [7.92206]
    [7.92206]
    return true
  • replacement in source_text.lua at line 252
    [7.21212][7.98872:98906](),[7.98872][7.98872:98906]()
    --== shortcuts that mutate text
    [7.21212]
    [7.98906]
    --== shortcuts that mutate text (must schedule_save)
  • edit in source_text.lua at line 1196
    [7.7962]
    [7.147120]
    end
    -- create a new iterator for s which provides the index and UTF-8 bytes corresponding to each codepoint
    function utf8chars(s, startpos)
    local next_pos = startpos or 1 -- in code points
    local next_offset = utf8.offset(s, next_pos) -- in bytes
    return function()
    assert(next_offset) -- never call the iterator after it returns nil
    local curr_pos = next_pos
    next_pos = next_pos+1
    local curr_offset = next_offset
    next_offset = utf8.offset(s, 2, next_offset)
    if next_offset == nil then return end
    local curr_char = s:sub(curr_offset, next_offset-1)
    return curr_pos, curr_char
    end
  • replacement in source_edit.lua at line 545
    [7.163966][7.163966:164021]()
    function edit.update_font_settings(State, font_height)
    [7.163966]
    [7.164021]
    function edit.update_font_settings(State, font_height, font)
  • replacement in source_edit.lua at line 547
    [7.164055][7.227:283]()
    State.font = love.graphics.newFont(State.font_height)
    [7.164055]
    [7.164128]
    State.font = font or love.graphics.newFont(State.font_height)
  • resurrect zombie in conf.lua at line 3
    [7.79][5.233:264](),[7.79][5.233:264]()
    t.window.usedpiscale = false
  • resolve order conflict in conf.lua at line 3
    [4.54]
    [5.233]