LXTTOB33N2HCUZFIUDRQGGBVHK2HODRG4NBLH6RXRQZDCHF27BSAC EWQ2VRMSOF4JOGWEF7W3CURXM3QSQY7SK6TGULPWG77ENA4XFUKQC IRJKWZWN6QV3QJGO234WCE6TKLZ2NKL2QNAKPQK25PHRFUJH5UQQC ORQKYYD7SUJKJYGIU5TNL5EASXQG3VRGCKHLB5YDXBWB2RPBA7CAC LS55YKGWKICTQTAHR5KLMNDOL6CDI4ATT3NT5Z2YL5IM3CRQOONQC 5FW7YOFTLKHRND6IOR4HG4X3C5BO2WV5KTEUW3PPKCRU5L5GXKXQC NP7PIUBTR4K6SWJS46YZG3H2RYYNRGNEJMPV4I24TQXT5O3YT27QC R5QXEHUIZLELJGGCZAE7ATNS3CLRJ7JFRENMGH4XXH24C5WABZDQC BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC 73OCE2MCBJJZZMN2KYPJTBOUCKBZAOQ2QIAMTGCNOOJ2AJAXFT2AC H2DPLWMVRFYTO2CQTG54FMT2LF3B6UKLXH32CUA22DNQJVP5XBNQC Z4XRNDTRTGSZHNB65WNHOVUBFW4QWQABLVSK4RM3QJHGK33DMRJAC HOSPP2ANSW654DYRTC6CQUQA2GUKV6T2FI7QBKXD2DZS3R32IMGAC TKFSYQ2ZTEPN27IGKGEYSUS7FQ2AUIC7DJWFF2RJ53AW6QRPMXWQC DHI6IJCNSTHGED67T6H5X6Y636C7PIDGIJD32HBEKLT5WIMRS5MAC VJ77YABHVJZWJKLHAGIPC562GYM73AUGRLCP4JLKP5JPWPT2RIHAC 5DOC2CBMBDMAOJ7IKLDGVRCY4SNPCJTTF7DK7WGNLPGNV4AWVJNAC KWOJ6XHEE7ERLFJ6FBXCL73DE6OFJQ7LXNXAN44G5P5EXFDH5HIAC XNFTJHC4QSHNSIWNN7K6QZEZ37GTQYKHS4EPNSVPQCUSWREROGIQC QYIFOHW3WDDQMK4ATY6IOSQRFHJOQ5QCPDKRC4GVGWLQEH4HGWVQC YTSPVDZHEN5LLNMGIBUBLPWFWSFM3SOHBRGWYSDEVFKRTH24ARRQC IMEJA43L3OX7S5KIYLZJ4F3ITACLAA5SZBHSCIJMULCPRSW7LXBAC YPHKZVWM2FS7U3VNVDXFRJTBF4RLQ6K7ZWISLHOQJPYSKBELHFEAC AD34IX2ZSGYGU3LGY2IZOZNKD4HRQOYJVG5UWMWLXJZJSM62FFOAC R5OKMVVCPAKL2IUMIY7A7ZMTJQZS6UWKW4EVLAVCPLPVNI5DCEYQC JF5L2BBS7ESMKHNGKLXI2F32GZKET2ICJ4KT2L5BMH3P2L2Y5MRAC 6E3HVYWFP3JLJ3DJ5BH4WGJUXQV5MDCBCE5GH3SXRHRQZOG4VJLQC 2ENZW7TVCS47BWCA4AIEVGKGMT4Y2TSM5IJ7O5K2VSWNXIN3SG4QC S5VCAFKYBM35HF3SI4MCAQROWBRUC7YHWJMKWC6GWTTV5PHUCEFAC U7M4M2F7P5TGLTHKQ7J72GQFNPBII4PLJVJ44YVVOYEI4KPUDI6AC LERERVPHE5SEWDHQ7IAGQSXUAI2QHQJ33NBNRMRXZ34X7P23I2IAC DFSDPDO7RHOLPVT4TD2Z3YZCKS6737LYIWBTJJI4BO73IIAJ5BYQC V5MJRFOZRVVDCPOWTLXPHS2HZBZKOOCPPKFMRP6MWZN6N62QLFAAC XOAHJ6M3QKHSE5F7ICLPXN3RMH7JQEZXDKTOTIOBYKJFVT6PMYRAC R53OF3ONKT5VL5BGK63YSN6GXIIAVNYDG4UMHITK72WXFWPJ25MQC A2NV3WVOKBOWBCSV3K4I6MO5LSVSSUZVNH226HV2HDCOMSPRVSSAC YYUGIYFVT5VH5XTOQCL2OEN3PLQ3U6SR4DQSOA5S3SNF25Z2AL7AC 4RUI5X52CSQODLT3WI4VBMXWZLACBYV5QANGDKRWS3VONZPVSEEQC PHFWIFYKFOGVX7CEAMGJ3FDY6LL5QSZ7T7CTCZ66WMNXV6C242FAC QVDQMJXVTM3BBMQDYIAIZMFLBQ56Q27U7H2OYRFE53MEMOCIXIAQC WY3JD6W6EANKQC4SRRIAM2Q3QZNVOGN3MIMBL3M55S4ZZSJFSF2AC HMODUNJEQLZ3W46GKYIDL55F6COVXHTIC6UW4AK3SXOOKOPE6NNAC NQWWTGXRLSBASOSP75FPOSVYP664VYRFQH7MY5LALLIP2VEBQMCQC N6V6UJ3P4EAGM7OLPAW7VBCDNQQHCWIK7GAEDB53LTL6XPR5YUKAC -- major tests for text editing flows-- This still isn't quite as thorough as I'd like.function test_draw_text()io.write('\ntest_draw_text')App.screen.init{width=120, height=60}Lines = load_array{'abc', 'def', 'ghi'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_draw_text/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_draw_text/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_draw_text/screen:3')endfunction test_draw_wrapping_text()io.write('\ntest_draw_wrapping_text')App.screen.init{width=50, height=60}Lines = load_array{'abc', 'defgh', 'xyz'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_draw_wrapping_text/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_draw_wrapping_text/screen:2')y = y + Line_heightApp.screen.check(y, 'gh', 'F - test_draw_wrapping_text/screen:3')endfunction test_draw_word_wrapping_text()io.write('\ntest_draw_word_wrapping_text')App.screen.init{width=60, height=60}Lines = load_array{'abc def ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc ', 'F - test_draw_word_wrapping_text/screen:1')y = y + Line_heightApp.screen.check(y, 'def ', 'F - test_draw_word_wrapping_text/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_draw_word_wrapping_text/screen:3')endfunction test_draw_text_wrapping_within_word()-- arrange a screen line that needs to be split within a wordio.write('\ntest_draw_text_wrapping_within_word')App.screen.init{width=60, height=60}Lines = load_array{'abcd e fghijk', 'xyz'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abcd ', 'F - test_draw_text_wrapping_within_word/screen:1')y = y + Line_heightApp.screen.check(y, 'e fghi', 'F - test_draw_text_wrapping_within_word/screen:2')y = y + Line_heightApp.screen.check(y, 'jk', 'F - test_draw_text_wrapping_within_word/screen:3')endfunction test_edit_wrapping_text()io.write('\ntest_edit_wrapping_text')App.screen.init{width=50, height=60}Lines = load_array{'abc', 'def', 'xyz'}Line_width = App.screen.widthCursor1 = {line=2, pos=4}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.run_after_textinput('g')App.run_after_textinput('h')App.run_after_textinput('i')App.run_after_textinput('j')App.run_after_textinput('k')App.run_after_textinput('l')local y = Margin_topApp.screen.check(y, 'abc', 'F - test_edit_wrapping_text/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_edit_wrapping_text/screen:2')y = y + Line_heightApp.screen.check(y, 'ghij', 'F - test_edit_wrapping_text/screen:3')endfunction test_insert_newline()io.write('\ntest_insert_newline')-- display a few lines with cursor on bottom lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_insert_newline/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_insert_newline/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_insert_newline/baseline/screen:3')-- after hitting the enter key the screen scrolls downApp.run_after_keychord('return')check_eq(Screen_top1.line, 1, 'F - test_insert_newline/screen_top')check_eq(Cursor1.line, 2, 'F - test_insert_newline/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_insert_newline/cursor:pos')y = Margin_topApp.screen.check(y, 'a', 'F - test_insert_newline/screen:1')y = y + Line_heightApp.screen.check(y, 'bc', 'F - test_insert_newline/screen:2')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_insert_newline/screen:3')endfunction test_insert_from_clipboard()io.write('\ntest_insert_from_clipboard')-- display a few lines with cursor on bottom lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_insert_from_clipboard/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_insert_from_clipboard/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_insert_from_clipboard/baseline/screen:3')-- after hitting the enter key the screen scrolls downApp.clipboard = 'xy\nz'App.run_after_keychord('C-v')check_eq(Screen_top1.line, 1, 'F - test_insert_from_clipboard/screen_top')check_eq(Cursor1.line, 2, 'F - test_insert_from_clipboard/cursor:line')check_eq(Cursor1.pos, 2, 'F - test_insert_from_clipboard/cursor:pos')y = Margin_topApp.screen.check(y, 'axy', 'F - test_insert_from_clipboard/screen:1')y = y + Line_heightApp.screen.check(y, 'zbc', 'F - test_insert_from_clipboard/screen:2')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_insert_from_clipboard/screen:3')endfunction test_move_cursor_using_mouse()io.write('\ntest_move_cursor_using_mouse')App.screen.init{width=50, height=60}Lines = load_array{'abc', 'def', 'xyz'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw() -- populate line.y for each line in Lineslocal screen_left_margin = 25 -- pixelsApp.run_after_mouserelease(screen_left_margin+8,Margin_top+5, '1')check_eq(Cursor1.line, 1, 'F - test_move_cursor_using_mouse/cursor:line')check_eq(Cursor1.pos, 2, 'F - test_move_cursor_using_mouse/cursor:pos')endfunction test_pagedown()io.write('\ntest_pagedown')App.screen.init{width=120, height=45}Lines = load_array{'abc', 'def', 'ghi'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}-- initially the first two lines are displayedApp.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_pagedown/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_pagedown/baseline/screen:2')-- after pagedown the bottom line becomes the topApp.run_after_keychord('pagedown')check_eq(Screen_top1.line, 2, 'F - test_pagedown/screen_top')check_eq(Cursor1.line, 2, 'F - test_pagedown/cursor')y = Margin_topApp.screen.check(y, 'def', 'F - test_pagedown/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_pagedown/screen:2')endfunction test_pagedown_skips_drawings()io.write('\ntest_pagedown_skips_drawings')-- some lines of text with a drawing intermixedApp.screen.init{width=50, height=80}Lines = load_array{'abc', -- height 15'```lines', '```', -- height 25'def', -- height 15'ghi'} -- height 15check_eq(Lines[2].mode, 'drawing', 'F - test_pagedown_skips_drawings/baseline/lines')Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}local drawing_height = 20 + App.screen.width / 2 -- default-- initially the screen displays the first line and the drawing-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80pxApp.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_pagedown_skips_drawings/baseline/screen:1')-- after pagedown the screen draws the drawing up top-- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80pxApp.run_after_keychord('pagedown')check_eq(Screen_top1.line, 2, 'F - test_pagedown_skips_drawings/screen_top')check_eq(Cursor1.line, 3, 'F - test_pagedown_skips_drawings/cursor')y = Margin_top + drawing_heightApp.screen.check(y, 'def', 'F - test_pagedown_skips_drawings/screen:1')endfunction test_pagedown_shows_one_screen_line_in_common()io.write('\ntest_pagedown_shows_one_screen_line_in_common')-- some lines of text with a drawing intermixedApp.screen.init{width=50, height=60}Lines = load_array{'abc', 'def ghi jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def ', 'F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi ', 'F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:3')-- after pagedown the bottom screen line becomes the topApp.run_after_keychord('pagedown')check_eq(Screen_top1.line, 2, 'F - test_pagedown_shows_one_screen_line_in_common/screen_top:line')check_eq(Screen_top1.pos, 5, 'F - test_pagedown_shows_one_screen_line_in_common/screen_top:pos')check_eq(Cursor1.line, 2, 'F - test_pagedown_shows_one_screen_line_in_common/cursor:line')check_eq(Cursor1.pos, 5, 'F - test_pagedown_shows_one_screen_line_in_common/cursor:pos')y = Margin_topApp.screen.check(y, 'ghi ', 'F - test_pagedown_shows_one_screen_line_in_common/screen:1')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_pagedown_shows_one_screen_line_in_common/screen:2')y = y + Line_heightApp.screen.check(y, 'mn', 'F - test_pagedown_shows_one_screen_line_in_common/screen:3')endfunction test_down_arrow_moves_cursor()io.write('\ntest_down_arrow_moves_cursor')App.screen.init{width=120, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}-- initially the first three lines are displayedApp.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_down_arrow_moves_cursor/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_down_arrow_moves_cursor/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_down_arrow_moves_cursor/baseline/screen:3')-- after hitting the down arrow, the cursor moves down by 1 lineApp.run_after_keychord('down')check_eq(Screen_top1.line, 1, 'F - test_down_arrow_moves_cursor/screen_top')check_eq(Cursor1.line, 2, 'F - test_down_arrow_moves_cursor/cursor')-- the screen is unchangedy = Margin_topApp.screen.check(y, 'abc', 'F - test_down_arrow_moves_cursor/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_down_arrow_moves_cursor/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_down_arrow_moves_cursor/screen:3')endfunction test_down_arrow_scrolls_down_by_one_line()io.write('\ntest_down_arrow_scrolls_down_by_one_line')-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=120, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=3, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:3')-- after hitting the down arrow the screen scrolls down by one lineApp.run_after_keychord('down')check_eq(Screen_top1.line, 2, 'F - test_down_arrow_scrolls_down_by_one_line/screen_top')check_eq(Cursor1.line, 4, 'F - test_down_arrow_scrolls_down_by_one_line/cursor')y = Margin_topApp.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_line/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_down_arrow_scrolls_down_by_one_line/screen:2')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_down_arrow_scrolls_down_by_one_line/screen:3')endfunction test_down_arrow_scrolls_down_by_one_screen_line()io.write('\ntest_down_arrow_scrolls_down_by_one_screen_line')-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=3, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi ', 'F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the down arrow the screen scrolls down by one lineApp.run_after_keychord('down')check_eq(Screen_top1.line, 2, 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen_top')check_eq(Cursor1.line, 3, 'F - test_down_arrow_scrolls_down_by_one_screen_line/cursor:line')check_eq(Cursor1.pos, 5, 'F - test_down_arrow_scrolls_down_by_one_screen_line/cursor:pos')y = Margin_topApp.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi ', 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen:2')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen:3')endfunction test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()io.write('\ntest_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word')-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghijkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=3, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghijk', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:3')-- after hitting the down arrow the screen scrolls down by one lineApp.run_after_keychord('down')check_eq(Screen_top1.line, 2, 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen_top')check_eq(Cursor1.line, 3, 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/cursor:line')check_eq(Cursor1.pos, 6, 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/cursor:pos')y = Margin_topApp.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:1')y = y + Line_heightApp.screen.check(y, 'ghijk', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:2')y = y + Line_heightApp.screen.check(y, 'l', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:3')endfunction 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')App.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghijkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=3, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghijk', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:3')-- after hitting pagedown the screen scrolls down to start of a long lineApp.run_after_keychord('pagedown')check_eq(Screen_top1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top')check_eq(Cursor1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_page_down_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.run_after_keychord('down')check_eq(Screen_top1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen_top')check_eq(Cursor1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line')check_eq(Cursor1.pos, 6, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos')y = Margin_topApp.screen.check(y, 'ghijk', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:1')y = y + Line_heightApp.screen.check(y, 'l', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:2')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:3')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}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = 120Cursor1 = {line=3, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_up_arrow_moves_cursor/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_moves_cursor/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_moves_cursor/baseline/screen:3')-- after hitting the up arrow the cursor moves up by 1 lineApp.run_after_keychord('up')check_eq(Screen_top1.line, 1, 'F - test_up_arrow_moves_cursor/screen_top')check_eq(Cursor1.line, 2, 'F - test_up_arrow_moves_cursor/cursor')-- the screen is unchangedy = Margin_topApp.screen.check(y, 'abc', 'F - test_up_arrow_moves_cursor/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_moves_cursor/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_moves_cursor/screen:3')endfunction test_up_arrow_scrolls_up_by_one_line()io.write('\ntest_up_arrow_scrolls_up_by_one_line')-- display the lines 2/3/4 with the cursor on line 2App.screen.init{width=120, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = 120Cursor1 = {line=2, pos=1}Screen_top1 = {line=2, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:3')-- after hitting the up arrow the screen scrolls up by one lineApp.run_after_keychord('up')check_eq(Screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_by_one_line/screen_top')check_eq(Cursor1.line, 1, 'F - test_up_arrow_scrolls_up_by_one_line/cursor')y = Margin_topApp.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_by_one_line/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_by_one_line/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_by_one_line/screen:3')endfunction test_up_arrow_scrolls_up_by_one_screen_line()io.write('\ntest_up_arrow_scrolls_up_by_one_screen_line')-- display lines starting from second screen line of a lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=3, pos=6}Screen_top1 = {line=3, pos=5}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:2')-- after hitting the up arrow the screen scrolls up to first screen lineApp.run_after_keychord('up')y = Margin_topApp.screen.check(y, 'ghi ', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:1')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:2')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:3')check_eq(Screen_top1.line, 3, 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top')check_eq(Screen_top1.pos, 1, 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top')check_eq(Cursor1.line, 3, 'F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:pos')endfunction test_up_arrow_scrolls_up_to_final_screen_line()io.write('\ntest_up_arrow_scrolls_up_to_final_screen_line')-- display lines starting just after a long lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=2, pos=1}Screen_top1 = {line=2, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:3')-- after hitting the up arrow the screen scrolls up to final screen line of previous lineApp.run_after_keychord('up')y = Margin_topApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:2')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:3')check_eq(Screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top')check_eq(Screen_top1.pos, 5, 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top')check_eq(Cursor1.line, 1, 'F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:line')check_eq(Cursor1.pos, 5, 'F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:pos')endfunction test_up_arrow_scrolls_up_to_empty_line()io.write('\ntest_up_arrow_scrolls_up_to_empty_line')-- display a screenful of text with an empty line just above it outside the screenApp.screen.init{width=120, height=60}Lines = load_array{'', 'abc', 'def', 'ghi', 'jkl'}Line_width = 120Cursor1 = {line=2, pos=1}Screen_top1 = {line=2, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:3')-- after hitting the up arrow the screen scrolls up by one lineApp.run_after_keychord('up')check_eq(Screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_to_empty_line/screen_top')check_eq(Cursor1.line, 1, 'F - test_up_arrow_scrolls_up_to_empty_line/cursor')y = Margin_top-- empty first liney = y + Line_heightApp.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_to_empty_line/screen:2')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_empty_line/screen:3')endfunction test_pageup()io.write('\ntest_pageup')App.screen.init{width=120, height=45}Lines = load_array{'abc', 'def', 'ghi'}Line_width = App.screen.widthCursor1 = {line=2, pos=1}Screen_top1 = {line=2, pos=1}Screen_bottom1 = {}-- initially the last two lines are displayedApp.draw()local y = Margin_topApp.screen.check(y, 'def', 'F - test_pageup/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_pageup/baseline/screen:2')-- after pageup the cursor goes to first lineApp.run_after_keychord('pageup')check_eq(Screen_top1.line, 1, 'F - test_pageup/screen_top')check_eq(Cursor1.line, 1, 'F - test_pageup/cursor')y = Margin_topApp.screen.check(y, 'abc', 'F - test_pageup/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_pageup/screen:2')endfunction test_pageup_scrolls_up_by_screen_line()io.write('\ntest_pageup_scrolls_up_by_screen_line')-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=2, pos=1}Screen_top1 = {line=2, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'ghi', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the page-up key the screen scrolls up to topApp.run_after_keychord('pageup')check_eq(Screen_top1.line, 1, 'F - test_pageup_scrolls_up_by_screen_line/screen_top')check_eq(Cursor1.line, 1, 'F - test_pageup_scrolls_up_by_screen_line/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_pageup_scrolls_up_by_screen_line/cursor:pos')y = Margin_topApp.screen.check(y, 'abc ', 'F - test_pageup_scrolls_up_by_screen_line/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_pageup_scrolls_up_by_screen_line/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_pageup_scrolls_up_by_screen_line/screen:3')endfunction test_pageup_scrolls_up_from_middle_screen_line()io.write('\ntest_pageup_scrolls_up_from_middle_screen_line')-- display a few lines starting from the middle of a line (Cursor1.pos > 1)App.screen.init{width=25+30, height=60}Lines = load_array{'abc def', 'ghi jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=2, pos=5}Screen_top1 = {line=2, pos=5}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'jkl', 'F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the page-up key the screen scrolls up to topApp.run_after_keychord('pageup')check_eq(Screen_top1.line, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/screen_top')check_eq(Cursor1.line, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/cursor:pos')y = Margin_topApp.screen.check(y, 'abc ', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi ', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:3')endfunction test_enter_on_bottom_line_scrolls_down()io.write('\ntest_enter_on_bottom_line_scrolls_down')-- display a few lines with cursor on bottom lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=3, pos=2}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:3')-- after hitting the enter key the screen scrolls downApp.run_after_keychord('return')check_eq(Screen_top1.line, 2, 'F - test_enter_on_bottom_line_scrolls_down/screen_top')check_eq(Cursor1.line, 4, 'F - test_enter_on_bottom_line_scrolls_down/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_enter_on_bottom_line_scrolls_down/cursor:pos')y = Margin_topApp.screen.check(y, 'def', 'F - test_enter_on_bottom_line_scrolls_down/screen:1')y = y + Line_heightApp.screen.check(y, 'g', 'F - test_enter_on_bottom_line_scrolls_down/screen:2')y = y + Line_heightApp.screen.check(y, 'hi', 'F - test_enter_on_bottom_line_scrolls_down/screen:3')endfunction test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()io.write('\ntest_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom')-- display just the bottom line on screenApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=4, pos=2}Screen_top1 = {line=4, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'jkl', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/baseline/screen:1')-- after hitting the enter key the screen does not scroll downApp.run_after_keychord('return')check_eq(Screen_top1.line, 4, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen_top')check_eq(Cursor1.line, 5, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:pos')y = Margin_topApp.screen.check(y, 'j', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:1')y = y + Line_heightApp.screen.check(y, 'kl', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:2')endfunction test_position_cursor_on_recently_edited_wrapping_line()-- draw a line wrapping over 2 screen linesio.write('\ntest_position_cursor_on_recently_edited_wrapping_line')App.screen.init{width=120, height=200}Lines = load_array{'abc def ghi jkl mno pqr ', 'xyz'}Line_width = 100Cursor1 = {line=1, pos=25}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_top-- I don't understand why 120px fits so much on a fake screen, but whatever..App.screen.check(y, 'abc def ghi ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:1')y = y + Line_heightApp.screen.check(y, 'jkl mno pqr ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:2')y = y + Line_heightApp.screen.check(y, 'xyz', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:3')-- add to the line until it's wrapping over 3 screen linesApp.run_after_textinput('s')App.run_after_textinput('t')App.run_after_textinput('u')check_eq(Cursor1.pos, 28, 'F - test_move_cursor_using_mouse/cursor:pos')y = Margin_topApp.screen.check(y, 'abc def ghi ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:1')y = y + Line_heightApp.screen.check(y, 'jkl mno pqr ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:2')y = y + Line_heightApp.screen.check(y, 'stu', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:3')-- try to move the cursor earlier in the third screen line by clicking the mouselocal screen_left_margin = 25 -- pixelsApp.run_after_mouserelease(screen_left_margin+8,Margin_top+Line_height*2+5, '1')-- cursor should movecheck_eq(Cursor1.line, 1, 'F - test_move_cursor_using_mouse/cursor:line')check_eq(Cursor1.pos, 26, 'F - test_move_cursor_using_mouse/cursor:pos')endfunction test_backspace_can_scroll_up()io.write('\ntest_backspace_can_scroll_up')-- display the lines 2/3/4 with the cursor on line 2App.screen.init{width=120, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = 120Cursor1 = {line=2, pos=1}Screen_top1 = {line=2, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'def', 'F - test_backspace_can_scroll_up/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_backspace_can_scroll_up/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up/baseline/screen:3')-- after hitting backspace the screen scrolls up by one lineApp.run_after_keychord('backspace')check_eq(Screen_top1.line, 1, 'F - test_backspace_can_scroll_up/screen_top')check_eq(Cursor1.line, 1, 'F - test_backspace_can_scroll_up/cursor')y = Margin_topApp.screen.check(y, 'abcdef', 'F - test_backspace_can_scroll_up/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_backspace_can_scroll_up/screen:2')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up/screen:3')endfunction test_backspace_can_scroll_up_screen_line()io.write('\ntest_backspace_can_scroll_up_screen_line')-- display lines starting from second screen line of a lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=3, pos=5}Screen_top1 = {line=3, pos=5}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up_screen_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_backspace_can_scroll_up_screen_line/baseline/screen:2')-- after hitting backspace the screen scrolls up by one screen lineApp.run_after_keychord('backspace')y = Margin_topApp.screen.check(y, 'ghijk', 'F - test_backspace_can_scroll_up_screen_line/screen:1')y = y + Line_heightApp.screen.check(y, 'l', 'F - test_backspace_can_scroll_up_screen_line/screen:2')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_backspace_can_scroll_up_screen_line/screen:3')check_eq(Screen_top1.line, 3, 'F - test_backspace_can_scroll_up_screen_line/screen_top')check_eq(Screen_top1.pos, 1, 'F - test_backspace_can_scroll_up_screen_line/screen_top')check_eq(Cursor1.line, 3, 'F - test_backspace_can_scroll_up_screen_line/cursor:line')check_eq(Cursor1.pos, 4, 'F - test_backspace_can_scroll_up_screen_line/cursor:pos')end-- some tests for operating over selections created using Shift- chords-- we're just testing delete_selection, and it works the same for all keysfunction test_backspace_over_selection()io.write('\ntest_backspace_over_selection')-- select just one character within a line with cursor before selectionApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Selection1 = {line=1, pos=2}-- backspace deletes the selected character, even though it's after the cursorApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'bc', "F - test_backspace_over_selection/data")-- cursor (remains) at start of selectioncheck_eq(Cursor1.line, 1, "F - test_backspace_over_selection/cursor:line")check_eq(Cursor1.pos, 1, "F - test_backspace_over_selection/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_over_selection/selection")endfunction test_backspace_over_selection_reverse()io.write('\ntest_backspace_over_selection_reverse')-- select just one character within a line with cursor after selectionApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Selection1 = {line=1, pos=1}-- backspace deletes the selected characterApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'bc', "F - test_backspace_over_selection_reverse/data")-- cursor moves to start of selectioncheck_eq(Cursor1.line, 1, "F - test_backspace_over_selection_reverse/cursor:line")check_eq(Cursor1.pos, 1, "F - test_backspace_over_selection_reverse/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_over_selection_reverse/selection")endfunction test_backspace_over_multiple_lines()io.write('\ntest_backspace_over_multiple_lines')-- select just one character within a line with cursor after selectionApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Selection1 = {line=4, pos=2}-- backspace deletes the region and joins the remaining portions of lines on either sideApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'akl', "F - test_backspace_over_multiple_lines/data:1")check_eq(Lines[2].data, 'mno', "F - test_backspace_over_multiple_lines/data:2")-- cursor remains at start of selectioncheck_eq(Cursor1.line, 1, "F - test_backspace_over_multiple_lines/cursor:line")check_eq(Cursor1.pos, 2, "F - test_backspace_over_multiple_lines/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_over_multiple_lines/selection")endfunction test_backspace_to_end_of_line()io.write('\ntest_backspace_to_end_of_line')-- select region from cursor to end of lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Selection1 = {line=1, pos=4}-- backspace deletes rest of line without joining to any other lineApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'a', "F - test_backspace_to_start_of_line/data:1")check_eq(Lines[2].data, 'def', "F - test_backspace_to_start_of_line/data:2")-- cursor remains at start of selectioncheck_eq(Cursor1.line, 1, "F - test_backspace_to_start_of_line/cursor:line")check_eq(Cursor1.pos, 2, "F - test_backspace_to_start_of_line/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_to_start_of_line/selection")endfunction test_backspace_to_start_of_line()io.write('\ntest_backspace_to_start_of_line')-- select region from cursor to start of lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=2, pos=1}Selection1 = {line=2, pos=3}-- backspace deletes beginning of line without joining to any other lineApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'abc', "F - test_backspace_to_start_of_line/data:1")check_eq(Lines[2].data, 'f', "F - test_backspace_to_start_of_line/data:2")-- cursor remains at start of selectioncheck_eq(Cursor1.line, 2, "F - test_backspace_to_start_of_line/cursor:line")check_eq(Cursor1.pos, 1, "F - test_backspace_to_start_of_line/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_to_start_of_line/selection")endfunction test_undo_insert_text()io.write('\ntest_undo_insert_text')App.screen.init{width=120, height=60}Lines = load_array{'abc', 'def', 'xyz'}Line_width = App.screen.widthCursor1 = {line=2, pos=4}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}-- insert a characterApp.run_after_textinput('g')check_eq(Cursor1.line, 2, 'F - test_undo_insert_text/baseline/cursor:line')check_eq(Cursor1.pos, 5, 'F - test_undo_insert_text/baseline/cursor:pos')check_nil(Selection1.line, 'F - test_undo_insert_text/baseline/selection:line')check_nil(Selection1.pos, 'F - test_undo_insert_text/baseline/selection:pos')local y = Margin_topApp.screen.check(y, 'abc', 'F - test_undo_insert_text/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'defg', 'F - test_undo_insert_text/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'xyz', 'F - test_undo_insert_text/baseline/screen:3')-- undoApp.run_after_keychord('C-z')check_eq(Cursor1.line, 2, 'F - test_undo_insert_text/cursor:line')check_eq(Cursor1.pos, 4, 'F - test_undo_insert_text/cursor:pos')check_nil(Selection1.line, 'F - test_undo_insert_text/selection:line')check_nil(Selection1.pos, 'F - test_undo_insert_text/selection:pos')y = Margin_topApp.screen.check(y, 'abc', 'F - test_undo_insert_text/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_undo_insert_text/screen:2')y = y + Line_heightApp.screen.check(y, 'xyz', 'F - test_undo_insert_text/screen:3')endfunction test_undo_delete_text()io.write('\ntest_undo_delete_text')App.screen.init{width=120, height=60}Lines = load_array{'abc', 'defg', 'xyz'}Line_width = App.screen.widthCursor1 = {line=2, pos=5}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}-- delete a characterApp.run_after_keychord('backspace')check_eq(Cursor1.line, 2, 'F - test_undo_delete_text/baseline/cursor:line')check_eq(Cursor1.pos, 4, 'F - test_undo_delete_text/baseline/cursor:pos')check_nil(Selection1.line, 'F - test_undo_delete_text/baseline/selection:line')check_nil(Selection1.pos, 'F - test_undo_delete_text/baseline/selection:pos')local y = Margin_topApp.screen.check(y, 'abc', 'F - test_undo_delete_text/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_undo_delete_text/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'xyz', 'F - test_undo_delete_text/baseline/screen:3')-- undo--? -- after undo, the backspaced key is selectedApp.run_after_keychord('C-z')check_eq(Cursor1.line, 2, 'F - test_undo_delete_text/cursor:line')check_eq(Cursor1.pos, 5, 'F - test_undo_delete_text/cursor:pos')check_nil(Selection1.line, 'F - test_undo_delete_text/selection:line')check_nil(Selection1.pos, 'F - test_undo_delete_text/selection:pos')--? check_eq(Selection1.line, 2, 'F - test_undo_delete_text/selection:line')--? check_eq(Selection1.pos, 4, 'F - test_undo_delete_text/selection:pos')y = Margin_topApp.screen.check(y, 'abc', 'F - test_undo_delete_text/screen:1')y = y + Line_heightApp.screen.check(y, 'defg', 'F - test_undo_delete_text/screen:2')y = y + Line_heightApp.screen.check(y, 'xyz', 'F - test_undo_delete_text/screen:3')end
function Text.draw_search_bar()local h = Line_height+2local y = App.screen.height-hlove.graphics.setColor(0.9,0.9,0.9)love.graphics.rectangle('fill', 0, y-10, App.screen.width-1, h+8)love.graphics.setColor(0.6,0.6,0.6)love.graphics.line(0, y-10, App.screen.width-1, y-10)love.graphics.setColor(1,1,1)love.graphics.rectangle('fill', 20, y-6, App.screen.width-40, h+2, 2,2)love.graphics.setColor(0.6,0.6,0.6)love.graphics.rectangle('line', 20, y-6, App.screen.width-40, h+2, 2,2)love.graphics.setColor(0,0,0)App.screen.print(Search_term, 25,y-5)love.graphics.setColor(1,0,0)if Search_text == nil thenSearch_text = App.newText(love.graphics.getFont(), Search_term)endlove.graphics.circle('fill', 25+App.width(Search_text),y-5+h, 2)love.graphics.setColor(0,0,0)endfunction Text.search_next()-- search current linelocal pos = Lines[Cursor1.line].data:find(Search_term, Cursor1.pos)if pos thenCursor1.pos = posendif pos == nil thenfor i=Cursor1.line+1,#Lines dopos = Lines[i].data:find(Search_term)if pos thenCursor1.line = iCursor1.pos = posbreakendendendif pos == nil then-- wrap aroundfor i=1,Cursor1.line-1 dopos = Lines[i].data:find(Search_term)if pos thenCursor1.line = iCursor1.pos = posbreakendendendif pos == nil thenCursor1.line = Search_backup.cursor.lineCursor1.pos = Search_backup.cursor.posScreen_top1.line = Search_backup.screen_top.lineScreen_top1.pos = Search_backup.screen_top.posendif Text.lt1(Cursor1, Screen_top1) or Text.lt1(Screen_bottom1, Cursor1) thenScreen_top1.line = Cursor1.linelocal _, pos = Text.pos_at_start_of_cursor_screen_line()Screen_top1.pos = posendendfunction Text.search_previous()-- search current linelocal pos = rfind(Lines[Cursor1.line].data, Search_term, Cursor1.pos)if pos thenCursor1.pos = posendif pos == nil thenfor i=Cursor1.line-1,1,-1 dopos = rfind(Lines[i].data, Search_term)if pos thenCursor1.line = iCursor1.pos = posbreakendendendif pos == nil then-- wrap aroundfor i=#Lines,Cursor1.line+1,-1 dopos = rfind(Lines[i].data, Search_term)if pos thenCursor1.line = iCursor1.pos = posbreakendendendif pos == nil thenCursor1.line = Search_backup.cursor.lineCursor1.pos = Search_backup.cursor.posScreen_top1.line = Search_backup.screen_top.lineScreen_top1.pos = Search_backup.screen_top.posendif Text.lt1(Cursor1, Screen_top1) or Text.lt1(Screen_bottom1, Cursor1) thenScreen_top1.line = Cursor1.linelocal _, pos = Text.pos_at_start_of_cursor_screen_line()Screen_top1.pos = posendendfunction rfind(s, pat, i)local rs = s:reverse()local rpat = pat:reverse()if i == nil then i = #s endlocal ri = #s - i + 1local rendpos = rs:find(rpat, ri)if rendpos == nil then return nil endlocal endpos = #s - rendpos + 1assert (endpos >= #pat)return endpos-#pat+1end-- Return any intersection of the region from Selection1 to 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 Selection1 and Cursor1 can be in any order.-- Result: positions spos,epos between apos,bpos.function Text.clip_selection(line_index, apos, bpos)if Selection1.line == nil then return nil,nil end-- min,max = sorted(Selection1,Cursor1)local minl,minp = Selection1.line,Selection1.poslocal maxl,maxpif love.mouse.isDown('1') thenmaxl,maxp = Text.mouse_pos()elsemaxl,maxp = Cursor1.line,Cursor1.posendif minl > maxl thenminl,maxl = maxl,minlminp,maxp = maxp,minpelseif minl == maxl thenif minp > maxp thenminp,maxp = maxp,minpendend-- check if intervals are disjointif line_index < minl then return nil,nil endif line_index > maxl then return nil,nil endif line_index == minl and bpos <= minp then return nil,nil endif line_index == maxl and apos >= maxp then return nil,nil end-- compare bounds more carefully (start inclusive, end exclusive)local a_ge = Text.le1({line=minl, pos=minp}, {line=line_index, pos=apos})local b_lt = Text.lt1({line=line_index, pos=bpos}, {line=maxl, pos=maxp})--? print(minl,line_index,maxl, '--', minp,apos,bpos,maxp, '--', a_ge,b_lt)if a_ge and b_lt then-- fully containedreturn apos,bposelseif a_ge thenassert(maxl == line_index)return apos,maxpelseif b_lt thenassert(minl == line_index)return minp,bposelseassert(minl == maxl and minl == line_index)return minp,maxpendend-- inefficient for some reason, so don't do it on every framefunction Text.mouse_pos()local time = love.timer.getTime()if Recent_mouse.time and Recent_mouse.time > time-0.1 thenreturn Recent_mouse.line, Recent_mouse.posendRecent_mouse.time = timelocal line,pos = Text.to_pos(love.mouse.getX(), love.mouse.getY())if line thenRecent_mouse.line = lineRecent_mouse.pos = posendreturn Recent_mouse.line, Recent_mouse.posendfunction Text.to_pos(x,y)for line_index,line in ipairs(Lines) doif line.mode == 'text' thenif Text.in_line(line, x,y) thenreturn line_index, Text.to_pos_on_line(line, x,y)endendendendfunction Text.delete_selection()local minl,maxl = minmax(Selection1.line, Cursor1.line)local before = snapshot(minl, maxl)Text.delete_selection_without_undo()record_undo_event({before=before, after=snapshot(Cursor1.line)})endfunction Text.delete_selection_without_undo()if Selection1.line == nil then return end-- min,max = sorted(Selection1,Cursor1)local minl,minp = Selection1.line,Selection1.poslocal maxl,maxp = Cursor1.line,Cursor1.posif minl > maxl thenminl,maxl = maxl,minlminp,maxp = maxp,minpelseif minl == maxl thenif minp > maxp thenminp,maxp = maxp,minpendend-- update Cursor1 and Selection1Cursor1.line = minlCursor1.pos = minpSelection1 = {}-- delete everything between min (inclusive) and max (exclusive)Lines[minl].fragments = nilLines[minl].screen_line_starting_pos = nillocal min_offset = utf8.offset(Lines[minl].data, minp)local max_offset = utf8.offset(Lines[maxl].data, maxp)if minl == maxl then--? print('minl == maxl')Lines[minl].data = Lines[minl].data:sub(1, min_offset-1)..Lines[minl].data:sub(max_offset)returnendassert(minl < maxl)local rhs = Lines[maxl].data:sub(max_offset)for i=maxl,minl+1,-1 dotable.remove(Lines, i)endLines[minl].data = Lines[minl].data:sub(1, min_offset-1)..rhsendfunction Text.selection()if Selection1.line == nil then return end-- min,max = sorted(Selection1,Cursor1)local minl,minp = Selection1.line,Selection1.poslocal maxl,maxp = Cursor1.line,Cursor1.posif minl > maxl thenminl,maxl = maxl,minlminp,maxp = maxp,minpelseif minl == maxl thenif minp > maxp thenminp,maxp = maxp,minpendendlocal min_offset = utf8.offset(Lines[minl].data, minp)local max_offset = utf8.offset(Lines[maxl].data, maxp)if minl == maxl thenreturn Lines[minl].data:sub(min_offset, max_offset-1)endassert(minl < maxl)local result = Lines[minl].data:sub(min_offset)..'\n'for i=minl+1,maxl-1 doif Lines[i].mode == 'text' thenresult = result..Lines[i].data..'\n'endendresult = result..Lines[maxl].data:sub(1, max_offset-1)return resultend
function test_draw_text()io.write('\ntest_draw_text')App.screen.init{width=120, height=60}Lines = load_array{'abc', 'def', 'ghi'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_draw_text/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_draw_text/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_draw_text/screen:3')endfunction test_draw_wrapping_text()io.write('\ntest_draw_wrapping_text')App.screen.init{width=50, height=60}Lines = load_array{'abc', 'defgh', 'xyz'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_draw_wrapping_text/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_draw_wrapping_text/screen:2')y = y + Line_heightApp.screen.check(y, 'gh', 'F - test_draw_wrapping_text/screen:3')endfunction test_draw_word_wrapping_text()io.write('\ntest_draw_word_wrapping_text')App.screen.init{width=60, height=60}Lines = load_array{'abc def ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc ', 'F - test_draw_word_wrapping_text/screen:1')y = y + Line_heightApp.screen.check(y, 'def ', 'F - test_draw_word_wrapping_text/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_draw_word_wrapping_text/screen:3')endfunction test_draw_text_wrapping_within_word()-- arrange a screen line that needs to be split within a wordio.write('\ntest_draw_text_wrapping_within_word')App.screen.init{width=60, height=60}Lines = load_array{'abcd e fghijk', 'xyz'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abcd ', 'F - test_draw_text_wrapping_within_word/screen:1')y = y + Line_heightApp.screen.check(y, 'e fghi', 'F - test_draw_text_wrapping_within_word/screen:2')y = y + Line_heightApp.screen.check(y, 'jk', 'F - test_draw_text_wrapping_within_word/screen:3')endfunction test_edit_wrapping_text()io.write('\ntest_edit_wrapping_text')App.screen.init{width=50, height=60}Lines = load_array{'abc', 'def', 'xyz'}Line_width = App.screen.widthCursor1 = {line=2, pos=4}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.run_after_textinput('g')App.run_after_textinput('h')App.run_after_textinput('i')App.run_after_textinput('j')App.run_after_textinput('k')App.run_after_textinput('l')local y = Margin_topApp.screen.check(y, 'abc', 'F - test_edit_wrapping_text/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_edit_wrapping_text/screen:2')y = y + Line_heightApp.screen.check(y, 'ghij', 'F - test_edit_wrapping_text/screen:3')endfunction test_insert_newline()io.write('\ntest_insert_newline')-- display a few lines with cursor on bottom lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_insert_newline/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_insert_newline/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_insert_newline/baseline/screen:3')-- after hitting the enter key the screen scrolls downApp.run_after_keychord('return')check_eq(Screen_top1.line, 1, 'F - test_insert_newline/screen_top')check_eq(Cursor1.line, 2, 'F - test_insert_newline/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_insert_newline/cursor:pos')y = Margin_topApp.screen.check(y, 'a', 'F - test_insert_newline/screen:1')y = y + Line_heightApp.screen.check(y, 'bc', 'F - test_insert_newline/screen:2')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_insert_newline/screen:3')endfunction test_insert_from_clipboard()io.write('\ntest_insert_from_clipboard')-- display a few lines with cursor on bottom lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_insert_from_clipboard/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_insert_from_clipboard/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_insert_from_clipboard/baseline/screen:3')-- after hitting the enter key the screen scrolls downApp.clipboard = 'xy\nz'App.run_after_keychord('C-v')check_eq(Screen_top1.line, 1, 'F - test_insert_from_clipboard/screen_top')check_eq(Cursor1.line, 2, 'F - test_insert_from_clipboard/cursor:line')check_eq(Cursor1.pos, 2, 'F - test_insert_from_clipboard/cursor:pos')y = Margin_topApp.screen.check(y, 'axy', 'F - test_insert_from_clipboard/screen:1')y = y + Line_heightApp.screen.check(y, 'zbc', 'F - test_insert_from_clipboard/screen:2')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_insert_from_clipboard/screen:3')endfunction test_move_cursor_using_mouse()io.write('\ntest_move_cursor_using_mouse')App.screen.init{width=50, height=60}Lines = load_array{'abc', 'def', 'xyz'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw() -- populate line.y for each line in Lineslocal screen_left_margin = 25 -- pixelsApp.run_after_mouserelease(screen_left_margin+8,Margin_top+5, '1')check_eq(Cursor1.line, 1, 'F - test_move_cursor_using_mouse/cursor:line')check_eq(Cursor1.pos, 2, 'F - test_move_cursor_using_mouse/cursor:pos')endfunction test_pagedown()io.write('\ntest_pagedown')App.screen.init{width=120, height=45}Lines = load_array{'abc', 'def', 'ghi'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}-- initially the first two lines are displayedApp.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_pagedown/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_pagedown/baseline/screen:2')-- after pagedown the bottom line becomes the topApp.run_after_keychord('pagedown')check_eq(Screen_top1.line, 2, 'F - test_pagedown/screen_top')check_eq(Cursor1.line, 2, 'F - test_pagedown/cursor')y = Margin_topApp.screen.check(y, 'def', 'F - test_pagedown/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_pagedown/screen:2')endfunction test_pagedown_skips_drawings()io.write('\ntest_pagedown_skips_drawings')-- some lines of text with a drawing intermixedApp.screen.init{width=50, height=80}Lines = load_array{'abc', -- height 15'```lines', '```', -- height 25'def', -- height 15'ghi'} -- height 15check_eq(Lines[2].mode, 'drawing', 'F - test_pagedown_skips_drawings/baseline/lines')Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}local drawing_height = 20 + App.screen.width / 2 -- default-- initially the screen displays the first line and the drawing-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80pxApp.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_pagedown_skips_drawings/baseline/screen:1')-- after pagedown the screen draws the drawing up top-- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80pxApp.run_after_keychord('pagedown')check_eq(Screen_top1.line, 2, 'F - test_pagedown_skips_drawings/screen_top')check_eq(Cursor1.line, 3, 'F - test_pagedown_skips_drawings/cursor')y = Margin_top + drawing_heightApp.screen.check(y, 'def', 'F - test_pagedown_skips_drawings/screen:1')endfunction test_pagedown_shows_one_screen_line_in_common()io.write('\ntest_pagedown_shows_one_screen_line_in_common')-- some lines of text with a drawing intermixedApp.screen.init{width=50, height=60}Lines = load_array{'abc', 'def ghi jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def ', 'F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi ', 'F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:3')-- after pagedown the bottom screen line becomes the topApp.run_after_keychord('pagedown')check_eq(Screen_top1.line, 2, 'F - test_pagedown_shows_one_screen_line_in_common/screen_top:line')check_eq(Screen_top1.pos, 5, 'F - test_pagedown_shows_one_screen_line_in_common/screen_top:pos')check_eq(Cursor1.line, 2, 'F - test_pagedown_shows_one_screen_line_in_common/cursor:line')check_eq(Cursor1.pos, 5, 'F - test_pagedown_shows_one_screen_line_in_common/cursor:pos')y = Margin_topApp.screen.check(y, 'ghi ', 'F - test_pagedown_shows_one_screen_line_in_common/screen:1')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_pagedown_shows_one_screen_line_in_common/screen:2')y = y + Line_heightApp.screen.check(y, 'mn', 'F - test_pagedown_shows_one_screen_line_in_common/screen:3')endfunction test_down_arrow_moves_cursor()io.write('\ntest_down_arrow_moves_cursor')App.screen.init{width=120, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}-- initially the first three lines are displayedApp.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_down_arrow_moves_cursor/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_down_arrow_moves_cursor/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_down_arrow_moves_cursor/baseline/screen:3')-- after hitting the down arrow, the cursor moves down by 1 lineApp.run_after_keychord('down')check_eq(Screen_top1.line, 1, 'F - test_down_arrow_moves_cursor/screen_top')check_eq(Cursor1.line, 2, 'F - test_down_arrow_moves_cursor/cursor')-- the screen is unchangedy = Margin_topApp.screen.check(y, 'abc', 'F - test_down_arrow_moves_cursor/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_down_arrow_moves_cursor/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_down_arrow_moves_cursor/screen:3')endfunction test_down_arrow_scrolls_down_by_one_line()io.write('\ntest_down_arrow_scrolls_down_by_one_line')-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=120, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=3, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:3')-- after hitting the down arrow the screen scrolls down by one lineApp.run_after_keychord('down')check_eq(Screen_top1.line, 2, 'F - test_down_arrow_scrolls_down_by_one_line/screen_top')check_eq(Cursor1.line, 4, 'F - test_down_arrow_scrolls_down_by_one_line/cursor')y = Margin_topApp.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_line/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_down_arrow_scrolls_down_by_one_line/screen:2')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_down_arrow_scrolls_down_by_one_line/screen:3')endfunction test_down_arrow_scrolls_down_by_one_screen_line()io.write('\ntest_down_arrow_scrolls_down_by_one_screen_line')-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=3, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi ', 'F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the down arrow the screen scrolls down by one lineApp.run_after_keychord('down')check_eq(Screen_top1.line, 2, 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen_top')check_eq(Cursor1.line, 3, 'F - test_down_arrow_scrolls_down_by_one_screen_line/cursor:line')check_eq(Cursor1.pos, 5, 'F - test_down_arrow_scrolls_down_by_one_screen_line/cursor:pos')y = Margin_topApp.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi ', 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen:2')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen:3')endfunction test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()io.write('\ntest_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word')-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghijkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=3, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghijk', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:3')-- after hitting the down arrow the screen scrolls down by one lineApp.run_after_keychord('down')check_eq(Screen_top1.line, 2, 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen_top')check_eq(Cursor1.line, 3, 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/cursor:line')check_eq(Cursor1.pos, 6, 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/cursor:pos')y = Margin_topApp.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:1')y = y + Line_heightApp.screen.check(y, 'ghijk', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:2')y = y + Line_heightApp.screen.check(y, 'l', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:3')endfunction 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')App.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghijkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=3, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghijk', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:3')-- after hitting pagedown the screen scrolls down to start of a long lineApp.run_after_keychord('pagedown')check_eq(Screen_top1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top')check_eq(Cursor1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_page_down_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.run_after_keychord('down')check_eq(Screen_top1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen_top')check_eq(Cursor1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line')check_eq(Cursor1.pos, 6, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos')y = Margin_topApp.screen.check(y, 'ghijk', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:1')y = y + Line_heightApp.screen.check(y, 'l', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:2')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:3')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}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = 120Cursor1 = {line=3, pos=1}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_up_arrow_moves_cursor/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_moves_cursor/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_moves_cursor/baseline/screen:3')-- after hitting the up arrow the cursor moves up by 1 lineApp.run_after_keychord('up')check_eq(Screen_top1.line, 1, 'F - test_up_arrow_moves_cursor/screen_top')check_eq(Cursor1.line, 2, 'F - test_up_arrow_moves_cursor/cursor')-- the screen is unchangedy = Margin_topApp.screen.check(y, 'abc', 'F - test_up_arrow_moves_cursor/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_moves_cursor/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_moves_cursor/screen:3')endfunction test_up_arrow_scrolls_up_by_one_line()io.write('\ntest_up_arrow_scrolls_up_by_one_line')-- display the lines 2/3/4 with the cursor on line 2App.screen.init{width=120, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = 120Cursor1 = {line=2, pos=1}Screen_top1 = {line=2, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:3')-- after hitting the up arrow the screen scrolls up by one lineApp.run_after_keychord('up')check_eq(Screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_by_one_line/screen_top')check_eq(Cursor1.line, 1, 'F - test_up_arrow_scrolls_up_by_one_line/cursor')y = Margin_topApp.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_by_one_line/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_by_one_line/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_by_one_line/screen:3')endfunction test_up_arrow_scrolls_up_by_one_screen_line()io.write('\ntest_up_arrow_scrolls_up_by_one_screen_line')-- display lines starting from second screen line of a lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=3, pos=6}Screen_top1 = {line=3, pos=5}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:2')-- after hitting the up arrow the screen scrolls up to first screen lineApp.run_after_keychord('up')y = Margin_topApp.screen.check(y, 'ghi ', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:1')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:2')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:3')check_eq(Screen_top1.line, 3, 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top')check_eq(Screen_top1.pos, 1, 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top')check_eq(Cursor1.line, 3, 'F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:pos')endfunction test_up_arrow_scrolls_up_to_final_screen_line()io.write('\ntest_up_arrow_scrolls_up_to_final_screen_line')-- display lines starting just after a long lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=2, pos=1}Screen_top1 = {line=2, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:3')-- after hitting the up arrow the screen scrolls up to final screen line of previous lineApp.run_after_keychord('up')y = Margin_topApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:2')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:3')check_eq(Screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top')check_eq(Screen_top1.pos, 5, 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top')check_eq(Cursor1.line, 1, 'F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:line')check_eq(Cursor1.pos, 5, 'F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:pos')endfunction test_up_arrow_scrolls_up_to_empty_line()io.write('\ntest_up_arrow_scrolls_up_to_empty_line')-- display a screenful of text with an empty line just above it outside the screenApp.screen.init{width=120, height=60}Lines = load_array{'', 'abc', 'def', 'ghi', 'jkl'}Line_width = 120Cursor1 = {line=2, pos=1}Screen_top1 = {line=2, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:3')-- after hitting the up arrow the screen scrolls up by one lineApp.run_after_keychord('up')check_eq(Screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_to_empty_line/screen_top')check_eq(Cursor1.line, 1, 'F - test_up_arrow_scrolls_up_to_empty_line/cursor')y = Margin_top-- empty first liney = y + Line_heightApp.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_to_empty_line/screen:2')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_empty_line/screen:3')endfunction test_pageup()io.write('\ntest_pageup')App.screen.init{width=120, height=45}Lines = load_array{'abc', 'def', 'ghi'}Line_width = App.screen.widthCursor1 = {line=2, pos=1}Screen_top1 = {line=2, pos=1}Screen_bottom1 = {}-- initially the last two lines are displayedApp.draw()local y = Margin_topApp.screen.check(y, 'def', 'F - test_pageup/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_pageup/baseline/screen:2')-- after pageup the cursor goes to first lineApp.run_after_keychord('pageup')check_eq(Screen_top1.line, 1, 'F - test_pageup/screen_top')check_eq(Cursor1.line, 1, 'F - test_pageup/cursor')y = Margin_topApp.screen.check(y, 'abc', 'F - test_pageup/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_pageup/screen:2')endfunction test_pageup_scrolls_up_by_screen_line()io.write('\ntest_pageup_scrolls_up_by_screen_line')-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=2, pos=1}Screen_top1 = {line=2, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'ghi', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the page-up key the screen scrolls up to topApp.run_after_keychord('pageup')check_eq(Screen_top1.line, 1, 'F - test_pageup_scrolls_up_by_screen_line/screen_top')check_eq(Cursor1.line, 1, 'F - test_pageup_scrolls_up_by_screen_line/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_pageup_scrolls_up_by_screen_line/cursor:pos')y = Margin_topApp.screen.check(y, 'abc ', 'F - test_pageup_scrolls_up_by_screen_line/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_pageup_scrolls_up_by_screen_line/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_pageup_scrolls_up_by_screen_line/screen:3')endfunction test_pageup_scrolls_up_from_middle_screen_line()io.write('\ntest_pageup_scrolls_up_from_middle_screen_line')-- display a few lines starting from the middle of a line (Cursor1.pos > 1)App.screen.init{width=25+30, height=60}Lines = load_array{'abc def', 'ghi jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=2, pos=5}Screen_top1 = {line=2, pos=5}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'jkl', 'F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the page-up key the screen scrolls up to topApp.run_after_keychord('pageup')check_eq(Screen_top1.line, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/screen_top')check_eq(Cursor1.line, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/cursor:pos')y = Margin_topApp.screen.check(y, 'abc ', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi ', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:3')endfunction test_enter_on_bottom_line_scrolls_down()io.write('\ntest_enter_on_bottom_line_scrolls_down')-- display a few lines with cursor on bottom lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=3, pos=2}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'abc', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:3')-- after hitting the enter key the screen scrolls downApp.run_after_keychord('return')check_eq(Screen_top1.line, 2, 'F - test_enter_on_bottom_line_scrolls_down/screen_top')check_eq(Cursor1.line, 4, 'F - test_enter_on_bottom_line_scrolls_down/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_enter_on_bottom_line_scrolls_down/cursor:pos')y = Margin_topApp.screen.check(y, 'def', 'F - test_enter_on_bottom_line_scrolls_down/screen:1')y = y + Line_heightApp.screen.check(y, 'g', 'F - test_enter_on_bottom_line_scrolls_down/screen:2')y = y + Line_heightApp.screen.check(y, 'hi', 'F - test_enter_on_bottom_line_scrolls_down/screen:3')endfunction test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()io.write('\ntest_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom')-- display just the bottom line on screenApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = App.screen.widthCursor1 = {line=4, pos=2}Screen_top1 = {line=4, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'jkl', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/baseline/screen:1')-- after hitting the enter key the screen does not scroll downApp.run_after_keychord('return')check_eq(Screen_top1.line, 4, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen_top')check_eq(Cursor1.line, 5, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:line')check_eq(Cursor1.pos, 1, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:pos')y = Margin_topApp.screen.check(y, 'j', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:1')y = y + Line_heightApp.screen.check(y, 'kl', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:2')endfunction test_position_cursor_on_recently_edited_wrapping_line()-- draw a line wrapping over 2 screen linesio.write('\ntest_position_cursor_on_recently_edited_wrapping_line')App.screen.init{width=120, height=200}Lines = load_array{'abc def ghi jkl mno pqr ', 'xyz'}Line_width = 100Cursor1 = {line=1, pos=25}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_top-- I don't understand why 120px fits so much on a fake screen, but whatever..App.screen.check(y, 'abc def ghi ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:1')y = y + Line_heightApp.screen.check(y, 'jkl mno pqr ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:2')y = y + Line_heightApp.screen.check(y, 'xyz', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:3')-- add to the line until it's wrapping over 3 screen linesApp.run_after_textinput('s')App.run_after_textinput('t')App.run_after_textinput('u')check_eq(Cursor1.pos, 28, 'F - test_move_cursor_using_mouse/cursor:pos')y = Margin_topApp.screen.check(y, 'abc def ghi ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:1')y = y + Line_heightApp.screen.check(y, 'jkl mno pqr ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:2')y = y + Line_heightApp.screen.check(y, 'stu', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:3')-- try to move the cursor earlier in the third screen line by clicking the mouselocal screen_left_margin = 25 -- pixelsApp.run_after_mouserelease(screen_left_margin+8,Margin_top+Line_height*2+5, '1')-- cursor should movecheck_eq(Cursor1.line, 1, 'F - test_move_cursor_using_mouse/cursor:line')check_eq(Cursor1.pos, 26, 'F - test_move_cursor_using_mouse/cursor:pos')endfunction test_backspace_can_scroll_up()io.write('\ntest_backspace_can_scroll_up')-- display the lines 2/3/4 with the cursor on line 2App.screen.init{width=120, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl'}Line_width = 120Cursor1 = {line=2, pos=1}Screen_top1 = {line=2, pos=1}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'def', 'F - test_backspace_can_scroll_up/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_backspace_can_scroll_up/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up/baseline/screen:3')-- after hitting backspace the screen scrolls up by one lineApp.run_after_keychord('backspace')check_eq(Screen_top1.line, 1, 'F - test_backspace_can_scroll_up/screen_top')check_eq(Cursor1.line, 1, 'F - test_backspace_can_scroll_up/cursor')y = Margin_topApp.screen.check(y, 'abcdef', 'F - test_backspace_can_scroll_up/screen:1')y = y + Line_heightApp.screen.check(y, 'ghi', 'F - test_backspace_can_scroll_up/screen:2')y = y + Line_heightApp.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up/screen:3')endfunction test_backspace_can_scroll_up_screen_line()io.write('\ntest_backspace_can_scroll_up_screen_line')-- display lines starting from second screen line of a lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=3, pos=5}Screen_top1 = {line=3, pos=5}Screen_bottom1 = {}App.draw()local y = Margin_topApp.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up_screen_line/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_backspace_can_scroll_up_screen_line/baseline/screen:2')-- after hitting backspace the screen scrolls up by one screen lineApp.run_after_keychord('backspace')y = Margin_topApp.screen.check(y, 'ghijk', 'F - test_backspace_can_scroll_up_screen_line/screen:1')y = y + Line_heightApp.screen.check(y, 'l', 'F - test_backspace_can_scroll_up_screen_line/screen:2')y = y + Line_heightApp.screen.check(y, 'mno', 'F - test_backspace_can_scroll_up_screen_line/screen:3')check_eq(Screen_top1.line, 3, 'F - test_backspace_can_scroll_up_screen_line/screen_top')check_eq(Screen_top1.pos, 1, 'F - test_backspace_can_scroll_up_screen_line/screen_top')check_eq(Cursor1.line, 3, 'F - test_backspace_can_scroll_up_screen_line/cursor:line')check_eq(Cursor1.pos, 4, 'F - test_backspace_can_scroll_up_screen_line/cursor:pos')end-- some tests for operating over selections created using Shift- chords-- we're just testing delete_selection, and it works the same for all keysfunction test_backspace_over_selection()io.write('\ntest_backspace_over_selection')-- select just one character within a line with cursor before selectionApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=1}Selection1 = {line=1, pos=2}-- backspace deletes the selected character, even though it's after the cursorApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'bc', "F - test_backspace_over_selection/data")-- cursor (remains) at start of selectioncheck_eq(Cursor1.line, 1, "F - test_backspace_over_selection/cursor:line")check_eq(Cursor1.pos, 1, "F - test_backspace_over_selection/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_over_selection/selection")endfunction test_backspace_over_selection_reverse()io.write('\ntest_backspace_over_selection_reverse')-- select just one character within a line with cursor after selectionApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Selection1 = {line=1, pos=1}-- backspace deletes the selected characterApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'bc', "F - test_backspace_over_selection_reverse/data")-- cursor moves to start of selectioncheck_eq(Cursor1.line, 1, "F - test_backspace_over_selection_reverse/cursor:line")check_eq(Cursor1.pos, 1, "F - test_backspace_over_selection_reverse/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_over_selection_reverse/selection")endfunction test_backspace_over_multiple_lines()io.write('\ntest_backspace_over_multiple_lines')-- select just one character within a line with cursor after selectionApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Selection1 = {line=4, pos=2}-- backspace deletes the region and joins the remaining portions of lines on either sideApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'akl', "F - test_backspace_over_multiple_lines/data:1")check_eq(Lines[2].data, 'mno', "F - test_backspace_over_multiple_lines/data:2")-- cursor remains at start of selectioncheck_eq(Cursor1.line, 1, "F - test_backspace_over_multiple_lines/cursor:line")check_eq(Cursor1.pos, 2, "F - test_backspace_over_multiple_lines/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_over_multiple_lines/selection")endfunction test_backspace_to_end_of_line()io.write('\ntest_backspace_to_end_of_line')-- select region from cursor to end of lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=1, pos=2}Selection1 = {line=1, pos=4}-- backspace deletes rest of line without joining to any other lineApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'a', "F - test_backspace_to_start_of_line/data:1")check_eq(Lines[2].data, 'def', "F - test_backspace_to_start_of_line/data:2")-- cursor remains at start of selectioncheck_eq(Cursor1.line, 1, "F - test_backspace_to_start_of_line/cursor:line")check_eq(Cursor1.pos, 2, "F - test_backspace_to_start_of_line/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_to_start_of_line/selection")endfunction test_backspace_to_start_of_line()io.write('\ntest_backspace_to_start_of_line')-- select region from cursor to start of lineApp.screen.init{width=25+30, height=60}Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}Line_width = App.screen.widthCursor1 = {line=2, pos=1}Selection1 = {line=2, pos=3}-- backspace deletes beginning of line without joining to any other lineApp.run_after_keychord('backspace')check_eq(Lines[1].data, 'abc', "F - test_backspace_to_start_of_line/data:1")check_eq(Lines[2].data, 'f', "F - test_backspace_to_start_of_line/data:2")-- cursor remains at start of selectioncheck_eq(Cursor1.line, 2, "F - test_backspace_to_start_of_line/cursor:line")check_eq(Cursor1.pos, 1, "F - test_backspace_to_start_of_line/cursor:pos")-- selection is clearedcheck_nil(Selection1.line, "F - test_backspace_to_start_of_line/selection")endfunction test_undo_insert_text()io.write('\ntest_undo_insert_text')App.screen.init{width=120, height=60}Lines = load_array{'abc', 'def', 'xyz'}Line_width = App.screen.widthCursor1 = {line=2, pos=4}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}-- insert a characterApp.run_after_textinput('g')check_eq(Cursor1.line, 2, 'F - test_undo_insert_text/baseline/cursor:line')check_eq(Cursor1.pos, 5, 'F - test_undo_insert_text/baseline/cursor:pos')check_nil(Selection1.line, 'F - test_undo_insert_text/baseline/selection:line')check_nil(Selection1.pos, 'F - test_undo_insert_text/baseline/selection:pos')local y = Margin_topApp.screen.check(y, 'abc', 'F - test_undo_insert_text/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'defg', 'F - test_undo_insert_text/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'xyz', 'F - test_undo_insert_text/baseline/screen:3')-- undoApp.run_after_keychord('C-z')check_eq(Cursor1.line, 2, 'F - test_undo_insert_text/cursor:line')check_eq(Cursor1.pos, 4, 'F - test_undo_insert_text/cursor:pos')check_nil(Selection1.line, 'F - test_undo_insert_text/selection:line')check_nil(Selection1.pos, 'F - test_undo_insert_text/selection:pos')y = Margin_topApp.screen.check(y, 'abc', 'F - test_undo_insert_text/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_undo_insert_text/screen:2')y = y + Line_heightApp.screen.check(y, 'xyz', 'F - test_undo_insert_text/screen:3')endfunction test_undo_delete_text()io.write('\ntest_undo_delete_text')App.screen.init{width=120, height=60}Lines = load_array{'abc', 'defg', 'xyz'}Line_width = App.screen.widthCursor1 = {line=2, pos=5}Screen_top1 = {line=1, pos=1}Screen_bottom1 = {}-- delete a characterApp.run_after_keychord('backspace')check_eq(Cursor1.line, 2, 'F - test_undo_delete_text/baseline/cursor:line')check_eq(Cursor1.pos, 4, 'F - test_undo_delete_text/baseline/cursor:pos')check_nil(Selection1.line, 'F - test_undo_delete_text/baseline/selection:line')check_nil(Selection1.pos, 'F - test_undo_delete_text/baseline/selection:pos')local y = Margin_topApp.screen.check(y, 'abc', 'F - test_undo_delete_text/baseline/screen:1')y = y + Line_heightApp.screen.check(y, 'def', 'F - test_undo_delete_text/baseline/screen:2')y = y + Line_heightApp.screen.check(y, 'xyz', 'F - test_undo_delete_text/baseline/screen:3')-- undo--? -- after undo, the backspaced key is selectedApp.run_after_keychord('C-z')check_eq(Cursor1.line, 2, 'F - test_undo_delete_text/cursor:line')check_eq(Cursor1.pos, 5, 'F - test_undo_delete_text/cursor:pos')check_nil(Selection1.line, 'F - test_undo_delete_text/selection:line')check_nil(Selection1.pos, 'F - test_undo_delete_text/selection:pos')--? check_eq(Selection1.line, 2, 'F - test_undo_delete_text/selection:line')--? check_eq(Selection1.pos, 4, 'F - test_undo_delete_text/selection:pos')y = Margin_topApp.screen.check(y, 'abc', 'F - test_undo_delete_text/screen:1')y = y + Line_heightApp.screen.check(y, 'defg', 'F - test_undo_delete_text/screen:2')y = y + Line_heightApp.screen.check(y, 'xyz', 'F - test_undo_delete_text/screen:3')end
-- helpers for selecting portions of textlocal utf8 = require 'utf8'-- Return any intersection of the region from Selection1 to 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 Selection1 and Cursor1 can be in any order.-- Result: positions spos,epos between apos,bpos.function Text.clip_selection(line_index, apos, bpos)if Selection1.line == nil then return nil,nil end-- min,max = sorted(Selection1,Cursor1)local minl,minp = Selection1.line,Selection1.poslocal maxl,maxpif love.mouse.isDown('1') thenmaxl,maxp = Text.mouse_pos()elsemaxl,maxp = Cursor1.line,Cursor1.posendif minl > maxl thenminl,maxl = maxl,minlminp,maxp = maxp,minpelseif minl == maxl thenif minp > maxp thenminp,maxp = maxp,minpendend-- check if intervals are disjointif line_index < minl then return nil,nil endif line_index > maxl then return nil,nil endif line_index == minl and bpos <= minp then return nil,nil endif line_index == maxl and apos >= maxp then return nil,nil end-- compare bounds more carefully (start inclusive, end exclusive)local a_ge = Text.le1({line=minl, pos=minp}, {line=line_index, pos=apos})local b_lt = Text.lt1({line=line_index, pos=bpos}, {line=maxl, pos=maxp})--? print(minl,line_index,maxl, '--', minp,apos,bpos,maxp, '--', a_ge,b_lt)if a_ge and b_lt then-- fully containedreturn apos,bposelseif a_ge thenassert(maxl == line_index)return apos,maxpelseif b_lt thenassert(minl == line_index)return minp,bposelseassert(minl == maxl and minl == line_index)return minp,maxpendend-- inefficient for some reason, so don't do it on every framefunction Text.mouse_pos()local time = love.timer.getTime()if Recent_mouse.time and Recent_mouse.time > time-0.1 thenreturn Recent_mouse.line, Recent_mouse.posendRecent_mouse.time = timelocal line,pos = Text.to_pos(love.mouse.getX(), love.mouse.getY())if line thenRecent_mouse.line = lineRecent_mouse.pos = posendreturn Recent_mouse.line, Recent_mouse.posendfunction Text.to_pos(x,y)for line_index,line in ipairs(Lines) doif line.mode == 'text' thenif Text.in_line(line, x,y) thenreturn line_index, Text.to_pos_on_line(line, x,y)endendendendfunction Text.delete_selection()local minl,maxl = minmax(Selection1.line, Cursor1.line)local before = snapshot(minl, maxl)Text.delete_selection_without_undo()record_undo_event({before=before, after=snapshot(Cursor1.line)})endfunction Text.delete_selection_without_undo()if Selection1.line == nil then return end-- min,max = sorted(Selection1,Cursor1)local minl,minp = Selection1.line,Selection1.poslocal maxl,maxp = Cursor1.line,Cursor1.posif minl > maxl thenminl,maxl = maxl,minlminp,maxp = maxp,minpelseif minl == maxl thenif minp > maxp thenminp,maxp = maxp,minpendend-- update Cursor1 and Selection1Cursor1.line = minlCursor1.pos = minpSelection1 = {}-- delete everything between min (inclusive) and max (exclusive)Lines[minl].fragments = nilLines[minl].screen_line_starting_pos = nillocal min_offset = utf8.offset(Lines[minl].data, minp)local max_offset = utf8.offset(Lines[maxl].data, maxp)if minl == maxl then--? print('minl == maxl')Lines[minl].data = Lines[minl].data:sub(1, min_offset-1)..Lines[minl].data:sub(max_offset)returnendassert(minl < maxl)local rhs = Lines[maxl].data:sub(max_offset)for i=maxl,minl+1,-1 dotable.remove(Lines, i)endLines[minl].data = Lines[minl].data:sub(1, min_offset-1)..rhsendfunction Text.selection()if Selection1.line == nil then return end-- min,max = sorted(Selection1,Cursor1)local minl,minp = Selection1.line,Selection1.poslocal maxl,maxp = Cursor1.line,Cursor1.posif minl > maxl thenminl,maxl = maxl,minlminp,maxp = maxp,minpelseif minl == maxl thenif minp > maxp thenminp,maxp = maxp,minpendendlocal min_offset = utf8.offset(Lines[minl].data, minp)local max_offset = utf8.offset(Lines[maxl].data, maxp)if minl == maxl thenreturn Lines[minl].data:sub(min_offset, max_offset-1)endassert(minl < maxl)local result = Lines[minl].data:sub(min_offset)..'\n'for i=minl+1,maxl-1 doif Lines[i].mode == 'text' thenresult = result..Lines[i].data..'\n'endendresult = result..Lines[maxl].data:sub(1, max_offset-1)return resultendfunction Text.cut_selection()local result = Text.selection()Text.delete_selection()return resultend
-- helpers for the search bar (C-f)function Text.draw_search_bar()local h = Line_height+2local y = App.screen.height-hlove.graphics.setColor(0.9,0.9,0.9)love.graphics.rectangle('fill', 0, y-10, App.screen.width-1, h+8)love.graphics.setColor(0.6,0.6,0.6)love.graphics.line(0, y-10, App.screen.width-1, y-10)love.graphics.setColor(1,1,1)love.graphics.rectangle('fill', 20, y-6, App.screen.width-40, h+2, 2,2)love.graphics.setColor(0.6,0.6,0.6)love.graphics.rectangle('line', 20, y-6, App.screen.width-40, h+2, 2,2)love.graphics.setColor(0,0,0)App.screen.print(Search_term, 25,y-5)love.graphics.setColor(1,0,0)if Search_text == nil thenSearch_text = App.newText(love.graphics.getFont(), Search_term)endlove.graphics.circle('fill', 25+App.width(Search_text),y-5+h, 2)love.graphics.setColor(0,0,0)endfunction Text.search_next()-- search current linelocal pos = Lines[Cursor1.line].data:find(Search_term, Cursor1.pos)if pos thenCursor1.pos = posendif pos == nil thenfor i=Cursor1.line+1,#Lines dopos = Lines[i].data:find(Search_term)if pos thenCursor1.line = iCursor1.pos = posbreakendendendif pos == nil then-- wrap aroundfor i=1,Cursor1.line-1 dopos = Lines[i].data:find(Search_term)if pos thenCursor1.line = iCursor1.pos = posbreakendendendif pos == nil thenCursor1.line = Search_backup.cursor.lineCursor1.pos = Search_backup.cursor.posScreen_top1.line = Search_backup.screen_top.lineScreen_top1.pos = Search_backup.screen_top.posendif Text.lt1(Cursor1, Screen_top1) or Text.lt1(Screen_bottom1, Cursor1) thenScreen_top1.line = Cursor1.linelocal _, pos = Text.pos_at_start_of_cursor_screen_line()Screen_top1.pos = posendendfunction Text.search_previous()-- search current linelocal pos = rfind(Lines[Cursor1.line].data, Search_term, Cursor1.pos)if pos thenCursor1.pos = posendif pos == nil thenfor i=Cursor1.line-1,1,-1 dopos = rfind(Lines[i].data, Search_term)if pos thenCursor1.line = iCursor1.pos = posbreakendendendif pos == nil then-- wrap aroundfor i=#Lines,Cursor1.line+1,-1 dopos = rfind(Lines[i].data, Search_term)if pos thenCursor1.line = iCursor1.pos = posbreakendendendif pos == nil thenCursor1.line = Search_backup.cursor.lineCursor1.pos = Search_backup.cursor.posScreen_top1.line = Search_backup.screen_top.lineScreen_top1.pos = Search_backup.screen_top.posendif Text.lt1(Cursor1, Screen_top1) or Text.lt1(Screen_bottom1, Cursor1) thenScreen_top1.line = Cursor1.linelocal _, pos = Text.pos_at_start_of_cursor_screen_line()Screen_top1.pos = posendendfunction rfind(s, pat, i)local rs = s:reverse()local rpat = pat:reverse()if i == nil then i = #s endlocal ri = #s - i + 1local rendpos = rs:find(rpat, ri)if rendpos == nil then return nil endlocal endpos = #s - rendpos + 1assert (endpos >= #pat)return endpos-#pat+1end