Merge lines.love

[?]
Sep 7, 2022, 1:46 AM
D4B52CQ2QKG2HQKFUQOO5S2ME325DTW3PH2D7SBXCW4BPQFYG7CAC

Dependencies

  • [2] VHUNJHXB Merge lines.love
  • [3] KMSL74GA support selections in the source editor
  • [4] XW7ANEJX switch shortcuts for bifold text
  • [5] VHQCNMAR several more modules
  • [6] CE4LZV4T drop last couple of manual tests
  • [7] RSZD5A7G forgot to add json.lua
  • [8] T4FRZSYL delete an ancient, unused file
  • [9] R5QXEHUI somebody stop me
  • [10] XX7G2FFJ intermingle freehand line drawings with text
  • [11] BLWAYPKV extract a module
  • [12] 73OCE2MC after much struggle, a brute-force undo
  • [13] MD3W5IRA new fork: rip out drawing support
  • [14] QS3YLNKZ Merge lines.love
  • [15] 6LJZN727 handle chords
  • [16] BJ5X5O4A let's prevent the text cursor from ever getting on a drawing
  • [17] 3QNOKBFM beginnings of a test harness
  • [18] OI4FPFIN support drawings in the source editor
  • [19] D2GCFTTT clean up repl functionality
  • [20] VXORMHME delete experimental REPL
  • [21] 2CTN2IEF Merge lines.love
  • [22] JOPVPUSA editing source code from within the app
  • [23] TLOAPLBJ add a license
  • [24] BULPIBEG beginnings of a module for the text editor
  • [25] 66X36NZN a little more prose describing manual_tests
  • [26] OTIBCAUJ love2d scaffold
  • [27] 32V6ZHQB Merge lines.love
  • [28] UN7GKYV5 support hyperlinks in the source editor
  • [29] AVTNUQYR basic test-enabled framework
  • [30] 4YDBYBA4 clean up memory leak experiments
  • [31] K2X6G75Z start writing some tests for drawings
  • [32] 2L5MEZV3 experiment: new edit namespace
  • [33] 3PSFWAIL Merge lines.love
  • [34] GUOQRUL7 Merge lines.love
  • [35] LXTTOB33 extract a couple of files
  • [36] OGUV4HSA remove some memory leaks from rendered fragments
  • [37] FS2ITYYH record a known issue
  • [38] TVCPXAAU rename
  • [39] KKMFQDR4 editing source code from within the app

