Too hard to think about selection alongside bifold text at the moment.
6PCE7VUWEE5UVSE3ZZSQAMGUJDDHOBNO2MVD6POUWFP7UY7WOZHAC
WPNRIC7DJZ3VNTE2LUNF5WO2JIVZ4JLZW467C37WEBX7GI7IJVCQC
AIP57TM4LFTWNVGTPJQXKAIY3H5CJWNG5LMK6ZH7LW7CICVP5O2AC
AZQMREI6PRUAJKPIIZRLSVC4YJZ544TSIQ5OFHRRXVE3IMIYFMAQC
NHNP76LGNIVNIDMSDILAKEVSWFQ4LKNCYXVQEGKKJ75TSRPEBVEQC
FLX77WI6PVTKLACN2LDU4RGX5CHM5YGOE4SVQQMAKGJTBYXD7F7QC
J46IUYKDSYY3G6LOTAWNH2AC5DQOJPHYOIXTPSLHGMECQANNRZDAC
EKKFWP4D2MNOHU265UCJU37KIFQV424CRLVASQMHDYUYY5T67D3QC
LF7BWEG4DKQI7NMXMZC4LC2BE5PB42HK5PD6OYBNIDMAZBJASOKQC
LXTTOB33N2HCUZFIUDRQGGBVHK2HODRG4NBLH6RXRQZDCHF27BSAC
5BMR5HRT7GN5L4XB4ISP4JJP3ONZESHEEQBCTQE4EVEDL7MBSDGAC
356GY7IQ467QQMIPFMEETHTXLSZE65HA36PXSOW4KKXBUHSMBQTAC
VSBSWTE4IVQDRXLPQ7VTDIIEBEF7GMGRBHZ2IA73ZR6B2KZWI5JAC
IWYLK45KJSPRXKW55OD4GEPMLTYMMTXNFJJU26JTZN3RE35DWSCQC
EMHRPJ3RAVIVJEQIRXIVDGENV6QHUUGXXRWTJ3BXC7SZNC66VK5QC
4VKEE43Z7MUPNIAOCK36INVBNHRTSWRRN37TIKRPXPH3DRKGHHAQC
M6TH7VSZQGKDB7SFNN5K52WWAX5VTVNT6GOKNKTXPVZBT6NEYDOQC
CNCYMM6ABOXCRI2IP5A4T2OGBO5FQ7GWBXBP2OQYL4YET5BLJCGQC
UHB4GARJI5AB5UCDCZRFSCJNXGJSLU5DYGUGX5ITYEXI7Q43Z4CAC
ILOA5BYFTQKBSHLFMMZUVPQ2JXBFJD62ERQFBTDK2WSRXUN525VQC
LNUHQOGHIOFGJXNGA3DZLYEASLYYDGLN2I3EDZY5ANASQAHCG3YQC
2JLVAYHBQGIYFYLPYP5MC7V3DGTSUKLKTFSAIDG4XZFWVDU33SNQC
APYPFFS3G6TDEUMIHQGMDBJNRNDTCNTPKI5M2AFACJ73P725XQRQC
7XERS4UFFJVY2MOIC5P3NOOE7OQYEPT26Z6G45XCTSV72RROV6TAC
7EQLPB3O4DPUWGILY4P5D32SSIKL63QWWU5XRL2HISGNJXFWD2SAC
MUJTM6REGQAK3LZTIFWGJRXE2UPCM4HSLXQYSF5ITLXLS6JCVPMQC
VG75U7IM2ZQTGM2QETDT6QQ4CSLQPB4APK436POAAQJWOMINPIJAC
MD3W5IRAC6UQALQE4LJC52VQNDO3I3HXF3XE2XHDABXBYJBUVAXQC
CIQN2MDEMWAASJAHOHMUZTI5PF4JV5SZSOBYYDCIIFYO2VHWULKAC
4WAFGF4ZMUQOLBWRZ2SI6RWEBKMFNFZQJMPECT25C2VPYHNDK2JQC
KMRJOSLYYHHPGMYXBSLUQTICP6F4LXRCGYSP55YTZQSX4SZISDEAC
4J2L6JMR7NZBGCNX63CL2E3AIB7P7QTCC7QQBPNAEPQ7ISQXL7EQC
IFTYOERMW7P3I24WISZN35X3GWJ5MSMRYDRBK3L52GCZTPP3CWZQC
ZLJGZYQGQ2S4UFWTVF4PQDSGMP6A4IS4GDHCMBAAA5SK2N2NWR3QC
WAR3HXHTN7JZVV6TFMU2F3QYAG6NDH7DN7KKPTM2ICEHRNQYP6PAC
JFFUF5ALUWPDM7IEDEZVAYG2SVXO334STONRGKVB3QKY2TT5QGBQC
DRFE3B3ZKRG4RY2R5Q3SDFD3LH4EXUX3CZCDFBNAXVI2SLDS57PAC
LAW2O3NWVFTPBSKIMIXPAGYBDOCHYJNKCAVWKNKH62G42DIKZCYQC
3TCZ7ADHZ4YALUYII4QRSITV2VUKN645P7D7XTXD7ASFZTAP7THAC
RMKMPFT5L67WIFWIO4GTC6XESX6UPKNL4GPNQLOBC5CXSUZABEHQC
T3B4NLV33PBD2L3YL3MHSOXZUWHDOGHPWLKKKHEBKJFSHYQWUK3AC
S2YQBEYCOBS4ADO5VX4YLAWY6CJEQOOZM3THYTDOTXM7ADID6PGQC
AOIRVVJARCGTWTRE5MAAU4YQAGD5J4HTR7XCS63UFAUY3A43L6NQC
FZBXBUFFNRE5ZJO5DLRU375HOXT2B7FO35XD7BTHHUXSARVWDFLQC
BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC
537TQ2QNPKPG322I4OIMN5IY22S45Z42LEBBZ2IN5MVM355BEJTAC
WLHI7KD3LJTQH6V7RLVJWGZUR4YQK6LN4OIUMIN45BGMMQGN6RNQC
JY4VK7L2JKRWRV45QEMGLWPFAQRUWKFHMAL6DWNYEDCKO5Y4W5FQC
HALS7E5UGKCP3DFY456F7Z3Y6WNGIABOCV2SHT34D5ZAGNCPV5PQC
PR4KIAZDOBQMEUOV2G7ZEZUW3E4L5ZCHYSS7PTYWGXPSNVRAGHCAC
X3F7ECSLGXCH6NBSDIH7LY47I4EG2RR5VFPEMM6ZVDYQIGFID4HQC
MXA3RZYKUI4UF2ISY7JEF6VKX6NOPZMZH5SLLCZHRJKFIXXXDPSAC
S2MISTTMPEULTO6WRO4Q4NRUO7XC2PTZW3UBR7K7SO6JPZO6HBHAC
SPSW74Y5OJ54Y7VQ3SJFCJR5CYDKTR4A3TOEVZODDZLUSDDU2GZAC
CG3264MMJTTSCJWUA2EMTBOPTDB2NZIJ7XICKHWUTZ4UWLFP7POAC
Z5HLXU4PJWWJJDBCK52NBD6PIRIA3TAN2BKZB5HBYFGIDBX4F5HAC
GN3IF4WF352YK5K4YHVMAIMPL7PNTCEMDWW22PTKDOXKV2FZJ7NQC
XNFTJHC4QSHNSIWNN7K6QZEZ37GTQYKHS4EPNSVPQCUSWREROGIQC
PTDO2SOTXEI6FROZ2AVRFXSKKNKCRMPPTQSI5LWD45UVGDJPMSGQC
PX7DDEMOBGPVK3FXKK5XEPG24CJXZSVW67DLG2JZZ5E77NVEAA3AC
DHI6IJCNSTHGED67T6H5X6Y636C7PIDGIJD32HBEKLT5WIMRS5MAC
SQLVYKVJ5O4UMKTT56LMFPDQX66SZJJ7FZSFEN5MTWPXXWL7X3WQC
73OCE2MCBJJZZMN2KYPJTBOUCKBZAOQ2QIAMTGCNOOJ2AJAXFT2AC
VJ77YABHVJZWJKLHAGIPC562GYM73AUGRLCP4JLKP5JPWPT2RIHAC
HTWAM4NZFOY463TNSKYIM2EWB7QNBGDRRTTGHF5N3Z4TGC7Q3SFAC
QCPXQ2E3USF3Z6R6WJ2JKHTRMPKA6QWXFKKRMLXA3MXABJEL543AC
4CXVIEBSQ5X62UYNJNSNMYKP24GE4IPO77T5ZWQW24QIK2BUQGWAC
YR4CXXEMRSWRAO4WUKWIYUKQ3JRYDK5YMFOLW6GFP2A6HCCTMXFAC
HIKLULFQG7Q7L4C5KXR3DV3TBZ2RGWXBJJXIGSE5YQWF37AJOYZAC
FHSZYAZ2KCHJM4BN2TAPYZMWTLTIE23SWKDYLCQOQIVL4263HDRQC
OCA2NNDNXAHZGHO3TDQVLJSXKYPHMOORTKSOSSWUX3XSTULSDIRQC
OP643FFG5WQWHLPLYZ2VTDJYXK6VQ3NODRDPJNVDN26CF3ESM5RAC
MTJEVRJR5GLWUSK7HMIM4UXM6GS6O6YCRWJT3DUSU2RYMHCQNOEQC
2TQR4PSY2FBIKEEKC2Y5ZPVPOD2QJ3EATII47PPWNMTAQA7EQ6GAC
WLJCIXYMSTCNSYCFOEBQNDLBZ5D2Z3WTF4E4WYL5CFGIJ434FKNQC
LSYLEVBDBZBGLSCXTRBW46WT4TUMMSPCH7M6HSNYI5SIH2WNPYEAC
MP2TBKU6CNDMZKENYMBV62F5KQ27ZWEVPVRFS2RESVDQQT2IRR4AC
TGHAJBESCIEGWUE2D3FGLNOIAYT4D2IRGZKRXRMTUFW7QZETC7OAC
O7QH4N4WVIN644DOEQ3OKCVGKPRAV3QUBAAO3L7XDXTK6TIRGCCQC
2L5MEZV344TOZLVY3432RHJFIRVXFD6O3GWLL5O4CV66BGAFTURQC
5PRIAZWT3XQZNBRJSDB2IQ3ADGL6KBSBS7QQ6XMGA553VPYFLTPQC
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')
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
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')
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
-- 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
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 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
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)
if State.selection1.line then
Text.delete_selection(State, State.left, State.right)
schedule_save(State)
return
end
if State.selection1.line then
Text.delete_selection(State, State.left, State.right)
schedule_save(State)
return
end
-- 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,minpB = State.selection1.line,State.selection1.pos,State.selection1.posB
local maxl,maxp,maxpB
if App.mouse_down(1) then
maxl,maxp,maxpB = Text.mouse_pos(State)
else
maxl,maxp,maxpB = State.cursor1.line,State.cursor1.pos,State.cursor1.posB
end
if Text.lt1({line=maxl, pos=maxp, posB=maxpB},
{line=minl, pos=minp, posB=minpB}) then
minl,maxl = maxl,minl
minp,maxp = maxp,minp
minpB,maxpB = maxpB,minpB
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
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, State.recent_mouse.posB
end
State.recent_mouse.time = time
local line,pos,posB = Text.to_pos(State, App.mouse_x(), App.mouse_y())
if line then
State.recent_mouse = {line=line, pos=pos, posB=posB}
end
return State.recent_mouse.line, State.recent_mouse.pos, State.recent_mouse.posB
end
function Text.to_pos(State, x,y)
for line_index,line in ipairs(State.lines) do
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
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, 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_cursor_screen_line(State)
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)
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
table.insert(result, State.lines[i].data)
end
table.insert(result, State.lines[maxl].data:sub(1, max_offset-1))
return table.concat(result, '\n')
end
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 = {},
-- 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()
if State.search_term then return end
--? print('release')
for line_index,line in ipairs(State.lines) do
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
--? print('selection:', State.selection1.line, State.selection1.pos, State.selection1.posB)
if State.selection1.line 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