GG2553RBUKMCWIAD3EWJV7C2QH2BIS5V2E3BDTFSTYRHAPXGBF7QC
QTN5YLMJPUZI5QYLIWFBB3MDZ5G3R33W2R2L4MFLQDAJFNDHWAFAC
2ZIMFVM5HXKX7RAG4ECXS5FGY4RR4D2SDZZ7WAW6QHZ7NR6WN6QAC
U4CNVXMQKYY7ZXAPOSLR4GXDTIDEVOCDID5FUW5WMGLPC7FEHBRQC
GDAWPFAVMBKIOLQSK2BYRSEJKB4VDZATZBWL3DYODU6T6DPZJLWQC
HZRO5BV5ZMCJFYAKHFJ5PCQCKCOF4GL6CNVVIQDJTFEU7PHPZRCQC
ORKN6EOBUFVAD2TXYW5OIKSL55RU24LOFDTTTXHDZUZ57QRDCY7QC
R5QXEHUIZLELJGGCZAE7ATNS3CLRJ7JFRENMGH4XXH24C5WABZDQC
BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC
LLQC2M2IMEJBJQXZTKC3OAKG5WKHSERXKAKCYHQRUZZD6CVRIHAQC
3VHUIIATPOF7FXB7NTL5MESCV5BCQACII2D7QZ4UIUCBX3CWXMMAC
XNFTJHC4QSHNSIWNN7K6QZEZ37GTQYKHS4EPNSVPQCUSWREROGIQC
2CK5QI7WA7M4IVSACFGOJYAIDKRUTZVMMPSFWEJTUNMWTN7AX4NAC
SPSW74Y5OJ54Y7VQ3SJFCJR5CYDKTR4A3TOEVZODDZLUSDDU2GZAC
PFT5Y2ZYGQA6XXOZ5HH75WVUGA4B3KTDRHSFOZRAUKTPSFOPMNRAC
DRFE3B3ZKRG4RY2R5Q3SDFD3LH4EXUX3CZCDFBNAXVI2SLDS57PAC
2L5MEZV344TOZLVY3432RHJFIRVXFD6O3GWLL5O4CV66BGAFTURQC
UHB4GARJI5AB5UCDCZRFSCJNXGJSLU5DYGUGX5ITYEXI7Q43Z4CAC
5UKUADTWMNWPOPBBTXUXY7UNFW64DWANI2RQHKSCSZNWHTQM4GUAC
APYPFFS3G6TDEUMIHQGMDBJNRNDTCNTPKI5M2AFACJ73P725XQRQC
KYNGDE2CKNOKUC2XMAS5MEU6YT2C3IW5SIZLOJE64G3ERT7BSWFAC
IFTYOERMW7P3I24WISZN35X3GWJ5MSMRYDRBK3L52GCZTPP3CWZQC
LNUHQOGHIOFGJXNGA3DZLYEASLYYDGLN2I3EDZY5ANASQAHCG3YQC
LXTTOB33N2HCUZFIUDRQGGBVHK2HODRG4NBLH6RXRQZDCHF27BSAC
LF7BWEG4DKQI7NMXMZC4LC2BE5PB42HK5PD6OYBNIDMAZBJASOKQC
P6SYWBLBN2KAYQ6VJBPYZQNCD2WQHOZGC6XOKWW4SLAMFFGH3ZYQC
EKKFWP4D2MNOHU265UCJU37KIFQV424CRLVASQMHDYUYY5T67D3QC
FQZ3U3YATUWJM4L4H3OK4CKXUZ6UWDDG5ZCV3LQWDI2UXY7XGRYAC
AMOPICKVRHMQERJLFPMAAEBV7TL5QACGGSBJWRCMV5R5O3KDVETAC
MSOQI3A5BC5PY2MZXZQAQ4EQDT4KICQJPN3YUZVDYTWXSPZWBLIAC
LLAOOMULEBXFMIGRBY6LRVEK4RXQGPNTFVWMCZNUEJZHWC7UGUEAC
FZBXBUFFNRE5ZJO5DLRU375HOXT2B7FO35XD7BTHHUXSARVWDFLQC
MUJTM6REGQAK3LZTIFWGJRXE2UPCM4HSLXQYSF5ITLXLS6JCVPMQC
S2YQBEYCOBS4ADO5VX4YLAWY6CJEQOOZM3THYTDOTXM7ADID6PGQC
356GY7IQ467QQMIPFMEETHTXLSZE65HA36PXSOW4KKXBUHSMBQTAC
NZKYPBSKYJ7NQU7ABRHLYZ2P2P5V2UF76OLRURGTGRUB54R4SPBQC
QKAMUWSB6GWKEGLXFKALGCIU7HBTZ4YGLIR7TLA6ZZCUK7WNCNUQC
CIQN2MDEMWAASJAHOHMUZTI5PF4JV5SZSOBYYDCIIFYO2VHWULKAC
VG75U7IM2ZQTGM2QETDT6QQ4CSLQPB4APK436POAAQJWOMINPIJAC
EMHRPJ3RAVIVJEQIRXIVDGENV6QHUUGXXRWTJ3BXC7SZNC66VK5QC
H3ECRBXFBASVUPMZYM5APUK6AR3UF2O6I7BF7KQPV3YHBNT6YZWQC
Y2ZIPXEMMCY5GHJDDF7OMRKEQYMSDR5QTJDA7Y2SBOTHAJKHWVOAC
5BMR5HRT7GN5L4XB4ISP4JJP3ONZESHEEQBCTQE4EVEDL7MBSDGAC
ILOA5BYFTQKBSHLFMMZUVPQ2JXBFJD62ERQFBTDK2WSRXUN525VQC
CNCYMM6ABOXCRI2IP5A4T2OGBO5FQ7GWBXBP2OQYL4YET5BLJCGQC
NUZFHX6IUV2KXZOIJQTD5VIU7ELDQCFPDXYBUNQGWLKH3OMYND5QC
7NQCCB34KI7PFWPR6EWLBTHLPHMZK25PVZKHK7HEOZKTKENACQHAC
JOPVPUSAMMU6RFVDQR4NJC4GNNUFB7GPKVH7OS5FKCYS5QZ53VLQC
VBU5YHLRO5ZSKFWBJRX7DWQGWPEHEWZMRRVV2WMWDJ54PKUNYCNQC
ATQO62TFDZ7J4RCOSB3K2QCCB5R6PNYQIIGNXTLZMEFG5UG5PUJQC
5E7BBR7HWGOEJJ6YWPL3NJKZVBTUZWET7OQNCRU2MMNVEGCEIAWQC
VRJ4PC7NCX53QGC3QFK5MJFJ6BYVM6ZEIK3UWBCGDAFLJD3XYELQC
5XMBCKJZ7YCUOOQWWZRNLXMXD3MUYC2VAF3VKA4BIEHQRPDQMUMAC
D4B52CQ2QKG2HQKFUQOO5S2ME325DTW3PH2D7SBXCW4BPQFYG7CAC
3PSFWAILGRA4OYXWS2DX7VF332AIBPYBXHEA4GIQY2XEJVD65UMAC
22K3VJIL4SRPXVAEJSWRS7KM4UPV4SOODPSTSSKPFWKLTYRG365QC
XRP727K3D2OPH2CJHBYQWLMK2QHQB4QTA4KP34JLTM7ETOLBCC3QC
YJE7KP2XEC23N3V2XOP5CPORSHXXRQT2QVQQJALY3VKYRK74FSVAC
FXI74QCLOZ4BS7UVZ3U2PE3LOL7MX3FWGHZCTGH3DYFXGTXVVIRAC
EWQEFSZQN6JAJVENZMUKCUEB25GUE23U6DYSBNFNLB2NVUUDQ7ZQC
DZMHZBYSL73BWS7OX3BGOYCITVIGFKNLUXNQUAGKXMGXDE4ZRL6QC
ANVLB2TX6ODOEUY7OADTQG47W7DNRHD6NXUPIJR5LVSJFMFJ3L2AC
U46N4W3QLSD5F7DNP6VB43CEE3OA2PJFLGNUG3MZ7EMNTFSUIP6AC
QSPYRABWJDUVTRV5HTCOC4FY3DGYPLQU4DJ2UDJLPIIJWMTPOA3AC
OL33UKKSQH4TKDUZMAZFEU3AOOMO5NZWBTZZ6I7VIC73BQPZI26AC
KKMFQDR43ZWVCDRHQLWWX3FCWCFA3ZSXYOBRJNPHUQZR2XPKWULAC
OTIBCAUJ3KDQJLVDN3A536DLZGNRYMGJLORZVR3WLCGXGO6UGO6AC
AVTNUQYRBW7IX2YQ3KDLVQ23RGW3BAKTAE7P73ASBYNKOHMQMH5AC
UOTHQWM74AFOCPGQKKPLZXRI5HQFH3SRGI336AVZRGWPR6P256EAC
4I2LMNEXDHZTOTDKBJXHSZRQDHIBDXQOYJDNGQSZYXRNHZGP7YPAC
KEFZWDCOCLPTLSZJKRV4VYAHRITV5T33YKG2VGT332YAUCOBS3EAC
EMBBTRXDTL6DYNYUHOIZ2BXRCBS3BM6BPPZ4YLJAONG4K7DRSKNAC
KTSXR2MUTVMBEU7BGN5CHXL5UP6PPA6TTHN4CON7T67YWGZB72VAC
4C5277X7DTT6YSUD7QXQJ323CIRU4IERYZGOG43JWCPZ67B4N5AQC
GRSHSSV5ZRYOXD3SGMCOXKVFXLNZYLAN3YVBDUE6IVZBIK5S3UXQC
-- Don't handle any keys here that would trigger text_input above.
function Text.keychord_press(State, chord)
--? print('chord', chord, State.selection1.line, State.selection1.pos)
function Text.text_input(State, t)
if App.mouse_down(1) then return end
left = math.floor(left),
right = math.floor(right),
edit.mouse_release(State, x,y, mouse_button)
App.screen.contents = {}
edit.mouse_release(State, x,y, mouse_button)
edit.mouse_press(State, x,y, mouse_button)
App.screen.contents = {}
App.screen.contents = {}
edit.keychord_press(State, chord)
edit.key_release(State, chord)
edit.mouse_press(State, x,y, mouse_button)
App.fake_mouse_release(x,y, mouse_button)
App.screen.contents = {}
-- not all keys are text_input
function edit.run_after_keychord(State, chord)
function edit.run_after_text_input(State, t)
edit.keychord_press(State, t)
edit.text_input(State, t)
edit.key_release(State, t)
App.screen.contents = {}
-- all text_input events are also keypresses
-- TODO: handle chords of multiple keys
function edit.key_release(State, key, scancode)
end
Text.keychord_press(State, chord)
end
end
function edit.keychord_press(State, chord, key)
if State.selection1.line and
function edit.text_input(State, t)
if State.search_term then
State.search_term = State.search_term..t
State.search_text = nil
function edit.mouse_release(State, x,y, mouse_button)
if State.search_term then return end
function edit.mouse_press(State, x,y, mouse_button)
if State.search_term then return end
width = right-left,
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- selection is empty to avoid perturbing future edits
check_nil(Editor_state.selection1.line, 'F - test_click_moves_cursor/selection:line')
check_nil(Editor_state.selection1.pos, 'F - test_click_moves_cursor/selection:pos')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line_takes_margins_into_account/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_on_wrapping_line_takes_margins_into_account/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_on_wrapping_line_takes_margins_into_account/selection is empty to avoid perturbing future edits')
App.screen.check(y, 'mno', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:3')
edit.run_after_text_input(Editor_state, 'a')
edit.run_after_keychord(Editor_state, 'up')
-- cursor wraps
check_eq(Editor_state.cursor1.line, 1, 'F - test_search_wrap_upwards/1/cursor:line')
check_eq(Editor_state.cursor1.pos, 5, 'F - test_search_wrap_upwards/1/cursor:pos')
end
edit.run_after_text_input(Editor_state, 'a')
edit.run_after_keychord(Editor_state, 'return')
-- cursor wraps
check_eq(Editor_state.cursor1.line, 1, 'F - test_search_wrap/1/cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'F - test_search_wrap/1/cursor:pos')
end
edit.run_after_text_input(Editor_state, 'a')
-- search for previous occurrence
edit.run_after_keychord(Editor_state, 'up')
check_eq(Editor_state.cursor1.line, 1, 'F - test_search_upwards/2/cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'F - test_search_upwards/2/cursor:pos')
end
edit.run_after_text_input(Editor_state, 'de')
edit.run_after_keychord(Editor_state, 'down')
edit.run_after_keychord(Editor_state, 'return')
check_eq(Editor_state.cursor1.line, 4, 'F - test_search/2/cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'F - test_search/2/cursor:pos')
end
edit.run_after_text_input(Editor_state, 'd')
edit.run_after_keychord(Editor_state, 'return')
check_eq(Editor_state.cursor1.line, 2, 'F - test_search/1/cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'F - test_search/1/cursor:pos')
-- reset cursor
Editor_state.cursor1 = {line=1, pos=1}
edit.run_after_text_input(Editor_state, 'g')
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')
edit.run_after_text_input(Editor_state, 'x')
check_eq(Editor_state.cursor1.line, 2, 'F - test_undo_insert_text/baseline/cursor:line')
check_eq(Editor_state.cursor1.pos, 5, 'F - test_undo_insert_text/baseline/cursor:pos')
edit.run_after_text_input(Editor_state, 's')
edit.run_after_text_input(Editor_state, 't')
edit.run_after_text_input(Editor_state, 'u')
check_eq(Editor_state.cursor1.pos, 28, 'F - test_position_cursor_on_recently_edited_wrapping_line/cursor:pos')
edit.run_after_text_input(Editor_state, 'j')
edit.run_after_text_input(Editor_state, 'k')
edit.run_after_text_input(Editor_state, 'l')
check_eq(Editor_state.screen_top1.line, 2, 'F - test_typing_on_bottom_line_scrolls_down/screen_top')
check_eq(Editor_state.cursor1.line, 3, 'F - test_typing_on_bottom_line_scrolls_down/cursor:line')
check_eq(Editor_state.cursor1.pos, 7, 'F - test_typing_on_bottom_line_scrolls_down/cursor:pos')
edit.run_after_text_input(Editor_state, 'a')
check_eq(Editor_state.screen_top1.line, 2, 'F - test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen_top')
check_eq(Editor_state.cursor1.line, 2, 'F - test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:pos')
end
function test_up_arrow_moves_cursor()
io.write('\ntest_up_arrow_moves_cursor')
-- display the first 3 lines with the cursor on the bottom line
App.screen.init{width=120, height=60}
App.screen.check(y, 'kl', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:2')
y = y + Editor_state.line_height
App.screen.check(y, 'ghij', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:1')
y = y + Editor_state.line_height
check_eq(Editor_state.screen_top1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen_top')
check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line')
check_eq(Editor_state.cursor1.pos, 5, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos')
y = Editor_state.top
check_eq(Editor_state.screen_top1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top')
check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:pos')
-- after hitting down arrow the screen doesn't scroll down further, and certainly doesn't scroll up
App.screen.check(y, 'ghij', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:3')
-- after hitting pagedown the screen scrolls down to start of a long line
edit.run_after_text_input(Editor_state, 'g')
local y = Editor_state.top
App.screen.check(y, 'def', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:2')
y = y + Editor_state.line_height
edit.keychord_press(Editor_state, 'd', 'd')
edit.text_input(Editor_state, 'D')
edit.key_release(Editor_state, 'd')
App.fake_key_release('lshift')
-- selected text is deleted and replaced with the key
App.screen.check(y, 'abc', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:1')
y = y + Editor_state.line_height
edit.run_after_text_input(Editor_state, 'x')
-- selected text is deleted and replaced with the key
edit.key_release(Editor_state, 'lshift')
-- selection persists even after shift is release
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')
function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()
io.write('\ntest_pagedown_followed_by_down_arrow_does_not_scroll_screen_up')
App.screen.init{width=Editor_state.left+30, height=60}
end
function test_click_on_wrapping_line_takes_margins_into_account()
io.write('\ntest_click_on_wrapping_line_takes_margins_into_account')
-- display two lines with cursor on one of them
App.screen.init{width=100, height=80}
Editor_state = edit.initialize_test_state()
Editor_state.left = 50 -- occupy only right side of screen
Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=20}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- click on the other line
edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- cursor moves
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_on_wrapping_line/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_on_wrapping_line/selection is empty to avoid perturbing future edits')
end
function test_click_on_wrapping_line()
io.write('\ntest_click_on_wrapping_line')
-- display two lines with cursor on one of them
App.screen.init{width=50, height=80}
Editor_state = edit.initialize_test_state()
Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=20}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- click on the other line
edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- cursor moves
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_empty_line/cursor')
end
function test_click_on_empty_line()
io.write('\ntest_click_on_empty_line')
-- display two lines with the first one empty
App.screen.init{width=50, height=80}
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_takes_margins_into_account/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_takes_margins_into_account/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_takes_margins_into_account/selection is empty to avoid perturbing future edits')
end
function test_click_takes_margins_into_account()
io.write('\ntest_click_takes_margins_into_account')
-- display two lines with cursor on one of them
App.screen.init{width=100, height=80}
Editor_state = edit.initialize_test_state()
Editor_state.left = 50 -- occupy only right side of screen
Editor_state.lines = load_array{'abc', 'def'}
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- click on the other line
edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- cursor moves
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_to_left_of_line/cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'F - test_click_to_left_of_line/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_to_left_of_line/selection is empty to avoid perturbing future edits')
end
function test_click_to_left_of_line()
io.write('\ntest_click_to_left_of_line')
-- display a line with the cursor in the middle
App.screen.init{width=50, height=80}
Editor_state = edit.initialize_test_state()
Editor_state.lines = load_array{'abc'}
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=3}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- click to the left of the line
edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1)
-- cursor moves to start of line
end
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_moves_cursor/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_moves_cursor/cursor:pos')
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.lines = load_array{'abc', 'def', 'xyz'}
Text.redraw_all(Editor_state)
edit.run_after_text_input(Editor_state, 'a')
local y = Editor_state.top
function test_click_moves_cursor()
io.write('\ntest_click_moves_cursor')
App.screen.init{width=50, height=60}
Editor_state = edit.initialize_test_state()
return log_browser.keychord_press(Log_browser_state, chordkey, scancode)
end
end
-- use this sparingly
function to_text(s)
if Text_cache[s] == nil then
Text_cache[s] = App.newText(love.graphics.getFont(), s)
end
return Text_cache[s]
end
return edit.key_release(Editor_state, key, scancode)
else
function source.key_release(key, scancode)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
if Focus == 'edit' then
return log_browser.keychord_press(Log_browser_state, chord, key)
end
end
return edit.keychord_press(Editor_state, chord, key)
else
keychord_press_on_file_navigator(chord, key)
return
end
if chord == 'C-l' then
--? print('C-l')
Show_log_browser_side = not Show_log_browser_side
if Show_log_browser_side then
function source.keychord_press(chord, key)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
--? print('source keychord')
if Show_file_navigator then
return log_browser.text_input(Log_browser_state, t)
end
end
return edit.text_input(Editor_state, t)
else
text_input_on_file_navigator(t)
return
end
function source.text_input(t)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
return log_browser.mouse_release(Log_browser_state, x,y, mouse_button)
end
end
return edit.mouse_release(Editor_state, x,y, mouse_button)
else
function source.mouse_release(x,y, mouse_button)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
if Focus == 'edit' then
log_browser.mouse_press(Log_browser_state, x,y, mouse_button)
for _,line_cache in ipairs(Editor_state.line_cache) do line_cache.starty = nil end -- just in case we scroll
end
end
edit.mouse_press(Editor_state, x,y, mouse_button)
elseif Show_log_browser_side and Log_browser_state.left <= x and x < Log_browser_state.right then
--? print('click on log_browser side')
if Focus ~= 'log_browser' then
Focus = 'log_browser'
edit.mouse_press(Editor_state, x,y, mouse_button)
return
end
function source.mouse_press(x,y, mouse_button)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
--? print('mouse click', x, y)
--? print(Editor_state.left, Editor_state.right)
--? print(Log_browser_state.left, Log_browser_state.right)
-- a copy of source.file_drop when given a filename
function source.switch_to_file(filename)
-- first make sure to save edits on any existing file
if Editor_state.next_save then
save_to_disk(Editor_state)
end
function source.file_drop(file)
-- first make sure to save edits on any existing file
if Editor_state.next_save then
save_to_disk(Editor_state)
end
-- clear the slate for the new file
Editor_state.filename = file:getFilename()
file:open('r')
Editor_state.lines = load_from_file(file)
file:close()
Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.cursor1 = {line=1, pos=1}
return edit.key_release(Editor_state, key, scancode)
end
-- use this sparingly
function to_text(s)
if Text_cache[s] == nil then
Text_cache[s] = App.newText(love.graphics.getFont(), s)
end
return Text_cache[s]
end
function run.key_release(key, scancode)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
return edit.keychord_press(Editor_state, chord, key)
end
function run.keychord_press(chord, key)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
return edit.text_input(Editor_state, t)
end
function run.text_input(t)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
return edit.mouse_release(Editor_state, x,y, mouse_button)
end
function run.mouse_release(x,y, mouse_button)
Cursor_time = 0 -- ensure cursor is visible immediately after it moves
function run.file_drop(file)
-- first make sure to save edits on any existing file
if Editor_state.next_save then
save_to_disk(Editor_state)
end
-- clear the slate for the new file
App.initialize_globals()
Editor_state.filename = file:getFilename()
file:open('r')
Editor_state.lines = load_from_file(file)
file:close()
Text.redraw_all(Editor_state)
love.window.setTitle('text.love - '..Editor_state.filename)
end
function run.draw()
edit.draw(Editor_state)
end
function run.update(dt)
Cursor_time = Cursor_time + dt
edit.update(Editor_state, dt)
end
function run.quit()
edit.quit(Editor_state)
end
function run.settings()
function log_browser.key_release(State, key, scancode)
end
function log_browser.keychord_press(State, chord, key)
-- move
if chord == 'up' then
while State.screen_top1.line > 1 do
State.screen_top1.line = State.screen_top1.line-1
if should_show(State.lines[State.screen_top1.line]) then
break
end
end
elseif chord == 'down' then
while State.screen_top1.line < #State.lines do
State.screen_top1.line = State.screen_top1.line+1
if should_show(State.lines[State.screen_top1.line]) then
break
end
end
elseif chord == 'pageup' then
local y = 0
while State.screen_top1.line > 1 and y < App.screen.height - 100 do
State.screen_top1.line = State.screen_top1.line - 1
if should_show(State.lines[State.screen_top1.line]) then
y = y + log_browser.height(State, State.screen_top1.line)
end
end
elseif chord == 'pagedown' then
local y = 0
while State.screen_top1.line < #State.lines and y < App.screen.height - 100 do
if should_show(State.lines[State.screen_top1.line]) then
y = y + log_browser.height(State, State.screen_top1.line)
end
State.screen_top1.line = State.screen_top1.line + 1
end
end
end
function log_browser.height(State, line_index)
local line = State.lines[line_index]
if line.data == nil then
-- section header
return State.line_height
elseif type(line.data) == 'string' then
return State.line_height
else
if line.height == nil then
--? print('nil line height! rendering off screen to calculate')
line.height = log_render[line.data.name](line.data, State.left, App.screen.height, State.right-State.left)
end
return line.height
end
end
function log_browser.text_input(State, t)
end
function log_browser.mouse_release(State, x,y, mouse_button)
end
function log_browser.mouse_press(State, x,y, mouse_button)
local line_index = log_browser.line_index(State, x,y)
if line_index == nil then
-- below lower margin
return
end
-- leave some space to click without focusing
local line = State.lines[line_index]
local xleft = log_browser.left_margin(State, line)
local xright = log_browser.right_margin(State, line)
if x < xleft or x > xright then
return
end
-- if it's a section begin/end and the section is collapsed, expand it
-- TODO: how to collapse?
if line.section_begin or line.section_end then
-- HACK: get section reference from next/previous line
local new_section
if line.section_begin then
if line_index < #State.lines then
local next_section_stack = State.lines[line_index+1].section_stack
if next_section_stack then
new_section = next_section_stack[#next_section_stack]
end
end
elseif line.section_end then
if line_index > 1 then
local previous_section_stack = State.lines[line_index-1].section_stack
if previous_section_stack then
new_section = previous_section_stack[#previous_section_stack]
end
end
end
if new_section and new_section.expanded == nil then
new_section.expanded = true
return
end
end
-- open appropriate file in source side
if line.filename ~= Editor_state.filename then
source.switch_to_file(line.filename)
end
-- set cursor
Editor_state.cursor1 = {line=line.line_number, pos=1, posB=nil}
-- make sure it's visible
-- TODO: handle extremely long lines
Editor_state.screen_top1.line = math.max(0, Editor_state.cursor1.line-5)
-- show cursor
Focus = 'edit'
-- expand B side
Editor_state.expanded = true
end
function log_browser.line_index(State, mx,my)
-- duplicate some logic from log_browser.draw
local y = State.top
for line_index = State.screen_top1.line,#State.lines do
local line = State.lines[line_index]
if should_show(line) then
y = y + log_browser.height(State, line_index)
if my < y then
return line_index
end
if y > App.screen.height then break end
end
end
end
function text_input_on_file_navigator(t)
File_navigation.filter = File_navigation.filter..t
File_navigation.candidates = source.file_navigator_candidates()
end
function keychord_press_on_file_navigator(chord, key)
log(2, 'file navigator: '..chord)
log(2, {name='file_navigator_state', files=File_navigation.candidates, index=File_navigation.index})
edit.run_after_text_input(Editor_state, 'a')
function test_click_moves_cursor()
io.write('\ntest_click_moves_cursor')
App.screen.init{width=50, height=60}
Editor_state.lines = load_array{'abc', 'def', 'xyz'}
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_moves_cursor/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_moves_cursor/cursor:pos')
-- selection is empty to avoid perturbing future edits
check_nil(Editor_state.selection1.line, 'F - test_click_moves_cursor/selection:line')
check_nil(Editor_state.selection1.pos, 'F - test_click_moves_cursor/selection:pos')
function test_click_to_left_of_line()
io.write('\ntest_click_to_left_of_line')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_to_left_of_line/cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'F - test_click_to_left_of_line/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_to_left_of_line/selection is empty to avoid perturbing future edits')
function test_click_takes_margins_into_account()
io.write('\ntest_click_takes_margins_into_account')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_takes_margins_into_account/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_takes_margins_into_account/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_takes_margins_into_account/selection is empty to avoid perturbing future edits')
function test_click_on_empty_line()
io.write('\ntest_click_on_empty_line')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_empty_line/cursor')
function test_click_on_wrapping_line()
io.write('\ntest_click_on_wrapping_line')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_on_wrapping_line/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_on_wrapping_line/selection is empty to avoid perturbing future edits')
function test_click_on_wrapping_line_takes_margins_into_account()
io.write('\ntest_click_on_wrapping_line_takes_margins_into_account')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line_takes_margins_into_account/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_on_wrapping_line_takes_margins_into_account/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_on_wrapping_line_takes_margins_into_account/selection is empty to avoid perturbing future edits')
edit.key_release(Editor_state, 'lshift')
-- selection persists even after shift is release
edit.run_after_text_input(Editor_state, 'x')
edit.keychord_press(Editor_state, 'd', 'd')
edit.text_input(Editor_state, 'D')
edit.key_release(Editor_state, 'd')
function test_click_with_mouse()
io.write('\ntest_click_with_mouse')
-- display two lines with cursor on one of them
App.screen.init{width=50, height=80}
function test_click_moves_cursor()
io.write('\ntest_click_moves_cursor')
App.screen.init{width=50, height=60}
-- click on the other line
edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- cursor moves
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse/cursor:line')
check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse/selection is empty to avoid perturbing future edits')
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_moves_cursor/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_moves_cursor/cursor:pos')
-- selection is empty to avoid perturbing future edits
check_nil(Editor_state.selection1.line, 'F - test_click_moves_cursor/selection:line')
check_nil(Editor_state.selection1.pos, 'F - test_click_moves_cursor/selection:pos')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_to_left_of_line/cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'F - test_click_with_mouse_to_left_of_line/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_to_left_of_line/selection is empty to avoid perturbing future edits')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_to_left_of_line/cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'F - test_click_to_left_of_line/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_to_left_of_line/selection is empty to avoid perturbing future edits')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_takes_margins_into_account/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_with_mouse_takes_margins_into_account/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_takes_margins_into_account/selection is empty to avoid perturbing future edits')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_takes_margins_into_account/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_takes_margins_into_account/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_takes_margins_into_account/selection is empty to avoid perturbing future edits')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_on_wrapping_line/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_with_mouse_on_wrapping_line/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_on_wrapping_line/selection is empty to avoid perturbing future edits')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_on_wrapping_line/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_on_wrapping_line/selection is empty to avoid perturbing future edits')
function test_click_with_mouse_on_wrapping_line_takes_margins_into_account()
io.write('\ntest_click_with_mouse_on_wrapping_line_takes_margins_into_account')
function test_click_on_wrapping_line_takes_margins_into_account()
io.write('\ntest_click_on_wrapping_line_takes_margins_into_account')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/selection is empty to avoid perturbing future edits')
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line_takes_margins_into_account/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_on_wrapping_line_takes_margins_into_account/cursor:pos')
check_nil(Editor_state.selection1.line, 'F - test_click_on_wrapping_line_takes_margins_into_account/selection is empty to avoid perturbing future edits')
function test_move_cursor_using_mouse()
io.write('\ntest_move_cursor_using_mouse')
App.screen.init{width=50, height=60}
Editor_state = edit.initialize_test_state()
Editor_state.lines = load_array{'abc', 'def', 'xyz'}
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
check_eq(Editor_state.cursor1.line, 1, 'F - test_move_cursor_using_mouse/cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'F - test_move_cursor_using_mouse/cursor:pos')
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_page_down_followed_by_down_arrow_does_not_scroll_screen_up()
io.write('\ntest_page_down_followed_by_down_arrow_does_not_scroll_screen_up')
function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()
io.write('\ntest_pagedown_followed_by_down_arrow_does_not_scroll_screen_up')
check_eq(Editor_state.screen_top1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top')
check_eq(Editor_state.cursor1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:pos')
check_eq(Editor_state.screen_top1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top')
check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:pos')
check_eq(Editor_state.screen_top1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen_top')
check_eq(Editor_state.cursor1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line')
check_eq(Editor_state.cursor1.pos, 5, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos')
check_eq(Editor_state.screen_top1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen_top')
check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line')
check_eq(Editor_state.cursor1.pos, 5, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos')
edit.run_after_textinput(Editor_state, 'j')
edit.run_after_textinput(Editor_state, 'k')
edit.run_after_textinput(Editor_state, 'l')
edit.run_after_text_input(Editor_state, 'j')
edit.run_after_text_input(Editor_state, 'k')
edit.run_after_text_input(Editor_state, 'l')
edit.run_after_textinput(Editor_state, 's')
edit.run_after_textinput(Editor_state, 't')
edit.run_after_textinput(Editor_state, 'u')
edit.run_after_text_input(Editor_state, 's')
edit.run_after_text_input(Editor_state, 't')
edit.run_after_text_input(Editor_state, 'u')
function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()
io.write('\ntest_pagedown_followed_by_down_arrow_does_not_scroll_screen_up')
App.screen.check(y, 'abc', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:1')
App.screen.check(y, 'def', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:2')
App.screen.check(y, 'ghij', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:3')
check_eq(Editor_state.screen_top1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top')
check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:pos')
check_eq(Editor_state.screen_top1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen_top')
check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line')
check_eq(Editor_state.cursor1.pos, 5, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos')
App.screen.check(y, 'ghij', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:1')
App.screen.check(y, 'kl', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:2')
App.screen.check(y, 'mno', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:3')
edit.run_after_text_input(Editor_state, 'a')
edit.run_after_text_input(Editor_state, 'j')
edit.run_after_text_input(Editor_state, 'k')
edit.run_after_text_input(Editor_state, 'l')
edit.run_after_text_input(Editor_state, 's')
edit.run_after_text_input(Editor_state, 't')
edit.run_after_text_input(Editor_state, 'u')
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.run_after_text_input(Editor_state, 'd')
edit.run_after_text_input(Editor_state, 'de')
edit.run_after_text_input(Editor_state, 'a')
edit.run_after_text_input(Editor_state, 'a')
edit.run_after_text_input(Editor_state, 'a')
function Text.text_input(State, t)
-- Don't handle any keys here that would trigger text_input above.
function Text.keychord_press(State, chord)
record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})
schedule_save(State)
end
elseif chord == 'escape' and not App.mouse_down(1) then
for _,line in ipairs(State.lines) do
if line.mode == 'drawing' then
line.show_help = false
end
end
elseif State.current_drawing_mode == 'name' then
if chord == 'return' then
State.current_drawing_mode = State.previous_drawing_mode
State.previous_drawing_mode = nil
else
local before = snapshot(State, State.lines.current_drawing_index)
local drawing = State.lines.current_drawing
local p = drawing.points[drawing.pending.target_point]
if chord == 'escape' then
p.name = nil
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
elseif chord == 'backspace' then
local len = utf8.len(p.name)
local byte_offset = Text.offset(p.name, len-1)
if len == 1 then byte_offset = 0 end
p.name = string.sub(p.name, 1, byte_offset)
record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
end
end
schedule_save(State)
function edit.run_after_textinput(State, t)
edit.keychord_pressed(State, t)
edit.textinput(State, t)
edit.key_released(State, t)
function edit.run_after_text_input(State, t)
edit.keychord_press(State, t)
edit.text_input(State, t)
edit.key_release(State, t)
Text.keychord_press(State, chord)
function edit.key_release(State, key, scancode)
-- all text_input events are also keypresses
function edit.run_after_text_input(State, t)
edit.keychord_press(State, t)
edit.text_input(State, t)
edit.key_release(State, t)
-- not all keys are text_input
edit.keychord_press(State, chord)
edit.key_release(State, chord)
edit.mouse_press(State, x,y, mouse_button)
edit.mouse_release(State, x,y, mouse_button)
edit.mouse_press(State, x,y, mouse_button)
edit.mouse_release(State, x,y, mouse_button)
if pong.keychord_press then pong.keychord_press(chord, key) end
if run.mouse_pressed then run.mouse_press(x,y, mouse_button) end
if source.mouse_pressed then source.mouse_press(x,y, mouse_button) end