I've written a few tests for delete_selection, but the way different operations initialize the selection seems fairly standard and not worth testing so far.
DHI6IJCNSTHGED67T6H5X6Y636C7PIDGIJD32HBEKLT5WIMRS5MAC 2ZRC7FULL5TSP77QJ4M3MOWPLYHM2MWGCDH6RT5FST6G3L5ZVFXQC KOYAJWE4NJ2J4X3SHEAVMRXYZPZGOMTI7OX3PTUQIDIZ2GQI6UKAC AYE2VEGJ63AWWX76SFQZLOTBIZOQRWBG4AZMIOSVOI2WZVRQJXYAC QYIFOHW3WDDQMK4ATY6IOSQRFHJOQ5QCPDKRC4GVGWLQEH4HGWVQC BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC OYXDYPGSJK2QICJ6RBA7357WT4FSNAWRUT77YLQHT3F3VYMWGNFQC H2DPLWMVRFYTO2CQTG54FMT2LF3B6UKLXH32CUA22DNQJVP5XBNQC 5DOC2CBMBDMAOJ7IKLDGVRCY4SNPCJTTF7DK7WGNLPGNV4AWVJNAC HMODUNJEQLZ3W46GKYIDL55F6COVXHTIC6UW4AK3SXOOKOPE6NNAC WY3JD6W6EANKQC4SRRIAM2Q3QZNVOGN3MIMBL3M55S4ZZSJFSF2AC PFT5Y2ZYGQA6XXOZ5HH75WVUGA4B3KTDRHSFOZRAUKTPSFOPMNRAC XNFTJHC4QSHNSIWNN7K6QZEZ37GTQYKHS4EPNSVPQCUSWREROGIQC CG3264MMJTTSCJWUA2EMTBOPTDB2NZIJ7XICKHWUTZ4UWLFP7POAC 2RXZ3PGOTTZ6M4R372JXIKPLBQKPVBMAXNPIEO2HZDN4EMYW4GNAC ZPUQSPQPQFVRUIHGLAWW3IDBYODIWDHO62HAC3WWF5TM3CIJGHNQC AMSESRTH4T7EIEMXEFPMZFC55QAOVSWAN2XOQUUEB5ECHRDZUAYQC AVTNUQYRBW7IX2YQ3KDLVQ23RGW3BAKTAE7P73ASBYNKOHMQMH5AC OIB2QPRCB4MAVZV5NCEKSAL45ITT6V4BYSET3Q2VCT3WBOIC4QVQC OTIBCAUJ3KDQJLVDN3A536DLZGNRYMGJLORZVR3WLCGXGO6UGO6AC HYEAFRZ2UEKDYTAE2GDQLHEJBPQASP2NDLMXB7F6MTVK2BKOXKEAC BOFNXP5GZDCUMQG3LQVTSSFEQP7REQ4RIRJLDLETFSAGFTVDVEKAC if Selection1.line thenlocal lo, hi = Text.clip_selection(line_index, pos, pos+frag_len)if lo thenlocal lo_offset = utf8.offset(line.data, lo)local hi_offset = utf8.offset(line.data, hi)local pos_offset = utf8.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)*Zoomend--? 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)*Zoomlove.graphics.setColor(0.7,0.7,0.9)love.graphics.rectangle('fill', x+lo_px,y, text_width,math.floor(15*Zoom))love.graphics.setColor(0,0,0)endend
-- short words break on spaces-- long words break when they must
-- Return any intersection of the region from Selection1 to Cursor1 with the-- region between {line=line_index, pos=apos} and {line=line_index, pos=bpos}.-- apos must be less than bpos. However Selection1 and Cursor1 can be in any order.-- Result: positions spos,epos between apos,bpos.function Text.clip_selection(line_index, apos, bpos)if Selection1.line == nil then return nil,nil end-- min,max = sorted(Selection1,Cursor1)local minl,minp = Selection1.line,Selection1.poslocal maxl,maxp = Cursor1.line,Cursor1.posif minl > maxl thenminl,maxl = maxl,minlminp,maxp = maxp,minpelseif minl == maxl thenif minp > maxp thenminp,maxp = maxp,minpendend-- 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,maxpendendfunction Text.delete_selection()if Selection1.line == nil then return end-- min,max = sorted(Selection1,Cursor1)local minl,minp = Selection1.line,Selection1.poslocal maxl,maxp = Cursor1.line,Cursor1.posif minl > maxl thenminl,maxl = maxl,minlminp,maxp = maxp,minpelseif minl == maxl thenif minp > maxp thenminp,maxp = maxp,minpendend-- update Cursor1 and Selection1Cursor1.line = minlCursor1.pos = minpSelection1 = {}-- delete everything between min (inclusive) and max (exclusive)Lines[minl].fragments = nilLines[minl].screen_line_starting_pos = nillocal min_offset = utf8.offset(Lines[minl].data, minp)local max_offset = utf8.offset(Lines[maxl].data, maxp)if minl == maxl then--? print('minl == maxl')Lines[minl].data = Lines[minl].data:sub(1, min_offset-1)..Lines[minl].data:sub(max_offset)returnendassert(minl < maxl)local rhs = Lines[maxl].data:sub(max_offset)for i=maxl,minl+1,-1 dotable.remove(Lines, i)endLines[minl].data = Lines[minl].data:sub(1, min_offset-1)..rhsend
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=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Selection1 = {line=1, pos=2}Zoom = 1-- backspace deletes the selected character, even though it's after the cursorApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'bc', "F - test_backspace_over_selection/data")-- cursor (remains) at start of selectioncheck_eq(Cursor1.line, 1, "F - test_backspace_over_selection/cursor:line")check_eq(Cursor1.pos, 1, "F - test_backspace_over_selection/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_over_selection/selection")
function test_backspace_over_selection_reverse()io.write('\ntest_backspace_over_selection')-- select just one character within a line with cursor after selectionApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Selection1 = {line=1, pos=1}Zoom = 1-- backspace deletes the selected characterApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'bc', "F - test_backspace_over_selection_reverse/data")-- cursor moves to start of selectioncheck_eq(Cursor1.line, 1, "F - test_backspace_over_selection_reverse/cursor:line")check_eq(Cursor1.pos, 1, "F - test_backspace_over_selection_reverse/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_over_selection_reverse/selection")endfunction test_backspace_over_multiple_lines()io.write('\ntest_backspace_over_selection')-- select just one character within a line with cursor after selectionApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Selection1 = {line=4, pos=2}Zoom = 1-- backspace deletes the region and joins the remaining portions of lines on either sideApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'akl', "F - test_backspace_over_multiple_lines/data:1")check_eq(Lines[2].data, 'mno', "F - test_backspace_over_multiple_lines/data:2")-- cursor remains at start of selectioncheck_eq(Cursor1.line, 1, "F - test_backspace_over_multiple_lines/cursor:line")check_eq(Cursor1.pos, 2, "F - test_backspace_over_multiple_lines/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_over_multiple_lines/selection")endfunction test_backspace_to_end_of_line()io.write('\ntest_backspace_over_selection')-- select region from cursor to end of lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Selection1 = {line=1, pos=4}Zoom = 1-- backspace deletes rest of line without joining to any other lineApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'a', "F - test_backspace_to_start_of_line/data:1")check_eq(Lines[2].data, 'def', "F - test_backspace_to_start_of_line/data:2")-- cursor remains at start of selectioncheck_eq(Cursor1.line, 1, "F - test_backspace_to_start_of_line/cursor:line")check_eq(Cursor1.pos, 2, "F - test_backspace_to_start_of_line/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_to_start_of_line/selection")endfunction test_backspace_to_start_of_line()io.write('\ntest_backspace_over_selection')-- select region from cursor to start of lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=2, pos=1}Selection1 = {line=2, pos=3}Zoom = 1-- backspace deletes beginning of line without joining to any other lineApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'abc', "F - test_backspace_to_start_of_line/data:1")check_eq(Lines[2].data, 'f', "F - test_backspace_to_start_of_line/data:2")-- cursor remains at start of selectioncheck_eq(Cursor1.line, 2, "F - test_backspace_to_start_of_line/cursor:line")check_eq(Cursor1.pos, 1, "F - test_backspace_to_start_of_line/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_to_start_of_line/selection")end
elseif chord == 'S-home' thenif Selection1.line == nil thenSelection1 = {line=Cursor1.line, pos=Cursor1.pos}endCursor1.pos = 1elseif chord == 'S-end' thenif Selection1.line == nil thenSelection1 = {line=Cursor1.line, pos=Cursor1.pos}endCursor1.pos = utf8.len(Lines[Cursor1.line].data) + 1
if Selection1.line thenSelection1 = {}endText.down()elseif chord == 'S-up' thenif Selection1.line == nil thenSelection1 = {line=Cursor1.line, pos=Cursor1.pos}endText.up()elseif chord == 'S-down' thenif Selection1.line == nil thenSelection1 = {line=Cursor1.line, pos=Cursor1.pos}end
if Selection1.line thenSelection1 = {}endText.pagedown()elseif chord == 'S-pageup' thenif Selection1.line == nil thenSelection1 = {line=Cursor1.line, pos=Cursor1.pos}endText.pageup()elseif chord == 'S-pagedown' thenif Selection1.line == nil thenSelection1 = {line=Cursor1.line, pos=Cursor1.pos}end