Change contents

  • file deletion: source_text_tests.lua (----------)source_text_tests.lua (----------)
    [5.2][5.83676:83721](),[5.2][5.83676:83721](),[5.83721][5.3498:3498]()
    function test_undo_restores_selection()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- delete selected text
    edit.run_after_textinput(Editor_state, 'x')
    check_eq(Editor_state.lines[1].data, 'xbc', 'F - test_undo_restores_selection/baseline')
    check_nil(Editor_state.selection1.line, 'F - test_undo_restores_selection/baseline:selection')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z')
    edit.run_after_keychord(Editor_state, 'C-z')
    -- selection is restored
    check_eq(Editor_state.selection1.line, 1, 'F - test_undo_restores_selection/line')
    check_eq(Editor_state.selection1.pos, 2, 'F - test_undo_restores_selection/pos')
    end
    function test_search()
    io.write('\ntest_search')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    check_nil(Editor_state.selection1.line, 'F - test_undo_delete_text/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_undo_delete_text/selection:pos')
    --? check_eq(Editor_state.selection1.line, 2, 'F - test_undo_delete_text/selection:line')
    --? check_eq(Editor_state.selection1.pos, 4, 'F - test_undo_delete_text/selection:pos')
    y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_undo_delete_text/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'defg', 'F - test_undo_delete_text/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'F - test_undo_delete_text/screen:3')
    end
    check_nil(Editor_state.selection1.line, 'F - test_undo_delete_text/baseline/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_undo_delete_text/baseline/selection:pos')
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_undo_delete_text/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_undo_delete_text/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'F - test_undo_delete_text/baseline/screen:3')
    -- undo
    --? -- after undo, the backspaced key is selected
    edit.run_after_keychord(Editor_state, 'C-z')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_undo_delete_text/cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'F - test_undo_delete_text/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_undo_insert_text/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_undo_insert_text/selection:pos')
    y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_undo_insert_text/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_undo_insert_text/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'F - test_undo_insert_text/screen:3')
    end
    function test_undo_delete_text()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    -- delete a character
    edit.run_after_keychord(Editor_state, 'backspace')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_undo_delete_text/baseline/cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'F - test_undo_delete_text/baseline/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_undo_insert_text/baseline/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_undo_insert_text/baseline/selection:pos')
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_undo_insert_text/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'defg', 'F - test_undo_insert_text/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'F - test_undo_insert_text/baseline/screen:3')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_undo_insert_text/cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'F - test_undo_insert_text/cursor:pos')
    -- 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()
    io.write('\ntest_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')
    check_eq(Editor_state.lines[1].data, 'bc', "F - test_backspace_over_selection/data")
    -- cursor (remains) at start of selection
    check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_over_selection/cursor:line")
    check_eq(Editor_state.cursor1.pos, 1, "F - test_backspace_over_selection/cursor:pos")
    -- selection is cleared
    check_nil(Editor_state.selection1.line, "F - test_backspace_over_selection/selection")
    end
    function test_backspace_over_selection_reverse()
    io.write('\ntest_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')
    check_eq(Editor_state.lines[1].data, 'bc', "F - test_backspace_over_selection_reverse/data")
    -- cursor moves to start of selection
    check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_over_selection_reverse/cursor:line")
    check_eq(Editor_state.cursor1.pos, 1, "F - test_backspace_over_selection_reverse/cursor:pos")
    -- selection is cleared
    check_nil(Editor_state.selection1.line, "F - test_backspace_over_selection_reverse/selection")
    end
    function test_backspace_over_multiple_lines()
    io.write('\ntest_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')
    check_eq(Editor_state.lines[1].data, 'akl', "F - test_backspace_over_multiple_lines/data:1")
    check_eq(Editor_state.lines[2].data, 'mno', "F - test_backspace_over_multiple_lines/data:2")
    -- cursor remains at start of selection
    check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_over_multiple_lines/cursor:line")
    check_eq(Editor_state.cursor1.pos, 2, "F - test_backspace_over_multiple_lines/cursor:pos")
    -- selection is cleared
    check_nil(Editor_state.selection1.line, "F - test_backspace_over_multiple_lines/selection")
    end
    function test_backspace_to_end_of_line()
    io.write('\ntest_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')
    check_eq(Editor_state.lines[1].data, 'a', "F - test_backspace_to_start_of_line/data:1")
    check_eq(Editor_state.lines[2].data, 'def', "F - test_backspace_to_start_of_line/data:2")
    -- cursor remains at start of selection
    check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_to_start_of_line/cursor:line")
    check_eq(Editor_state.cursor1.pos, 2, "F - test_backspace_to_start_of_line/cursor:pos")
    -- selection is cleared
    check_nil(Editor_state.selection1.line, "F - test_backspace_to_start_of_line/selection")
    end
    function test_backspace_to_start_of_line()
    io.write('\ntest_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')
    check_eq(Editor_state.lines[1].data, 'abc', "F - test_backspace_to_start_of_line/data:1")
    check_eq(Editor_state.lines[2].data, 'f', "F - test_backspace_to_start_of_line/data:2")
    -- cursor remains at start of selection
    check_eq(Editor_state.cursor1.line, 2, "F - test_backspace_to_start_of_line/cursor:line")
    check_eq(Editor_state.cursor1.pos, 1, "F - test_backspace_to_start_of_line/cursor:pos")
    -- selection is cleared
    check_nil(Editor_state.selection1.line, "F - test_backspace_to_start_of_line/selection")
    end
    function test_undo_insert_text()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    -- insert a character
    edit.draw(Editor_state)
    edit.run_after_textinput(Editor_state, 'g')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_undo_insert_text/baseline/cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'F - test_undo_insert_text/baseline/cursor:pos')
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height*2+5, 1)
    -- cursor should move
    check_eq(Editor_state.cursor1.line, 1, 'F - test_position_cursor_on_recently_edited_wrapping_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 26, 'F - test_position_cursor_on_recently_edited_wrapping_line/cursor:pos')
    end
    function test_backspace_can_scroll_up()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'F - test_backspace_can_scroll_up/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_backspace_can_scroll_up/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up/baseline/screen:3')
    -- after hitting backspace the screen scrolls up by one line
    edit.run_after_keychord(Editor_state, 'backspace')
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_backspace_can_scroll_up/screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_backspace_can_scroll_up/cursor')
    y = Editor_state.top
    App.screen.check(y, 'abcdef', 'F - test_backspace_can_scroll_up/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_backspace_can_scroll_up/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up/screen:3')
    end
    function test_backspace_can_scroll_up_screen_line()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up_screen_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'F - test_backspace_can_scroll_up_screen_line/baseline/screen:2')
    -- after hitting backspace the screen scrolls up by one screen line
    edit.run_after_keychord(Editor_state, 'backspace')
    y = Editor_state.top
    App.screen.check(y, 'ghij', 'F - test_backspace_can_scroll_up_screen_line/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'F - test_backspace_can_scroll_up_screen_line/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'F - test_backspace_can_scroll_up_screen_line/screen:3')
    check_eq(Editor_state.screen_top1.line, 3, 'F - test_backspace_can_scroll_up_screen_line/screen_top')
    check_eq(Editor_state.screen_top1.pos, 1, 'F - test_backspace_can_scroll_up_screen_line/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_backspace_can_scroll_up_screen_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'F - test_backspace_can_scroll_up_screen_line/cursor:pos')
    end
    function test_backspace_past_line_boundary()
    io.write('\ntest_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')
    check_eq(Editor_state.lines[1].data, 'abcdef', "F - test_backspace_past_line_boundary")
    end
    function test_cut_without_selection()
    io.write('\ntest_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.screen_bottom1 = {}
    Editor_state.selection1 = {}
    edit.draw(Editor_state)
    -- try to cut without selecting text
    edit.run_after_keychord(Editor_state, 'C-x')
    -- no crash
    check_nil(Editor_state.selection1.line, 'F - test_cut_without_selection')
    end
    function test_pagedown()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    -- initially the first two lines are displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_pagedown/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_pagedown/baseline/screen:2')
    -- after pagedown the bottom line becomes the top
    edit.run_after_keychord(Editor_state, 'pagedown')
    check_eq(Editor_state.screen_top1.line, 2, 'F - test_pagedown/screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_pagedown/cursor')
    y = Editor_state.top
    App.screen.check(y, 'def', 'F - test_pagedown/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_pagedown/screen:2')
    check_nil(Editor_state.selection1.line, 'F - test_move_cursor_using_mouse/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_move_cursor_using_mouse/selection:pos')
    end
    function test_select_text_using_mouse()
    io.write('\ntest_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.screen_bottom1 = {}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
    -- 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, 'F - test_select_text_using_mouse/selection:line')
    check_eq(Editor_state.selection1.pos, 2, 'F - test_select_text_using_mouse/selection:pos')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_select_text_using_mouse/cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'F - test_select_text_using_mouse/cursor:pos')
    end
    function test_select_text_using_mouse_and_shift()
    io.write('\ntest_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.screen_bottom1 = {}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
    -- 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, 'F - test_select_text_using_mouse_and_shift/selection:line')
    check_eq(Editor_state.selection1.pos, 2, 'F - test_select_text_using_mouse_and_shift/selection:pos')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_select_text_using_mouse_and_shift/cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'F - test_select_text_using_mouse_and_shift/cursor:pos')
    end
    function test_select_text_repeatedly_using_mouse_and_shift()
    io.write('\ntest_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.screen_bottom1 = {}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
    -- 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, 'F - test_select_text_repeatedly_using_mouse_and_shift/selection:line')
    check_eq(Editor_state.selection1.pos, 2, 'F - test_select_text_repeatedly_using_mouse_and_shift/selection:pos')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_select_text_repeatedly_using_mouse_and_shift/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_select_text_repeatedly_using_mouse_and_shift/cursor:pos')
    end
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    check_eq(Editor_state.cursor1.line, 1, 'F - test_move_cursor_using_mouse/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_move_cursor_using_mouse/cursor:pos')
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
    function test_edit_deletes_selection()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- press a key
    edit.run_after_textinput(Editor_state, 'x')
    -- selected text is deleted and replaced with the key
    check_eq(Editor_state.lines[1].data, 'xbc', 'F - test_edit_deletes_selection')
    end
    function test_edit_with_shift_key_deletes_selection()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- mimic precise keypresses for a capital letter
    App.fake_key_press('lshift')
    edit.keychord_pressed(Editor_state, 'd', 'd')
    edit.textinput(Editor_state, 'D')
    edit.key_released(Editor_state, 'd')
    App.fake_key_release('lshift')
    -- selected text is deleted and replaced with the key
    check_nil(Editor_state.selection1.line, 'F - test_edit_with_shift_key_deletes_selection')
    check_eq(Editor_state.lines[1].data, 'Dbc', 'F - test_edit_with_shift_key_deletes_selection/data')
    end
    function test_copy_does_not_reset_selection()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- copy selection
    edit.run_after_keychord(Editor_state, 'C-c')
    check_eq(App.clipboard, 'a', 'F - test_copy_does_not_reset_selection/clipboard')
    -- selection is reset since shift key is not pressed
    check(Editor_state.selection1.line, 'F - test_copy_does_not_reset_selection')
    end
    function test_cut()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- press a key
    edit.run_after_keychord(Editor_state, 'C-x')
    check_eq(App.clipboard, 'a', 'F - test_cut/clipboard')
    -- selected text is deleted
    check_eq(Editor_state.lines[1].data, 'bc', 'F - test_cut/data')
    end
    function test_paste_replaces_selection()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- set clipboard
    App.clipboard = 'xyz'
    -- paste selection
    edit.run_after_keychord(Editor_state, 'C-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', 'F - test_paste_replaces_selection')
    end
    function test_deleting_selection_may_scroll()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'F - test_deleting_selection_may_scroll/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_deleting_selection_may_scroll/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_deleting_selection_may_scroll/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')
    -- page scrolls up
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_deleting_selection_may_scroll')
    check_eq(Editor_state.lines[1].data, 'ahi', 'F - test_deleting_selection_may_scroll/data')
    end
    function test_edit_wrapping_text()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    edit.run_after_textinput(Editor_state, 'g')
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_edit_wrapping_text/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'de', 'F - test_edit_wrapping_text/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'fg', 'F - test_edit_wrapping_text/screen:3')
    end
    function test_insert_newline()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_insert_newline/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_insert_newline/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_insert_newline/baseline/screen:3')
    -- hitting the enter key splits the line
    edit.run_after_keychord(Editor_state, 'return')
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_insert_newline/screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_insert_newline/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_insert_newline/cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'a', 'F - test_insert_newline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'bc', 'F - test_insert_newline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_insert_newline/screen:3')
    end
    function test_insert_newline_at_start_of_line()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    -- hitting the enter key splits the line
    edit.run_after_keychord(Editor_state, 'return')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_insert_newline_at_start_of_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_insert_newline_at_start_of_line/cursor:pos')
    check_eq(Editor_state.lines[1].data, '', 'F - test_insert_newline_at_start_of_line/data:1')
    check_eq(Editor_state.lines[2].data, 'abc', 'F - test_insert_newline_at_start_of_line/data:2')
    end
    function test_insert_from_clipboard()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_insert_from_clipboard/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_insert_from_clipboard/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_insert_from_clipboard/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')
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_insert_from_clipboard/screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_insert_from_clipboard/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_insert_from_clipboard/cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'axy', 'F - test_insert_from_clipboard/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'zbc', 'F - test_insert_from_clipboard/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_insert_from_clipboard/screen:3')
    end
    function test_move_cursor_using_mouse()
    io.write('\ntest_move_cursor_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.screen_bottom1 = {}
    end
    function test_select_text()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- select a letter
    App.fake_key_press('lshift')
    edit.run_after_keychord(Editor_state, 'S-right')
    App.fake_key_release('lshift')
    edit.key_released(Editor_state, 'lshift')
    -- selection persists even after shift is released
    check_eq(Editor_state.selection1.line, 1, 'F - test_select_text/selection:line')
    check_eq(Editor_state.selection1.pos, 1, 'F - test_select_text/selection:pos')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_select_text/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_select_text/cursor:pos')
    end
    function test_cursor_movement_without_shift_resets_selection()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- press an arrow key without shift
    edit.run_after_keychord(Editor_state, 'right')
    -- no change to data, selection is reset
    check_nil(Editor_state.selection1.line, 'F - test_cursor_movement_without_shift_resets_selection')
    check_eq(Editor_state.lines[1].data, 'abc', 'F - test_cursor_movement_without_shift_resets_selection/data')
    end
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/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
    io.write('\ntest_draw_text_wrapping_within_word')
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abcd e fghijk', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abcd ', 'F - test_draw_text_wrapping_within_word/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'e fgh', 'F - test_draw_text_wrapping_within_word/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ijk', 'F - test_draw_text_wrapping_within_word/screen:3')
    end
    function test_draw_wrapping_text_containing_non_ascii()
    -- draw a long line containing non-ASCII
    io.write('\ntest_draw_wrapping_text_containing_non_ascii')
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'madam I’m adam', 'xyz'} -- notice the non-ASCII apostrophe
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'mad', 'F - test_draw_wrapping_text_containing_non_ascii/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'am I', 'F - test_draw_wrapping_text_containing_non_ascii/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, '’m a', 'F - test_draw_wrapping_text_containing_non_ascii/screen:3')
    end
    function test_click_on_wrapping_line()
    io.write('\ntest_click_on_wrapping_line')
    -- display a wrapping line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'F - test_click_on_wrapping_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, "I'm ad", 'F - test_click_on_wrapping_line/baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 12, 'F - test_click_on_wrapping_line/cursor:pos')
    end
    function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()
    io.write('\ntest_click_on_wrapping_line_rendered_from_partway_at_top_of_screen')
    -- display a wrapping line from its second screen line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=8}
    Editor_state.screen_top1 = {line=1, pos=7}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, "I'm ad", 'F - test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen/baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen/cursor:line')
    check_eq(Editor_state.cursor1.pos, 12, 'F - test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen/cursor:pos')
    end
    function test_click_past_end_of_wrapping_line()
    io.write('\ntest_click_past_end_of_wrapping_line')
    -- display a wrapping line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'F - test_click_past_end_of_wrapping_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, "I'm ad", 'F - test_click_past_end_of_wrapping_line/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'am', 'F - test_click_past_end_of_wrapping_line/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, 'F - test_click_past_end_of_wrapping_line/cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_wrapping_line_containing_non_ascii()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'F - test_click_past_end_of_wrapping_line_containing_non_ascii/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'I’m ad', 'F - test_click_past_end_of_wrapping_line_containing_non_ascii/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'am', 'F - test_click_past_end_of_wrapping_line_containing_non_ascii/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, 'F - test_click_past_end_of_wrapping_line_containing_non_ascii/cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_word_wrapping_line()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'the quick brown fox ', 'F - test_click_past_end_of_word_wrapping_line/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
    check_eq(Editor_state.cursor1.pos, 20, 'F - test_click_past_end_of_word_wrapping_line/cursor')
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_on_wrapping_line/selection is empty to avoid perturbing future edits')
    end
    function test_click_with_mouse_on_wrapping_line_takes_margins_into_account()
    io.write('\ntest_click_with_mouse_on_wrapping_line_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 ghi jkl mno pqr stu'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=20}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_takes_margins_into_account/selection is empty to avoid perturbing future edits')
    end
    function test_click_with_mouse_on_empty_line()
    io.write('\ntest_click_with_mouse_on_empty_line')
    -- display two lines with the first one empty
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- 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, 'F - test_click_with_mouse_on_empty_line/cursor')
    end
    function test_draw_text()
    io.write('\ntest_draw_text')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_draw_text/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_draw_text/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_draw_text/screen:3')
    end
    function test_draw_wrapping_text()
    io.write('\ntest_draw_wrapping_text')
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'defgh', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_draw_wrapping_text/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'de', 'F - test_draw_wrapping_text/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'fgh', 'F - test_draw_wrapping_text/screen:3')
    end
    function test_draw_word_wrapping_text()
    io.write('\ntest_draw_word_wrapping_text')
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc ', 'F - test_draw_word_wrapping_text/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def ', 'F - test_draw_word_wrapping_text/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_draw_word_wrapping_text/screen:3')
    end
    function test_click_with_mouse_on_wrapping_line()
    io.write('\ntest_click_with_mouse_on_wrapping_line')
    -- display two lines with cursor on one of them
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=20}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_on_wrapping_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_with_mouse_on_wrapping_line/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_to_left_of_line/selection is empty to avoid perturbing future edits')
    end
    function test_click_with_mouse_takes_margins_into_account()
    io.write('\ntest_click_with_mouse_takes_margins_into_account')
    -- display two lines with cursor on one of them
    App.screen.init{width=100, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.left = 50 -- occupy only right side of screen
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- 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, 'F - test_click_with_mouse_takes_margins_into_account/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_with_mouse_takes_margins_into_account/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse/selection is empty to avoid perturbing future edits')
    end
    function test_click_with_mouse_to_left_of_line()
    io.write('\ntest_click_with_mouse_to_left_of_line')
    -- display a line with the cursor in the middle
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- 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, 'F - test_click_with_mouse_to_left_of_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_click_with_mouse_to_left_of_line/cursor:pos')
  • file deletion: source_text.lua (----------)source_text.lua (----------)
    [5.2][5.147062:147101](),[5.2][5.147062:147101](),[5.147101][5.83723:83723]()
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
    Text.pagedown(State)
    end
    end
    function Text.insert_return(State)
    if State.cursor1.pos then
    -- when inserting a newline, move any B side to the new line
    local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
    Text.pageup(State)
    elseif chord == 'S-pagedown' then
    State.selection1 = {}
    elseif chord == 'S-pageup' then
    State.selection1 = {}
    elseif chord == 'pagedown' then
    Text.pagedown(State)
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
    Text.down(State)
    elseif chord == 'pageup' then
    Text.pageup(State)
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
    Text.up(State)
    elseif chord == 'S-down' then
    State.selection1 = {}
    elseif chord == 'S-up' then
    State.selection1 = {}
    elseif chord == 'down' then
    Text.down(State)
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
    Text.end_of_line(State)
    elseif chord == 'up' then
    Text.up(State)
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
    Text.start_of_line(State)
    elseif chord == 'S-end' then
    State.selection1 = {}
    elseif chord == 'S-home' then
    State.selection1 = {}
    elseif chord == 'end' then
    Text.end_of_line(State)
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
    Text.word_right(State)
    elseif chord == 'home' then
    Text.start_of_line(State)
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
    Text.word_left(State)
    elseif chord == 'M-S-right' then
    State.selection1 = {}
    elseif chord == 'M-S-left' then
    State.selection1 = {}
    elseif chord == 'M-right' then
    Text.word_right(State)
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
    Text.right(State)
    -- C- hotkeys reserved for drawings, so we'll use M-
    elseif chord == 'M-left' then
    Text.word_left(State)
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
    Text.left(State)
    elseif chord == 'S-right' then
    State.selection1 = {}
    elseif chord == 'S-left' then
    State.selection1 = {}
    elseif chord == 'right' then
    Text.right(State)
    if State.selection1.line then
    Text.delete_selection(State, State.left, State.right)
    schedule_save(State)
    return
    end
    local before
    if State.cursor1.posB or State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
    before = snapshot(State, State.cursor1.line)
    else
    before = snapshot(State, State.cursor1.line, State.cursor1.line+1)
    end
    if State.cursor1.pos and State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
    local byte_start = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
    local byte_end = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos+1)
    if byte_start then
    if byte_end then
    State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].data, byte_end)
    else
    State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)
    end
    -- no change to State.cursor1.pos
    end
    elseif State.cursor1.posB then
    if State.cursor1.posB <= utf8.len(State.lines[State.cursor1.line].dataB) then
    local byte_start = utf8.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB)
    local byte_end = utf8.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB+1)
    if byte_start then
    if byte_end then
    State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].dataB, byte_end)
    else
    State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1)
    end
    -- no change to State.cursor1.pos
    end
    else
    -- refuse to delete past end of side B
    end
    elseif State.cursor1.line < #State.lines then
    if State.selection1.line then
    Text.delete_selection(State, State.left, State.right)
    schedule_save(State)
    return
    end
    local before
    if State.cursor1.pos and State.cursor1.pos > 1 then
    before = snapshot(State, State.cursor1.line)
    local byte_start = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos-1)
    local byte_end = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
    if byte_start then
    if byte_end then
    State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].data, byte_end)
    else
    State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)
    end
    State.cursor1.pos = State.cursor1.pos-1
    end
    elseif State.cursor1.posB then
    if State.cursor1.posB > 1 then
    before = snapshot(State, State.cursor1.line)
    local byte_start = utf8.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB-1)
    local byte_end = utf8.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB)
    if byte_start then
    if byte_end then
    State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].dataB, byte_end)
    else
    State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1)
    end
    State.cursor1.posB = State.cursor1.posB-1
    end
    else
    -- refuse to delete past beginning of side B
    end
    elseif State.cursor1.line > 1 then
    before = snapshot(State, State.cursor1.line-1, State.cursor1.line)
    State.selection1 = {}
    if State.cursor_y > App.screen.height - State.line_height then
    Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
    end
    schedule_save(State)
    record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
    elseif chord == 'tab' then
    local before = snapshot(State, State.cursor1.line)
    --? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
    Text.insert_at_cursor(State, '\t')
    if State.cursor_y > App.screen.height - State.line_height then
    Text.populate_screen_line_starting_pos(State, State.cursor1.line)
    Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
    --? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
    end
    schedule_save(State)
    record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
    elseif chord == 'backspace' then
    --? print('chord', chord, State.selection1.line, State.selection1.pos)
    --== shortcuts that mutate text
    if chord == 'return' then
    local before_line = State.cursor1.line
    local before = snapshot(State, before_line)
    Text.insert_return(State)
    end
    if State.selection1.line then
    local lo, hi = Text.clip_selection(State, line_index, pos, pos+frag_len)
    Text.draw_highlight(State, line, x,y, pos, lo,hi)
    end
    App.screen.draw(frag_text, x,y)
    -- render cursor if necessary
    if State.cursor1.posB and line_index == State.cursor1.line then
    if pos <= State.cursor1.posB and pos + frag_len > State.cursor1.posB then
    if State.search_term then
    if State.lines[State.cursor1.line].dataB:sub(State.cursor1.posB, State.cursor1.posB+utf8.len(State.search_term)-1) == State.search_term then
    local lo_px = Text.draw_highlight(State, line, x,y, pos, State.cursor1.posB, State.cursor1.posB+utf8.len(State.search_term))
    App.color(Fold_color)
    love.graphics.print(State.search_term, x+lo_px,y)
    end
    elseif Focus == 'edit' then
    Text.draw_cursor(State, x+Text.x(frag, State.cursor1.posB-pos+1), y)
    App.color(Fold_color)
    end
    end
    end
    x = x + frag_width
    end
    pos = pos + frag_len
    end
    return false, x,y, pos, screen_line_starting_pos
    end
    function Text.draw_cursor(State, x, y)
    -- blink every 0.5s
    if math.floor(Cursor_time*2)%2 == 0 then
    App.color(Cursor_color)
    love.graphics.rectangle('fill', x,y, 3,State.line_height)
    end
    State.cursor_x = x
    State.cursor_y = y+State.line_height
    end
    function Text.populate_screen_line_starting_pos(State, line_index)
    local line = State.lines[line_index]
    end
    if State.selection1.line then
    local lo, hi = Text.clip_selection(State, line_index, pos, pos+frag_len)
    Text.draw_highlight(State, line, x,y, pos, lo,hi)
    end
  • file deletion: source_edit.lua (----------)source_edit.lua (----------)
    [5.2][5.165725:165764](),[5.2][5.165725:165764](),[5.165764][5.152440:152440]()
    State.selection1 = deepcopy(src.selection)
    patch(State.lines, event.before, event.after)
    State.selection1 = deepcopy(src.selection)
    patch(State.lines, event.after, event.before)
    patch_placeholders(State.line_cache, event.after, event.before)
    if State.selection1.line and
    not State.lines.current_drawing and
    -- printable character created using shift key => delete selection
    -- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)
    (not App.shift_down() or utf8.len(key) == 1) and
    chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and backspace ~= 'delete' and not App.is_cursor_movement(chord) then
    Text.delete_selection(State, State.left, State.right)
    end
    elseif chord == 'M-d' then
    if State.cursor1.posB == nil then
    local before = snapshot(State, State.cursor1.line)
    if State.lines[State.cursor1.line].dataB == nil then
    State.lines[State.cursor1.line].dataB = ''
    end
    State.lines[State.cursor1.line].expanded = true
    State.cursor1.pos = nil
    State.cursor1.posB = 1
    if Text.cursor_out_of_screen(State) then
    Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
    end
    schedule_save(State)
    record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
    end
    -- zoom
    elseif chord == 'C-=' then
    edit.update_font_settings(State, State.font_height+2)
    Text.redraw_all(State)
    elseif chord == 'C--' then
    edit.update_font_settings(State, State.font_height-2)
    Text.redraw_all(State)
    elseif chord == 'C-0' then
    edit.update_font_settings(State, 20)
    Text.redraw_all(State)
    -- undo
    elseif chord == 'C-z' then
    for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
    local event = undo_event(State)
    if event then
    local src = event.before
    State.screen_top1 = deepcopy(src.screen_top)
    State.cursor1 = deepcopy(src.cursor)
    elseif chord == 'M-b' then
    State.expanded = not State.expanded
    Text.redraw_all(State)
    if not State.expanded then
    for _,line in ipairs(State.lines) do
    line.expanded = nil
    end
    edit.eradicate_locations_after_the_fold(State)
    end
    if State.search_term then
    if chord == 'escape' then
    State.search_term = nil
    State.search_text = nil
    State.cursor1 = State.search_backup.cursor
    State.screen_top1 = State.search_backup.screen_top
    State.search_backup = nil
    Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
    elseif chord == 'return' then
    State.search_term = nil
    State.search_text = nil
    State.search_backup = nil
    elseif chord == 'backspace' then
    local len = utf8.len(State.search_term)
    local byte_offset = Text.offset(State.search_term, len)
    State.search_term = string.sub(State.search_term, 1, byte_offset-1)
    State.search_text = nil
    elseif chord == 'down' then
    if State.cursor1.pos then
    State.cursor1.pos = State.cursor1.pos+1
    else
    State.cursor1.posB = State.cursor1.posB+1
    end
    Text.search_next(State)
    elseif chord == 'up' then
    Text.search_previous(State)
    end
    return
    elseif chord == 'C-f' then
    State.search_term = ''
    State.search_backup = {
    cursor={line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB},
    screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos, posB=State.screen_top1.posB},
    }
    assert(State.search_text == nil)
    -- bifold text
    else
    for line_index,line in ipairs(State.lines) do
    if line.mode == 'text' then
    if Text.in_line(State, line_index, x,y) then
    --? print('reset selection')
    local pos,posB = Text.to_pos_on_line(State, line_index, x, y)
    State.cursor1 = {line=line_index, pos=pos, posB=posB}
    --? print('cursor', State.cursor1.line, State.cursor1.pos, State.cursor1.posB)
    if State.mousepress_shift then
    if State.old_selection1.line == nil then
    State.selection1 = State.old_cursor1
    else
    State.selection1 = State.old_selection1
    end
    end
    State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
    if eq(State.cursor1, State.selection1) then
    State.selection1 = {}
    end
    break
    end
    end
    end
    --? print('selection:', State.selection1.line, State.selection1.pos)
    end
    State.selection1 = {line=line_index, pos=pos, posB=posB}
    --? print('selection', State.selection1.line, State.selection1.pos, State.selection1.posB)
    break
    end
    elseif line.mode == 'drawing' then
    local line_cache = State.line_cache[line_index]
    if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
    State.lines.current_drawing_index = line_index
    State.lines.current_drawing = line
    Drawing.before = snapshot(State, line_index)
    Drawing.mouse_pressed(State, line_index, x,y, mouse_button)
    break
    end
    -- delicate dance between cursor, selection and old cursor/selection
    -- scenarios:
    -- regular press+release: sets cursor, clears selection
    -- shift press+release:
    -- sets selection to old cursor if not set otherwise leaves it untouched
    -- sets cursor
    -- press and hold to start a selection: sets selection on press, cursor on release
    -- press and hold, then press shift: ignore shift
    -- i.e. mouse_released should never look at shift state
    State.old_cursor1 = State.cursor1
    State.old_selection1 = State.selection1
    State.mousepress_shift = App.shift_down()
    local pos,posB = Text.to_pos_on_line(State, line_index, x, y)
    --? print(x,y, 'setting cursor:', line_index, pos, posB)
    selection1 = {},
    -- some extra state to compute selection between mouse press and release
    old_cursor1 = nil,
    old_selection1 = nil,
    mousepress_shift = nil,
    -- when selecting text, avoid recomputing some state on every single frame
    recent_mouse = {},
    -- cursor coordinates in pixels
    cursor_x = 0,
    cursor_y = 0,
  • file deletion: commands.lua (----------)commands.lua (----------)
    [5.2][5.207726:207762](),[5.2][5.207726:207762](),[5.207762][5.204370:204370]()
    add_hotkey_to_menu('alt+d: create/edit debug print')
    add_hotkey_to_menu('ctrl+f: find in file')
    add_hotkey_to_menu('alt+left alt+right: prev/next word')
    elseif Focus == 'log_browser' then
    -- nothing yet
    else
    assert(false, 'unknown focus "'..Focus..'"')
    end
    add_hotkey_to_menu('ctrl+z ctrl+y: undo/redo')
    add_hotkey_to_menu('ctrl+x ctrl+c ctrl+v: cut/copy/paste')
    add_hotkey_to_menu('ctrl+= ctrl+- ctrl+0: zoom')
    end
    function add_hotkey_to_menu(s)
    if Text_cache[s] == nil then
    Text_cache[s] = App.newText(love.graphics.getFont(), s)
    end
    local width = App.width(Text_cache[s])
    if Menu_cursor + width > App.screen.width - 5 then
    return
    end
    App.color(Menu_command_color)
    App.screen.draw(Text_cache[s], Menu_cursor,5)
    Menu_cursor = Menu_cursor + width + 30
    end
    function source.draw_file_navigator()
    for i,file in ipairs(File_navigation.candidates) do
    if file == 'source' then
    App.color(Menu_border_color)
    love.graphics.line(Menu_cursor-10,2, Menu_cursor-10,Menu_status_bar_height-2)
    end
    add_file_to_menu(file, i == File_navigation.index)
    end
    end
    function add_file_to_menu(s, cursor_highlight)
    if Text_cache[s] == nil then
    Text_cache[s] = App.newText(love.graphics.getFont(), s)
    end
    local width = App.width(Text_cache[s])
    if Menu_cursor + width > App.screen.width - 5 then
    return
    end
    if cursor_highlight then
    App.color(Menu_highlight_color)
    love.graphics.rectangle('fill', Menu_cursor-5,5-2, App.width(Text_cache[s])+5*2,Editor_state.line_height+2*2)
    end
    App.color(Menu_command_color)
    App.screen.draw(Text_cache[s], Menu_cursor,5)
    Menu_cursor = Menu_cursor + width + 30
    end
    function keychord_pressed_on_file_navigator(chord, key)
    if chord == 'escape' then
    Show_file_navigator = false
    elseif chord == 'return' then
    local candidate = guess_source(File_navigation.candidates[File_navigation.index]..'.lua')
    source.switch_to_file(candidate)
    Show_file_navigator = false
    elseif chord == 'left' then
    if File_navigation.index > 1 then
    File_navigation.index = File_navigation.index-1
    end
    elseif chord == 'right' then
    if File_navigation.index < #File_navigation.candidates then
    File_navigation.index = File_navigation.index+1
    end
    end
    end
    add_hotkey_to_menu('alt+b: expand debug prints')
    end
    add_hotkey_to_menu('alt+b: collapse debug prints')
    else
  • edit in source_text_tests.lua at line 285
    [5.13950]
    [5.13950]
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse/selection is empty to avoid perturbing future edits')
  • edit in source_text_tests.lua at line 304
    [5.14763]
    [5.14763]
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_to_left_of_line/selection is empty to avoid perturbing future edits')
  • edit in source_text_tests.lua at line 324
    [5.15667]
    [5.15667]
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_takes_margins_into_account/selection is empty to avoid perturbing future edits')
  • edit in source_text_tests.lua at line 414
    [5.19271]
    [5.19271]
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_on_wrapping_line/selection is empty to avoid perturbing future edits')
  • edit in source_text_tests.lua at line 434
    [5.20261]
    [5.20261]
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/selection is empty to avoid perturbing future edits')
  • edit in source_text_tests.lua at line 591
    [5.27913]
    [5.27913]
    end
    function test_select_text()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- select a letter
    App.fake_key_press('lshift')
    edit.run_after_keychord(Editor_state, 'S-right')
    App.fake_key_release('lshift')
    edit.key_released(Editor_state, 'lshift')
    -- selection persists even after shift is released
    check_eq(Editor_state.selection1.line, 1, 'F - test_select_text/selection:line')
    check_eq(Editor_state.selection1.pos, 1, 'F - test_select_text/selection:pos')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_select_text/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_select_text/cursor:pos')
    end
    function test_cursor_movement_without_shift_resets_selection()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- press an arrow key without shift
    edit.run_after_keychord(Editor_state, 'right')
    -- no change to data, selection is reset
    check_nil(Editor_state.selection1.line, 'F - test_cursor_movement_without_shift_resets_selection')
    check_eq(Editor_state.lines[1].data, 'abc', 'F - test_cursor_movement_without_shift_resets_selection/data')
  • edit in source_text_tests.lua at line 635
    [5.27918]
    [5.27918]
    function test_edit_deletes_selection()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- press a key
    edit.run_after_textinput(Editor_state, 'x')
    -- selected text is deleted and replaced with the key
    check_eq(Editor_state.lines[1].data, 'xbc', 'F - test_edit_deletes_selection')
    end
    function test_edit_with_shift_key_deletes_selection()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- mimic precise keypresses for a capital letter
    App.fake_key_press('lshift')
    edit.keychord_pressed(Editor_state, 'd', 'd')
    edit.textinput(Editor_state, 'D')
    edit.key_released(Editor_state, 'd')
    App.fake_key_release('lshift')
    -- selected text is deleted and replaced with the key
    check_nil(Editor_state.selection1.line, 'F - test_edit_with_shift_key_deletes_selection')
    check_eq(Editor_state.lines[1].data, 'Dbc', 'F - test_edit_with_shift_key_deletes_selection/data')
    end
    function test_copy_does_not_reset_selection()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- copy selection
    edit.run_after_keychord(Editor_state, 'C-c')
    check_eq(App.clipboard, 'a', 'F - test_copy_does_not_reset_selection/clipboard')
    -- selection is reset since shift key is not pressed
    check(Editor_state.selection1.line, 'F - test_copy_does_not_reset_selection')
    end
    function test_cut()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- press a key
    edit.run_after_keychord(Editor_state, 'C-x')
    check_eq(App.clipboard, 'a', 'F - test_cut/clipboard')
    -- selected text is deleted
    check_eq(Editor_state.lines[1].data, 'bc', 'F - test_cut/data')
    end
    function test_paste_replaces_selection()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- set clipboard
    App.clipboard = 'xyz'
    -- paste selection
    edit.run_after_keychord(Editor_state, 'C-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', 'F - test_paste_replaces_selection')
    end
    function test_deleting_selection_may_scroll()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'F - test_deleting_selection_may_scroll/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_deleting_selection_may_scroll/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_deleting_selection_may_scroll/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')
    -- page scrolls up
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_deleting_selection_may_scroll')
    check_eq(Editor_state.lines[1].data, 'ahi', 'F - test_deleting_selection_may_scroll/data')
    end
  • edit in source_text_tests.lua at line 868
    [5.32839]
    [5.32839]
    Editor_state.selection1 = {}
  • replacement in source_text_tests.lua at line 870
    [5.32934][5.32934:33020]()
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    [5.32934]
    [5.33020]
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
  • edit in source_text_tests.lua at line 873
    [5.33196]
    [5.33196]
    check_nil(Editor_state.selection1.line, 'F - test_move_cursor_using_mouse/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_move_cursor_using_mouse/selection:pos')
    end
    function test_select_text_using_mouse()
    io.write('\ntest_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.screen_bottom1 = {}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
    -- 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, 'F - test_select_text_using_mouse/selection:line')
    check_eq(Editor_state.selection1.pos, 2, 'F - test_select_text_using_mouse/selection:pos')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_select_text_using_mouse/cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'F - test_select_text_using_mouse/cursor:pos')
    end
    function test_select_text_using_mouse_and_shift()
    io.write('\ntest_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.screen_bottom1 = {}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
    -- 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, 'F - test_select_text_using_mouse_and_shift/selection:line')
    check_eq(Editor_state.selection1.pos, 2, 'F - test_select_text_using_mouse_and_shift/selection:pos')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_select_text_using_mouse_and_shift/cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'F - test_select_text_using_mouse_and_shift/cursor:pos')
    end
    function test_select_text_repeatedly_using_mouse_and_shift()
    io.write('\ntest_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.screen_bottom1 = {}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
    -- 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, 'F - test_select_text_repeatedly_using_mouse_and_shift/selection:line')
    check_eq(Editor_state.selection1.pos, 2, 'F - test_select_text_repeatedly_using_mouse_and_shift/selection:pos')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_select_text_repeatedly_using_mouse_and_shift/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_select_text_repeatedly_using_mouse_and_shift/cursor:pos')
  • edit in source_text_tests.lua at line 954
    [5.33201]
    [5.33201]
    function test_cut_without_selection()
    io.write('\ntest_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.screen_bottom1 = {}
    Editor_state.selection1 = {}
    edit.draw(Editor_state)
    -- try to cut without selecting text
    edit.run_after_keychord(Editor_state, 'C-x')
    -- no crash
    check_nil(Editor_state.selection1.line, 'F - test_cut_without_selection')
    end
  • replacement in source_text_tests.lua at line 1714
    [5.73329][5.73329:73442]()
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height*2+5, 1)
    [5.73329]
    [5.73442]
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height*2+5, 1)
  • edit in source_text_tests.lua at line 1791
    [5.77346]
    [5.77346]
    -- 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()
    io.write('\ntest_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')
    check_eq(Editor_state.lines[1].data, 'bc', "F - test_backspace_over_selection/data")
    -- cursor (remains) at start of selection
    check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_over_selection/cursor:line")
    check_eq(Editor_state.cursor1.pos, 1, "F - test_backspace_over_selection/cursor:pos")
    -- selection is cleared
    check_nil(Editor_state.selection1.line, "F - test_backspace_over_selection/selection")
    end
    function test_backspace_over_selection_reverse()
    io.write('\ntest_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')
    check_eq(Editor_state.lines[1].data, 'bc', "F - test_backspace_over_selection_reverse/data")
    -- cursor moves to start of selection
    check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_over_selection_reverse/cursor:line")
    check_eq(Editor_state.cursor1.pos, 1, "F - test_backspace_over_selection_reverse/cursor:pos")
    -- selection is cleared
    check_nil(Editor_state.selection1.line, "F - test_backspace_over_selection_reverse/selection")
    end
    function test_backspace_over_multiple_lines()
    io.write('\ntest_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')
    check_eq(Editor_state.lines[1].data, 'akl', "F - test_backspace_over_multiple_lines/data:1")
    check_eq(Editor_state.lines[2].data, 'mno', "F - test_backspace_over_multiple_lines/data:2")
    -- cursor remains at start of selection
    check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_over_multiple_lines/cursor:line")
    check_eq(Editor_state.cursor1.pos, 2, "F - test_backspace_over_multiple_lines/cursor:pos")
    -- selection is cleared
    check_nil(Editor_state.selection1.line, "F - test_backspace_over_multiple_lines/selection")
    end
    function test_backspace_to_end_of_line()
    io.write('\ntest_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')
    check_eq(Editor_state.lines[1].data, 'a', "F - test_backspace_to_start_of_line/data:1")
    check_eq(Editor_state.lines[2].data, 'def', "F - test_backspace_to_start_of_line/data:2")
    -- cursor remains at start of selection
    check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_to_start_of_line/cursor:line")
    check_eq(Editor_state.cursor1.pos, 2, "F - test_backspace_to_start_of_line/cursor:pos")
    -- selection is cleared
    check_nil(Editor_state.selection1.line, "F - test_backspace_to_start_of_line/selection")
    end
    function test_backspace_to_start_of_line()
    io.write('\ntest_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')
    check_eq(Editor_state.lines[1].data, 'abc', "F - test_backspace_to_start_of_line/data:1")
    check_eq(Editor_state.lines[2].data, 'f', "F - test_backspace_to_start_of_line/data:2")
    -- cursor remains at start of selection
    check_eq(Editor_state.cursor1.line, 2, "F - test_backspace_to_start_of_line/cursor:line")
    check_eq(Editor_state.cursor1.pos, 1, "F - test_backspace_to_start_of_line/cursor:pos")
    -- selection is cleared
    check_nil(Editor_state.selection1.line, "F - test_backspace_to_start_of_line/selection")
    end
  • edit in source_text_tests.lua at line 1906
    [5.77987]
    [5.77987]
    check_nil(Editor_state.selection1.line, 'F - test_undo_insert_text/baseline/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_undo_insert_text/baseline/selection:pos')
  • edit in source_text_tests.lua at line 1918
    [5.78534]
    [5.78534]
    check_nil(Editor_state.selection1.line, 'F - test_undo_insert_text/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_undo_insert_text/selection:pos')
  • edit in source_text_tests.lua at line 1941
    [5.79456]
    [5.79456]
    check_nil(Editor_state.selection1.line, 'F - test_undo_delete_text/baseline/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_undo_delete_text/baseline/selection:pos')
  • edit in source_text_tests.lua at line 1954
    [5.80054]
    [5.80054]
    check_nil(Editor_state.selection1.line, 'F - test_undo_delete_text/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_undo_delete_text/selection:pos')
    --? check_eq(Editor_state.selection1.line, 2, 'F - test_undo_delete_text/selection:line')
    --? check_eq(Editor_state.selection1.pos, 4, 'F - test_undo_delete_text/selection:pos')
  • edit in source_text_tests.lua at line 1966
    [5.80354]
    [5.80354]
    function test_undo_restores_selection()
    io.write('\ntest_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}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- delete selected text
    edit.run_after_textinput(Editor_state, 'x')
    check_eq(Editor_state.lines[1].data, 'xbc', 'F - test_undo_restores_selection/baseline')
    check_nil(Editor_state.selection1.line, 'F - test_undo_restores_selection/baseline:selection')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z')
    edit.run_after_keychord(Editor_state, 'C-z')
    -- selection is restored
    check_eq(Editor_state.selection1.line, 1, 'F - test_undo_restores_selection/line')
    check_eq(Editor_state.selection1.pos, 2, 'F - test_undo_restores_selection/pos')
    end
  • edit in source_text.lua at line 112
    [5.88242]
    [5.88242]
    end
    if State.selection1.line then
    local lo, hi = Text.clip_selection(State, line_index, pos, pos+frag_len)
    Text.draw_highlight(State, line, x,y, pos, lo,hi)
  • edit in source_text.lua at line 178
    [5.90188]
    [5.90188]
    end
    if State.selection1.line then
    local lo, hi = Text.clip_selection(State, line_index, pos, pos+frag_len)
    Text.draw_highlight(State, line, x,y, pos, lo,hi)
  • replacement in source_text.lua at line 386
    [5.98844][5.98844:98872]()
    --? print('chord', chord)
    [5.98844]
    [5.98872]
    --? print('chord', chord, State.selection1.line, State.selection1.pos)
  • edit in source_text.lua at line 392
    [5.99055]
    [5.99055]
    State.selection1 = {}
  • edit in source_text.lua at line 410
    [5.100140]
    [5.100140]
    if State.selection1.line then
    Text.delete_selection(State, State.left, State.right)
    schedule_save(State)
    return
    end
  • edit in source_text.lua at line 473
    [5.103120]
    [5.103120]
    if State.selection1.line then
    Text.delete_selection(State, State.left, State.right)
    schedule_save(State)
    return
    end
  • edit in source_text.lua at line 526
    [5.105607]
    [5.105607]
    State.selection1 = {}
  • edit in source_text.lua at line 529
    [5.105660]
    [5.105660]
    State.selection1 = {}
  • edit in source_text.lua at line 531
    [5.105692]
    [5.105692]
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
  • edit in source_text.lua at line 536
    [5.105746]
    [5.105746]
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
  • edit in source_text.lua at line 543
    [5.105881]
    [5.105881]
    State.selection1 = {}
  • edit in source_text.lua at line 546
    [5.105941]
    [5.105941]
    State.selection1 = {}
  • edit in source_text.lua at line 548
    [5.105975]
    [5.105975]
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
  • edit in source_text.lua at line 553
    [5.106036]
    [5.106036]
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
  • edit in source_text.lua at line 559
    [5.106123]
    [5.106123]
    State.selection1 = {}
  • edit in source_text.lua at line 562
    [5.106180]
    [5.106180]
    State.selection1 = {}
  • edit in source_text.lua at line 564
    [5.106212]
    [5.106212]
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
  • edit in source_text.lua at line 569
    [5.106273]
    [5.106273]
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
  • edit in source_text.lua at line 575
    [5.106348]
    [5.106348]
    State.selection1 = {}
  • edit in source_text.lua at line 578
    [5.106399]
    [5.106399]
    State.selection1 = {}
  • edit in source_text.lua at line 580
    [5.106429]
    [5.106429]
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
  • edit in source_text.lua at line 585
    [5.106480]
    [5.106480]
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
  • edit in source_text.lua at line 591
    [5.106556]
    [5.106556]
    State.selection1 = {}
  • edit in source_text.lua at line 594
    [5.106615]
    [5.106615]
    State.selection1 = {}
  • edit in source_text.lua at line 596
    [5.106649]
    [5.106649]
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
  • edit in source_text.lua at line 601
    [5.106708]
    [5.106708]
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
  • edit in source_edit.lua at line 78
    [5.154793]
    [5.154793]
    selection1 = {},
    -- some extra state to compute selection between mouse press and release
    old_cursor1 = nil,
    old_selection1 = nil,
    mousepress_shift = nil,
    -- when selecting text, avoid recomputing some state on every single frame
    recent_mouse = {},
  • edit in source_edit.lua at line 219
    [5.20196]
    [5.20196]
    -- delicate dance between cursor, selection and old cursor/selection
    -- scenarios:
    -- regular press+release: sets cursor, clears selection
    -- shift press+release:
    -- sets selection to old cursor if not set otherwise leaves it untouched
    -- sets cursor
    -- press and hold to start a selection: sets selection on press, cursor on release
    -- press and hold, then press shift: ignore shift
    -- i.e. mouse_released should never look at shift state
    State.old_cursor1 = State.cursor1
    State.old_selection1 = State.selection1
    State.mousepress_shift = App.shift_down()
  • replacement in source_edit.lua at line 233
    [5.20331][5.20331:20393]()
    State.cursor1 = {line=line_index, pos=pos, posB=posB}
    [5.20331]
    [5.20393]
    State.selection1 = {line=line_index, pos=pos, posB=posB}
    --? print('selection', State.selection1.line, State.selection1.pos, State.selection1.posB)
  • edit in source_edit.lua at line 260
    [5.21189]
    [5.21189]
    else
    for line_index,line in ipairs(State.lines) do
    if line.mode == 'text' then
    if Text.in_line(State, line_index, x,y) then
    --? print('reset selection')
    local pos,posB = Text.to_pos_on_line(State, line_index, x, y)
    State.cursor1 = {line=line_index, pos=pos, posB=posB}
    --? print('cursor', State.cursor1.line, State.cursor1.pos, State.cursor1.posB)
    if State.mousepress_shift then
    if State.old_selection1.line == nil then
    State.selection1 = State.old_cursor1
    else
    State.selection1 = State.old_selection1
    end
    end
    State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
    if eq(State.cursor1, State.selection1) then
    State.selection1 = {}
    end
    break
    end
    end
    end
    --? print('selection:', State.selection1.line, State.selection1.pos)
  • edit in source_edit.lua at line 306
    [5.158209]
    [5.158209]
    if State.selection1.line and
    not State.lines.current_drawing and
    -- printable character created using shift key => delete selection
    -- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)
    (not App.shift_down() or utf8.len(key) == 1) and
    chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and backspace ~= 'delete' and not App.is_cursor_movement(chord) then
    Text.delete_selection(State, State.left, State.right)
    end
  • replacement in source_edit.lua at line 350
    [5.159569][5.159569:159598]()
    elseif chord == 'C-b' then
    [5.159569]
    [5.159598]
    elseif chord == 'M-b' then
  • replacement in source_edit.lua at line 359
    [5.159838][5.21552:21581]()
    elseif chord == 'C-i' then
    [5.159838]
    [5.159867]
    elseif chord == 'M-d' then
  • edit in source_edit.lua at line 392
    [5.161115]
    [5.161115]
    State.selection1 = deepcopy(src.selection)
  • edit in source_edit.lua at line 408
    [5.161688]
    [5.161688]
    State.selection1 = deepcopy(src.selection)
  • replacement in commands.lua at line 27
    [5.204749][5.204749:204807]()
    add_hotkey_to_menu('ctrl+b: collapse debug prints')
    [5.204749]
    [5.204807]
    add_hotkey_to_menu('alt+b: collapse debug prints')
  • replacement in commands.lua at line 29
    [5.204816][5.204816:204872]()
    add_hotkey_to_menu('ctrl+b: expand debug prints')
    [5.204816]
    [5.204872]
    add_hotkey_to_menu('alt+b: expand debug prints')
  • replacement in commands.lua at line 31
    [5.204880][5.113930:113988]()
    add_hotkey_to_menu('ctrl+i: create/edit debug print')
    [5.204880]
    [5.204938]
    add_hotkey_to_menu('alt+d: create/edit debug print')