support selections in the source editor

[?]
Sep 6, 2022, 5:05 PM
KMSL74GAMFNTAKGDKZFP2AMQXUMOC3XH373BO4IABZWBEP3YAXKAC

Dependencies

  • [2] OI4FPFIN support drawings in the source editor
  • [3] TSK2OXU2 .
  • [4] ODLKHO7B switch to line index in a function
  • [5] MXA3RZYK deduce left/right from state where possible
  • [6] WQOSZSUE warn on unused commandline args
  • [7] QCPXQ2E3 add state arg to a few functions
  • [8] KKMFQDR4 editing source code from within the app
  • [*] BULPIBEG beginnings of a module for the text editor
  • [*] R5QXEHUI somebody stop me
  • [*] OTIBCAUJ love2d scaffold

Change contents

  • edit in text.lua at line 934
    [3.3710][3.5037:5077](),[3.5037][3.5037:5077]()
    --? print('a', State.selection1.line)
  • edit in source_text_tests.lua at line 285
    [3.13887]
    [3.13887]
    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
    [3.14700]
    [3.14700]
    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
    [3.15604]
    [3.15604]
    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
    [3.19208]
    [3.19208]
    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
    [3.20198]
    [3.20198]
    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
    [3.27850]
    [3.27850]
    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
    [3.27855]
    [3.27855]
    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
    [3.32776]
    [3.32776]
    Editor_state.selection1 = {}
  • replacement in source_text_tests.lua at line 870
    [3.32871][3.32871:32957]()
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    [3.32871]
    [3.32957]
    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
    [3.33133]
    [3.33133]
    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
    [3.33138]
    [3.33138]
    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
    [3.73266][3.73266:73379]()
    edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height*2+5, 1)
    [3.73266]
    [3.73379]
    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
    [3.77283]
    [3.77283]
    -- 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
    [3.77924]
    [3.77924]
    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
    [3.78471]
    [3.78471]
    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
    [3.79393]
    [3.79393]
    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
    [3.79991]
    [3.79991]
    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
    [3.80291]
    [3.80291]
    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
    [3.88179]
    [3.88179]
    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
    [3.90125]
    [3.90125]
    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
    [3.98781][3.98781:98809]()
    --? print('chord', chord)
    [3.98781]
    [3.98809]
    --? print('chord', chord, State.selection1.line, State.selection1.pos)
  • edit in source_text.lua at line 392
    [3.98992]
    [3.98992]
    State.selection1 = {}
  • edit in source_text.lua at line 410
    [3.100077]
    [3.100077]
    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
    [3.103057]
    [3.103057]
    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
    [3.105544]
    [3.105544]
    State.selection1 = {}
  • edit in source_text.lua at line 529
    [3.105597]
    [3.105597]
    State.selection1 = {}
  • edit in source_text.lua at line 531
    [3.105629]
    [3.105629]
    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
    [3.105683]
    [3.105683]
    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
    [3.105818]
    [3.105818]
    State.selection1 = {}
  • edit in source_text.lua at line 546
    [3.105878]
    [3.105878]
    State.selection1 = {}
  • edit in source_text.lua at line 548
    [3.105912]
    [3.105912]
    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
    [3.105973]
    [3.105973]
    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
    [3.106060]
    [3.106060]
    State.selection1 = {}
  • edit in source_text.lua at line 562
    [3.106117]
    [3.106117]
    State.selection1 = {}
  • edit in source_text.lua at line 564
    [3.106149]
    [3.106149]
    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
    [3.106210]
    [3.106210]
    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
    [3.106285]
    [3.106285]
    State.selection1 = {}
  • edit in source_text.lua at line 578
    [3.106336]
    [3.106336]
    State.selection1 = {}
  • edit in source_text.lua at line 580
    [3.106366]
    [3.106366]
    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
    [3.106417]
    [3.106417]
    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
    [3.106493]
    [3.106493]
    State.selection1 = {}
  • edit in source_text.lua at line 594
    [3.106552]
    [3.106552]
    State.selection1 = {}
  • edit in source_text.lua at line 596
    [3.106586]
    [3.106586]
    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
    [3.106645]
    [3.106645]
    if State.selection1.line == nil then
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    end
  • file addition: source_select.lua (----------)
    [11.2]
    -- helpers for selecting portions of text
    -- To keep things simple, we'll ignore the B side when selections start on the
    -- A side, and stick to within a single B side selections start in.
    -- Return any intersection of the region from State.selection1 to State.cursor1 (or
    -- current mouse, if mouse is pressed; or recent mouse if mouse is pressed and
    -- currently over a drawing) with the region between {line=line_index, pos=apos}
    -- and {line=line_index, pos=bpos}.
    -- apos must be less than bpos. However State.selection1 and State.cursor1 can be in any order.
    -- Result: positions spos,epos between apos,bpos.
    function Text.clip_selection(State, line_index, apos, bpos)
    if State.selection1.line == nil then return nil,nil end
    -- min,max = sorted(State.selection1,State.cursor1)
    local minl,minp = State.selection1.line,State.selection1.pos
    local maxl,maxp
    if App.mouse_down(1) then
    maxl,maxp = Text.mouse_pos(State)
    else
    maxl,maxp = State.cursor1.line,State.cursor1.pos
    end
    if Text.lt1({line=maxl, pos=maxp},
    {line=minl, pos=minp}) then
    minl,maxl = maxl,minl
    minp,maxp = maxp,minp
    end
    -- check if intervals are disjoint
    if line_index < minl then return nil,nil end
    if line_index > maxl then return nil,nil end
    if line_index == minl and bpos <= minp then return nil,nil end
    if line_index == maxl and apos >= maxp then return nil,nil end
    -- compare bounds more carefully (start inclusive, end exclusive)
    local a_ge = Text.le1({line=minl, pos=minp}, {line=line_index, pos=apos})
    local b_lt = Text.lt1({line=line_index, pos=bpos}, {line=maxl, pos=maxp})
    --? print(minl,line_index,maxl, '--', minp,apos,bpos,maxp, '--', a_ge,b_lt)
    if a_ge and b_lt then
    -- fully contained
    return apos,bpos
    elseif a_ge then
    assert(maxl == line_index)
    return apos,maxp
    elseif b_lt then
    assert(minl == line_index)
    return minp,bpos
    else
    assert(minl == maxl and minl == line_index)
    return minp,maxp
    end
    end
    -- draw highlight for line corresponding to (lo,hi) given an approximate x,y and pos on the same screen line
    -- Creates text objects every time, so use this sparingly.
    -- Returns some intermediate computation useful elsewhere.
    function Text.draw_highlight(State, line, x,y, pos, lo,hi)
    if lo then
    local lo_offset = Text.offset(line.data, lo)
    local hi_offset = Text.offset(line.data, hi)
    local pos_offset = Text.offset(line.data, pos)
    local lo_px
    if pos == lo then
    lo_px = 0
    else
    local before = line.data:sub(pos_offset, lo_offset-1)
    local before_text = App.newText(love.graphics.getFont(), before)
    lo_px = App.width(before_text)
    end
    --? print(lo,pos,hi, '--', lo_offset,pos_offset,hi_offset, '--', lo_px)
    local s = line.data:sub(lo_offset, hi_offset-1)
    local text = App.newText(love.graphics.getFont(), s)
    local text_width = App.width(text)
    App.color(Highlight_color)
    love.graphics.rectangle('fill', x+lo_px,y, text_width,State.line_height)
    App.color(Text_color)
    return lo_px
    end
    end
    -- inefficient for some reason, so don't do it on every frame
    function Text.mouse_pos(State)
    local time = love.timer.getTime()
    if State.recent_mouse.time and State.recent_mouse.time > time-0.1 then
    return State.recent_mouse.line, State.recent_mouse.pos
    end
    State.recent_mouse.time = time
    local line,pos = Text.to_pos(State, App.mouse_x(), App.mouse_y())
    if line then
    State.recent_mouse.line = line
    State.recent_mouse.pos = pos
    end
    return State.recent_mouse.line, State.recent_mouse.pos
    end
    function Text.to_pos(State, x,y)
    for line_index,line in ipairs(State.lines) do
    if line.mode == 'text' then
    if Text.in_line(State, line_index, x,y) then
    return line_index, Text.to_pos_on_line(State, line_index, x,y)
    end
    end
    end
    end
    function Text.cut_selection(State)
    if State.selection1.line == nil then return end
    local result = Text.selection(State)
    Text.delete_selection(State)
    return result
    end
    function Text.delete_selection(State)
    if State.selection1.line == nil then return end
    local minl,maxl = minmax(State.selection1.line, State.cursor1.line)
    local before = snapshot(State, minl, maxl)
    Text.delete_selection_without_undo(State)
    record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
    end
    function Text.delete_selection_without_undo(State)
    if State.selection1.line == nil then return end
    -- min,max = sorted(State.selection1,State.cursor1)
    local minl,minp = State.selection1.line,State.selection1.pos
    local maxl,maxp = State.cursor1.line,State.cursor1.pos
    if minl > maxl then
    minl,maxl = maxl,minl
    minp,maxp = maxp,minp
    elseif minl == maxl then
    if minp > maxp then
    minp,maxp = maxp,minp
    end
    end
    -- update State.cursor1 and State.selection1
    State.cursor1.line = minl
    State.cursor1.pos = minp
    if Text.lt1(State.cursor1, State.screen_top1) then
    State.screen_top1.line = State.cursor1.line
    State.screen_top1.pos = Text.pos_at_start_of_screen_line(State, State.cursor1)
    end
    State.selection1 = {}
    -- delete everything between min (inclusive) and max (exclusive)
    Text.clear_screen_line_cache(State, minl)
    local min_offset = Text.offset(State.lines[minl].data, minp)
    local max_offset = Text.offset(State.lines[maxl].data, maxp)
    if minl == maxl then
    --? print('minl == maxl')
    State.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..State.lines[minl].data:sub(max_offset)
    return
    end
    assert(minl < maxl)
    local rhs = State.lines[maxl].data:sub(max_offset)
    for i=maxl,minl+1,-1 do
    table.remove(State.lines, i)
    table.remove(State.line_cache, i)
    end
    State.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..rhs
    end
    function Text.selection(State)
    if State.selection1.line == nil then return end
    -- min,max = sorted(State.selection1,State.cursor1)
    local minl,minp = State.selection1.line,State.selection1.pos
    local maxl,maxp = State.cursor1.line,State.cursor1.pos
    if minl > maxl then
    minl,maxl = maxl,minl
    minp,maxp = maxp,minp
    elseif minl == maxl then
    if minp > maxp then
    minp,maxp = maxp,minp
    end
    end
    local min_offset = Text.offset(State.lines[minl].data, minp)
    local max_offset = Text.offset(State.lines[maxl].data, maxp)
    if minl == maxl then
    return State.lines[minl].data:sub(min_offset, max_offset-1)
    end
    assert(minl < maxl)
    local result = {State.lines[minl].data:sub(min_offset)}
    for i=minl+1,maxl-1 do
    if State.lines[i].mode == 'text' then
    table.insert(result, State.lines[i].data)
    end
    end
    table.insert(result, State.lines[maxl].data:sub(1, max_offset-1))
    return table.concat(result, '\n')
    end
  • edit in source_edit.lua at line 78
    [3.154730]
    [3.154730]
    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
    [2.21646]
    [2.21646]
    -- 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
    [2.21781][2.21781:21843]()
    State.cursor1 = {line=line_index, pos=pos, posB=posB}
    [2.21781]
    [2.21843]
    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
    [2.22639]
    [2.22639]
    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
    [3.158146]
    [3.158146]
    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
  • edit in source_edit.lua at line 392
    [3.161052]
    [3.161052]
    State.selection1 = deepcopy(src.selection)
  • edit in source_edit.lua at line 408
    [3.161625]
    [3.161625]
    State.selection1 = deepcopy(src.selection)
  • replacement in main.lua at line 63
    [3.186396][3.186396:186458]()
    load_file_from_source_or_save_directory('select.lua')
    [3.186396]
    [3.186458]
    load_file_from_source_or_save_directory('source_select.lua')