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_inputfunction 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 keysfunction edit.key_release(State, key, scancode)endText.keychord_press(State, chord)endendfunction edit.keychord_press(State, chord, key)if State.selection1.line andfunction edit.text_input(State, t)if State.search_term thenState.search_term = State.search_term..tState.search_text = nilfunction edit.mouse_release(State, x,y, mouse_button)if State.search_term then return endfunction edit.mouse_press(State, x,y, mouse_button)if State.search_term then return endwidth = right-left,
Editor_state.selection1 = {}edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cacheedit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- selection is empty to avoid perturbing future editscheck_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 wrapscheck_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')endedit.run_after_text_input(Editor_state, 'a')edit.run_after_keychord(Editor_state, 'return')-- cursor wrapscheck_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')endedit.run_after_text_input(Editor_state, 'a')-- search for previous occurrenceedit.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')endedit.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')endedit.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 cursorEditor_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')endfunction test_up_arrow_moves_cursor()io.write('\ntest_up_arrow_moves_cursor')-- display the first 3 lines with the cursor on the bottom lineApp.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_heightApp.screen.check(y, 'ghij', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:1')y = y + Editor_state.line_heightcheck_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.topcheck_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 upApp.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 lineedit.run_after_text_input(Editor_state, 'g')local y = Editor_state.topApp.screen.check(y, 'def', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:2')y = y + Editor_state.line_heightedit.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 keyApp.screen.check(y, 'abc', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:1')y = y + Editor_state.line_heightedit.run_after_text_input(Editor_state, 'x')-- selected text is deleted and replaced with the keyedit.key_release(Editor_state, 'lshift')-- selection persists even after shift is releasecheck_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}endfunction 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 themApp.screen.init{width=100, height=80}Editor_state = edit.initialize_test_state()Editor_state.left = 50 -- occupy only right side of screenEditor_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 lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_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')endfunction test_click_on_wrapping_line()io.write('\ntest_click_on_wrapping_line')-- display two lines with cursor on one of themApp.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 lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_empty_line/cursor')endfunction test_click_on_empty_line()io.write('\ntest_click_on_empty_line')-- display two lines with the first one emptyApp.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')endfunction test_click_takes_margins_into_account()io.write('\ntest_click_takes_margins_into_account')-- display two lines with cursor on one of themApp.screen.init{width=100, height=80}Editor_state = edit.initialize_test_state()Editor_state.left = 50 -- occupy only right side of screenEditor_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 lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_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')endfunction test_click_to_left_of_line()io.write('\ntest_click_to_left_of_line')-- display a line with the cursor in the middleApp.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 lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1)-- cursor moves to start of lineendcheck_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.topfunction 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)endend-- use this sparinglyfunction to_text(s)if Text_cache[s] == nil thenText_cache[s] = App.newText(love.graphics.getFont(), s)endreturn Text_cache[s]endreturn edit.key_release(Editor_state, key, scancode)elsefunction source.key_release(key, scancode)Cursor_time = 0 -- ensure cursor is visible immediately after it movesif Focus == 'edit' thenreturn log_browser.keychord_press(Log_browser_state, chord, key)endendreturn edit.keychord_press(Editor_state, chord, key)elsekeychord_press_on_file_navigator(chord, key)returnendif chord == 'C-l' then--? print('C-l')Show_log_browser_side = not Show_log_browser_sideif Show_log_browser_side thenfunction source.keychord_press(chord, key)Cursor_time = 0 -- ensure cursor is visible immediately after it moves--? print('source keychord')if Show_file_navigator thenreturn log_browser.text_input(Log_browser_state, t)endendreturn edit.text_input(Editor_state, t)elsetext_input_on_file_navigator(t)returnendfunction source.text_input(t)Cursor_time = 0 -- ensure cursor is visible immediately after it movesreturn log_browser.mouse_release(Log_browser_state, x,y, mouse_button)endendreturn edit.mouse_release(Editor_state, x,y, mouse_button)elsefunction source.mouse_release(x,y, mouse_button)Cursor_time = 0 -- ensure cursor is visible immediately after it movesif Focus == 'edit' thenlog_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 scrollendendedit.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' thenFocus = 'log_browser'edit.mouse_press(Editor_state, x,y, mouse_button)returnendfunction 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 filenamefunction source.switch_to_file(filename)-- first make sure to save edits on any existing fileif Editor_state.next_save thensave_to_disk(Editor_state)endfunction source.file_drop(file)-- first make sure to save edits on any existing fileif Editor_state.next_save thensave_to_disk(Editor_state)end-- clear the slate for the new fileEditor_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 sparinglyfunction to_text(s)if Text_cache[s] == nil thenText_cache[s] = App.newText(love.graphics.getFont(), s)endreturn Text_cache[s]endfunction run.key_release(key, scancode)Cursor_time = 0 -- ensure cursor is visible immediately after it movesreturn edit.keychord_press(Editor_state, chord, key)endfunction run.keychord_press(chord, key)Cursor_time = 0 -- ensure cursor is visible immediately after it movesreturn edit.text_input(Editor_state, t)endfunction run.text_input(t)Cursor_time = 0 -- ensure cursor is visible immediately after it movesreturn edit.mouse_release(Editor_state, x,y, mouse_button)endfunction run.mouse_release(x,y, mouse_button)Cursor_time = 0 -- ensure cursor is visible immediately after it movesfunction run.file_drop(file)-- first make sure to save edits on any existing fileif Editor_state.next_save thensave_to_disk(Editor_state)end-- clear the slate for the new fileApp.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)endfunction run.draw()edit.draw(Editor_state)endfunction run.update(dt)Cursor_time = Cursor_time + dtedit.update(Editor_state, dt)endfunction run.quit()edit.quit(Editor_state)endfunction run.settings()
function log_browser.key_release(State, key, scancode)endfunction log_browser.keychord_press(State, chord, key)-- moveif chord == 'up' thenwhile State.screen_top1.line > 1 doState.screen_top1.line = State.screen_top1.line-1if should_show(State.lines[State.screen_top1.line]) thenbreakendendelseif chord == 'down' thenwhile State.screen_top1.line < #State.lines doState.screen_top1.line = State.screen_top1.line+1if should_show(State.lines[State.screen_top1.line]) thenbreakendendelseif chord == 'pageup' thenlocal y = 0while State.screen_top1.line > 1 and y < App.screen.height - 100 doState.screen_top1.line = State.screen_top1.line - 1if should_show(State.lines[State.screen_top1.line]) theny = y + log_browser.height(State, State.screen_top1.line)endendelseif chord == 'pagedown' thenlocal y = 0while State.screen_top1.line < #State.lines and y < App.screen.height - 100 doif should_show(State.lines[State.screen_top1.line]) theny = y + log_browser.height(State, State.screen_top1.line)endState.screen_top1.line = State.screen_top1.line + 1endendendfunction log_browser.height(State, line_index)local line = State.lines[line_index]if line.data == nil then-- section headerreturn State.line_heightelseif type(line.data) == 'string' thenreturn State.line_heightelseif 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)endreturn line.heightendendfunction log_browser.text_input(State, t)endfunction log_browser.mouse_release(State, x,y, mouse_button)endfunction 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 marginreturnend-- leave some space to click without focusinglocal 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 thenreturnend-- 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 linelocal new_sectionif line.section_begin thenif line_index < #State.lines thenlocal next_section_stack = State.lines[line_index+1].section_stackif next_section_stack thennew_section = next_section_stack[#next_section_stack]endendelseif line.section_end thenif line_index > 1 thenlocal previous_section_stack = State.lines[line_index-1].section_stackif previous_section_stack thennew_section = previous_section_stack[#previous_section_stack]endendendif new_section and new_section.expanded == nil thennew_section.expanded = truereturnendend-- open appropriate file in source sideif line.filename ~= Editor_state.filename thensource.switch_to_file(line.filename)end-- set cursorEditor_state.cursor1 = {line=line.line_number, pos=1, posB=nil}-- make sure it's visible-- TODO: handle extremely long linesEditor_state.screen_top1.line = math.max(0, Editor_state.cursor1.line-5)-- show cursorFocus = 'edit'-- expand B sideEditor_state.expanded = trueendfunction log_browser.line_index(State, mx,my)-- duplicate some logic from log_browser.drawlocal y = State.topfor line_index = State.screen_top1.line,#State.lines dolocal line = State.lines[line_index]if should_show(line) theny = y + log_browser.height(State, line_index)if my < y thenreturn line_indexendif y > App.screen.height then break endendendend
function text_input_on_file_navigator(t)File_navigation.filter = File_navigation.filter..tFile_navigation.candidates = source.file_navigator_candidates()endfunction 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_cacheedit.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 editscheck_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 releaseedit.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 themApp.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 lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_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_cacheedit.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 editscheck_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_cacheedit.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')-- 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
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)endelseif chord == 'escape' and not App.mouse_down(1) thenfor _,line in ipairs(State.lines) doif line.mode == 'drawing' thenline.show_help = falseendendelseif State.current_drawing_mode == 'name' thenif chord == 'return' thenState.current_drawing_mode = State.previous_drawing_modeState.previous_drawing_mode = nilelselocal before = snapshot(State, State.lines.current_drawing_index)local drawing = State.lines.current_drawinglocal p = drawing.points[drawing.pending.target_point]if chord == 'escape' thenp.name = nilrecord_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})elseif chord == 'backspace' thenlocal len = utf8.len(p.name)local byte_offset = Text.offset(p.name, len-1)if len == 1 then byte_offset = 0 endp.name = string.sub(p.name, 1, byte_offset)record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})endendschedule_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 keypressesfunction 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_inputedit.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) endif source.mouse_pressed then source.mouse_press(x,y, mouse_button) end