I've only tested side A so far, and included a statement of how I want side B to behave.
KMSL74GAMFNTAKGDKZFP2AMQXUMOC3XH373BO4IABZWBEP3YAXKAC OI4FPFINEROK6GNDEMOBTGSPYIULCLRGGT5W3H7VLM7VFH22GMWQC MXA3RZYKUI4UF2ISY7JEF6VKX6NOPZMZH5SLLCZHRJKFIXXXDPSAC QCPXQ2E3USF3Z6R6WJ2JKHTRMPKA6QWXFKKRMLXA3MXABJEL543AC BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC KKMFQDR43ZWVCDRHQLWWX3FCWCFA3ZSXYOBRJNPHUQZR2XPKWULAC R5QXEHUIZLELJGGCZAE7ATNS3CLRJ7JFRENMGH4XXH24C5WABZDQC OTIBCAUJ3KDQJLVDN3A536DLZGNRYMGJLORZVR3WLCGXGO6UGO6AC endfunction test_select_text()io.write('\ntest_select_text')-- display a line of textApp.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 letterApp.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 releasedcheck_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')endfunction test_cursor_movement_without_shift_resets_selection()io.write('\ntest_cursor_movement_without_shift_resets_selection')-- display a line of text with some part selectedApp.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 shiftedit.run_after_keychord(Editor_state, 'right')-- no change to data, selection is resetcheck_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')
function test_edit_deletes_selection()io.write('\ntest_edit_deletes_selection')-- display a line of text with some part selectedApp.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 keyedit.run_after_textinput(Editor_state, 'x')-- selected text is deleted and replaced with the keycheck_eq(Editor_state.lines[1].data, 'xbc', 'F - test_edit_deletes_selection')endfunction test_edit_with_shift_key_deletes_selection()io.write('\ntest_edit_with_shift_key_deletes_selection')-- display a line of text with some part selectedApp.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 letterApp.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 keycheck_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')endfunction test_copy_does_not_reset_selection()io.write('\ntest_copy_does_not_reset_selection')-- display a line of text with a selectionApp.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 selectionedit.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 pressedcheck(Editor_state.selection1.line, 'F - test_copy_does_not_reset_selection')endfunction test_cut()io.write('\ntest_cut')-- display a line of text with some part selectedApp.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 keyedit.run_after_keychord(Editor_state, 'C-x')check_eq(App.clipboard, 'a', 'F - test_cut/clipboard')-- selected text is deletedcheck_eq(Editor_state.lines[1].data, 'bc', 'F - test_cut/data')endfunction test_paste_replaces_selection()io.write('\ntest_paste_replaces_selection')-- display a line of text with a selectionApp.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 clipboardApp.clipboard = 'xyz'-- paste selectionedit.run_after_keychord(Editor_state, 'C-v')-- selection is reset since shift key is not pressed-- selection includes the newline, so it's also deletedcheck_eq(Editor_state.lines[1].data, 'xyzdef', 'F - test_paste_replaces_selection')endfunction test_deleting_selection_may_scroll()io.write('\ntest_deleting_selection_may_scroll')-- display lines 2/3/4App.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.topApp.screen.check(y, 'def', 'F - test_deleting_selection_may_scroll/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_deleting_selection_may_scroll/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'F - test_deleting_selection_may_scroll/baseline/screen:3')-- set up a selection starting above the currently displayed pageEditor_state.selection1 = {line=1, pos=2}-- delete selectionedit.run_after_keychord(Editor_state, 'backspace')-- page scrolls upcheck_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
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')endfunction 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 locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- drag and release somewhere elseedit.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')endfunction 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 locationedit.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 elseApp.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')endfunction 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 locationedit.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 locationApp.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 locationApp.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')
function test_cut_without_selection()io.write('\ntest_cut_without_selection')-- display a few linesApp.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 textedit.run_after_keychord(Editor_state, 'C-x')-- no crashcheck_nil(Editor_state.selection1.line, 'F - test_cut_without_selection')end
-- some tests for operating over selections created using Shift- chords-- we're just testing delete_selection, and it works the same for all keysfunction test_backspace_over_selection()io.write('\ntest_backspace_over_selection')-- select just one character within a line with cursor before selectionApp.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 cursoredit.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 selectioncheck_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 clearedcheck_nil(Editor_state.selection1.line, "F - test_backspace_over_selection/selection")endfunction test_backspace_over_selection_reverse()io.write('\ntest_backspace_over_selection_reverse')-- select just one character within a line with cursor after selectionApp.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 characteredit.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 selectioncheck_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 clearedcheck_nil(Editor_state.selection1.line, "F - test_backspace_over_selection_reverse/selection")endfunction test_backspace_over_multiple_lines()io.write('\ntest_backspace_over_multiple_lines')-- select just one character within a line with cursor after selectionApp.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 sideedit.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 selectioncheck_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 clearedcheck_nil(Editor_state.selection1.line, "F - test_backspace_over_multiple_lines/selection")endfunction test_backspace_to_end_of_line()io.write('\ntest_backspace_to_end_of_line')-- select region from cursor to end of lineApp.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 lineedit.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 selectioncheck_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 clearedcheck_nil(Editor_state.selection1.line, "F - test_backspace_to_start_of_line/selection")endfunction test_backspace_to_start_of_line()io.write('\ntest_backspace_to_start_of_line')-- select region from cursor to start of lineApp.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 lineedit.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 selectioncheck_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 clearedcheck_nil(Editor_state.selection1.line, "F - test_backspace_to_start_of_line/selection")end
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')
function test_undo_restores_selection()io.write('\ntest_undo_restores_selection')-- display a line of text with some part selectedApp.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 textedit.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')-- undoedit.run_after_keychord(Editor_state, 'C-z')edit.run_after_keychord(Editor_state, 'C-z')-- selection is restoredcheck_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
-- 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.poslocal maxl,maxpif App.mouse_down(1) thenmaxl,maxp = Text.mouse_pos(State)elsemaxl,maxp = State.cursor1.line,State.cursor1.posendif Text.lt1({line=maxl, pos=maxp},{line=minl, pos=minp}) thenminl,maxl = maxl,minlminp,maxp = maxp,minpend-- check if intervals are disjointif line_index < minl then return nil,nil endif line_index > maxl then return nil,nil endif line_index == minl and bpos <= minp then return nil,nil endif 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 containedreturn apos,bposelseif a_ge thenassert(maxl == line_index)return apos,maxpelseif b_lt thenassert(minl == line_index)return minp,bposelseassert(minl == maxl and minl == line_index)return minp,maxpendend-- 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 thenlocal 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_pxif pos == lo thenlo_px = 0elselocal 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_pxendend-- inefficient for some reason, so don't do it on every framefunction Text.mouse_pos(State)local time = love.timer.getTime()if State.recent_mouse.time and State.recent_mouse.time > time-0.1 thenreturn State.recent_mouse.line, State.recent_mouse.posendState.recent_mouse.time = timelocal line,pos = Text.to_pos(State, App.mouse_x(), App.mouse_y())if line thenState.recent_mouse.line = lineState.recent_mouse.pos = posendreturn State.recent_mouse.line, State.recent_mouse.posendfunction Text.to_pos(State, x,y)for line_index,line in ipairs(State.lines) doif line.mode == 'text' thenif Text.in_line(State, line_index, x,y) thenreturn line_index, Text.to_pos_on_line(State, line_index, x,y)endendendendfunction Text.cut_selection(State)if State.selection1.line == nil then return endlocal result = Text.selection(State)Text.delete_selection(State)return resultendfunction Text.delete_selection(State)if State.selection1.line == nil then return endlocal 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)})endfunction 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.poslocal maxl,maxp = State.cursor1.line,State.cursor1.posif minl > maxl thenminl,maxl = maxl,minlminp,maxp = maxp,minpelseif minl == maxl thenif minp > maxp thenminp,maxp = maxp,minpendend-- update State.cursor1 and State.selection1State.cursor1.line = minlState.cursor1.pos = minpif Text.lt1(State.cursor1, State.screen_top1) thenState.screen_top1.line = State.cursor1.lineState.screen_top1.pos = Text.pos_at_start_of_screen_line(State, State.cursor1)endState.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)returnendassert(minl < maxl)local rhs = State.lines[maxl].data:sub(max_offset)for i=maxl,minl+1,-1 dotable.remove(State.lines, i)table.remove(State.line_cache, i)endState.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..rhsendfunction 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.poslocal maxl,maxp = State.cursor1.line,State.cursor1.posif minl > maxl thenminl,maxl = maxl,minlminp,maxp = maxp,minpelseif minl == maxl thenif minp > maxp thenminp,maxp = maxp,minpendendlocal min_offset = Text.offset(State.lines[minl].data, minp)local max_offset = Text.offset(State.lines[maxl].data, maxp)if minl == maxl thenreturn State.lines[minl].data:sub(min_offset, max_offset-1)endassert(minl < maxl)local result = {State.lines[minl].data:sub(min_offset)}for i=minl+1,maxl-1 doif State.lines[i].mode == 'text' thentable.insert(result, State.lines[i].data)endendtable.insert(result, State.lines[maxl].data:sub(1, max_offset-1))return table.concat(result, '\n')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 stateState.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()
elsefor line_index,line in ipairs(State.lines) doif line.mode == 'text' thenif 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 thenif State.old_selection1.line == nil thenState.selection1 = State.old_cursor1elseState.selection1 = State.old_selection1endendState.old_cursor1, State.old_selection1, State.mousepress_shift = nilif eq(State.cursor1, State.selection1) thenState.selection1 = {}endbreakendendend--? print('selection:', State.selection1.line, State.selection1.pos)
if State.selection1.line andnot 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) andchord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and backspace ~= 'delete' and not App.is_cursor_movement(chord) thenText.delete_selection(State, State.left, State.right)end