CRBLAWBOTECOU5MFURWCRBR43OO7NWIHP5LC35IMGTQKN73AUS6AC CGASWIHLBVXWKY6Y6JZUSZ7CMWAR2PH43BTJT7CPRYA26TVJL4EAC ESH2GCJ65I27ODSOBQHEGPLUTWEEFQGNTTTEYU2I227L534YNJPQC NBTKJTI5MDKJNTD7JAORTXPPKTYVLVT4DEWSPRYBQAM6QVWNWXCAC GVJEOWYQMHB2YEI5O6YUGHWDNSEINZJY7XM7D5HOFC2MN24J4YRQC TMWSQNZD3AOV2SMW5E2CKG7ICZLAHPWHDELOG53A4LCF73HFAP4AC N33WSVVOATNVQAY25QOGUMMYT7FKV5BP5AIYCJKBQLKHYE3PAJ4AC NSM73TX3N6HN6CYVX5LHHWQTHOZITHWIWNOWXZQROBGP6KSDEPBAC R5QXEHUIZLELJGGCZAE7ATNS3CLRJ7JFRENMGH4XXH24C5WABZDQC KKMFQDR43ZWVCDRHQLWWX3FCWCFA3ZSXYOBRJNPHUQZR2XPKWULAC OI4FPFINEROK6GNDEMOBTGSPYIULCLRGGT5W3H7VLM7VFH22GMWQC JYB3RFWHD6IXYGHJ77BEILJ5QNRPPH77DITIH4PKZM4D4OZSHRVQC KOTNETIMJP2G753SAAQHR5LNOIC7LWLTFSY3QXA276L3TUN63UHQC 2TCIWW6ZC73FHPZLO5QOBUFZCIL74U2MBYTPNXFYYFJ7IH4YAGUQC KMSL74GAMFNTAKGDKZFP2AMQXUMOC3XH373BO4IABZWBEP3YAXKAC 5SM6DRHKPLFWQPCZDTVM4ENVENWBYBQ3Q2KYKSWLWYUTOSEPCRLAC ORRSP7FVCHI2TF5GXBRGQYYJAA3JFYXZBM3T663BKSBV22FCZVCAC ZS5IYZH5EXXPSVIFWS7XW5POEVRRCK6XV6PB36D3EJXJRT22LKOQC G3DLS5OUO77V4MC6754KTETRCTVUBYBHMGR7MTV52IYYM7QA3ROQC G54H3YG2NEZPW2F6OYT5JPV7KSKVMNW5D3QT3FBCXTJHAQYTV5UAC GZ5WULJVEZJJQPQPSQZE7CEPIYPJ2BJDYUJBMZRA5HLOO7TE3DOQC PXSQR2ADSFEOG4SMIRHUZ2HGBKUQIYA73HZU4IRPR2UG2RBAVUJAC 656FM555BRGLHJ7PTIZXD2IY5T7PGFEYSHG2T3Z7WNX6QZ5KROSAC TXI6GSQDOUHU4DWQTCMLVVCA2YSIAUZJWSFMH22QPO3W4NGNRF5AC KYNGDE2CKNOKUC2XMAS5MEU6YT2C3IW5SIZLOJE64G3ERT7BSWFAC 2MGBV7NPY2RVK7EZ7OJRWBYDUMADFZAFSBKLQFX74ERBVZODJLUQC DSLD74DK3P6J2VAFCYF5BGTHZ637QTW3PDHOUHFACDZU66YNM3IAC QAMVLUK22RP5RBDTDV5XVPQCSJUWDWESV4TRCUTNUM46E26BH2AQC LXTTOB33N2HCUZFIUDRQGGBVHK2HODRG4NBLH6RXRQZDCHF27BSAC MD3W5IRAC6UQALQE4LJC52VQNDO3I3HXF3XE2XHDABXBYJBUVAXQC BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC Z5HLXU4PJWWJJDBCK52NBD6PIRIA3TAN2BKZB5HBYFGIDBX4F5HAC UHB4GARJI5AB5UCDCZRFSCJNXGJSLU5DYGUGX5ITYEXI7Q43Z4CAC KOYAJWE4NJ2J4X3SHEAVMRXYZPZGOMTI7OX3PTUQIDIZ2GQI6UKAC KKQKPGCIHAG2JESQAWEMCBTAKBDC5AVIQ6LCZ2ORQM2AUCFQYLSQC MXA3RZYKUI4UF2ISY7JEF6VKX6NOPZMZH5SLLCZHRJKFIXXXDPSAC ZPUQSPQPQFVRUIHGLAWW3IDBYODIWDHO62HAC3WWF5TM3CIJGHNQC TOXPJJYYZY7QRCVXJJDGDAM7NBSHEXNPRW5X4THW55FTSIZ3GKVQC MUJTM6REGQAK3LZTIFWGJRXE2UPCM4HSLXQYSF5ITLXLS6JCVPMQC LF7BWEG4DKQI7NMXMZC4LC2BE5PB42HK5PD6OYBNIDMAZBJASOKQC QCPXQ2E3USF3Z6R6WJ2JKHTRMPKA6QWXFKKRMLXA3MXABJEL543AC QCQTMUZ7M3BKJFTKXTTXL4TS4CAQNIUNK3LR3WQIJDU3VVTOPS6AC 2EKE4XLLUF44XPHJOJ53SAUP3TUDM572HWXMHJ5UVIYIQICRNEVAC GNQC72UXBU6KYXW6MXLNRGTLXV2VPQXMVCLYMJT6POTFXSF5ASJAC KV7GGVERB4IOIWQJUK2RYBZZEUDMLCAUV3DIX7J7JISDCYRIQCGAC TGHAJBESCIEGWUE2D3FGLNOIAYT4D2IRGZKRXRMTUFW7QZETC7OAC ILOA5BYFTQKBSHLFMMZUVPQ2JXBFJD62ERQFBTDK2WSRXUN525VQC AQMZJXUR5NFNATJ4LPTVGVLQFIKRKRSPYAICXWHGQCQ4WLMQ2JTQC CUIV2LE5D6GUQ4NU7K2TGUVO5CTUXVJDRCZUIV47LXTOUSEPEJHQC HTWAM4NZFOY463TNSKYIM2EWB7QNBGDRRTTGHF5N3Z4TGC7Q3SFAC 6XCJX4DZB6UEAXEXXUGVVPBCF5SNDYOJGU3Q6BPB4ZMI5DZH4MYQC 3PSFWAILGRA4OYXWS2DX7VF332AIBPYBXHEA4GIQY2XEJVD65UMAC JOPVPUSAMMU6RFVDQR4NJC4GNNUFB7GPKVH7OS5FKCYS5QZ53VLQC MBAJPTDJ4KHWACEHWYGCFMHPQYM6FQKCSIIDKWCE765UI3VTDMIAC A4BSGS2CX4JK7IELL655EC6HAY6ILCWTGIHWZXHRGQOKU3HSUPLAC ORKN6EOBUFVAD2TXYW5OIKSL55RU24LOFDTTTXHDZUZ57QRDCY7QC YXQOITYSGYQQBOMF7CJ33C2CTDAI5R2W2RCRSKJ6JTLV7MS4WVXAC D4B52CQ2QKG2HQKFUQOO5S2ME325DTW3PH2D7SBXCW4BPQFYG7CAC ONHKBLLCD5NDO3HSSUMAMGJ7HDT53JYVV56DI42AEYI3W63GKACQC B4FAIVRAXKCS2ZZXYUHL7HHWVMDMG2MQ3BY6IOJARK7B5XTBAQNAC 2TQUKHBC2EB3WDBD5UL62DQYV7CV6B7OJYK7CHOEDNOZENSOG42AC KWIVKQQ7AANRG6R4ZRB5TDBZ2TZTXAXIR2P6JNT362KIAJ7JQ4VQC 34BZ5ZKNAB4XQGXOPVBZHBDYD5D3X4V6T72XZSR5LJXF4UIVSWQAC MXSAHZN4AGTGEXFTMREVVYJLR2KY7X2Z4SUYKEPO2TGSVYR6N5MQC VOU73AK6XOVIOCY6PHUXS5RQZ2TGFEF7RYNOKFE2XSHRCZBAJMYQC 2JBAEQHUJZRKTFH6FYMEQMPE2WFE2GS6VDLDHINCJ66SFKZQQSLQC XS3PZI7GCTJQNIB2BJRFBBXJZGVEOUVKSD442YDWZHEGIUAWMFQAC ZLJYLPOTXIVBVWJ4NTRM2YCQPT2FCSN7446P56MJFEFY45QTB7IAC VHQCNMARPMNBSIUFLJG7HVK4QGDNPCGNVFLHS3I4IGNVSV5MRLYQC FNJF2FMQJPQDVLHDZTVMUX5R3LGRKLGSNUPTHUT2WZY3RKVSNRIQC 4KC7I3E2DIKLIP7LQRKB5WFA2Z5XZXAU46RFHNFQU5BVEJPDX6UQC R2ASHK5CEE3PTRLS37GP4PXJ7HIGJ6UD72KKBI57UDJI7VRROQGQC 2L5MEZV344TOZLVY3432RHJFIRVXFD6O3GWLL5O4CV66BGAFTURQC VP5KC4XZBKD536KCBFO47UKH74RNONENDCFJAHUMVUTPVPGJWD4AC K2X6G75Z6XBC4DVIRWC5HC7XA3A2SKOM3MWSQTCFEYWIJL7LME2QC 7CLGG7J277QZGMNOVFEXBX6DRETCVK7GH66HJ7BYOBMPHTJCDFMAC BLWAYPKV3MLDZ4ALXLUJ25AIR6PCIL4RFYNRYLB26GFVC2KQBYBAC JFFUF5ALUWPDM7IEDEZVAYG2SVXO334STONRGKVB3QKY2TT5QGBQC 7JH2ZT3FCW4WX52IJFSLGRVZWQQVYJXULSQM2BM4BJUZWG3IVGKAC R3XGABERHYD7X42VXYENLVRSNFUXO5JAO4HCX3N4CMHZCHWEIW6AC FZCKGO2IA5ZMX7TD4HL3WBUR6NPOYYMT4RGVWF7KFQBFV2V3RZBAC 2CK5QI7WA7M4IVSACFGOJYAIDKRUTZVMMPSFWEJTUNMWTN7AX4NAC HYEAFRZ2UEKDYTAE2GDQLHEJBPQASP2NDLMXB7F6MTVK2BKOXKEAC KTZQ57HVZU4XGWRPXBA27G4GXZFV74YYKJRXCJCE7UKDS7NGJVBAC 3OTESDW65UJ2W5RIXA6FNKRSD7TBCZTCCCEAYOQMEVGYZ6RCU34QC QFC3WRDZEZJM3UICG2R7YZL35JJNWKAML4KLHCDX2ZDKREWV44GQC PJEQCTBL2ZX5Q7NJQ3KCWSHHBV2QWGYS5NVMRDNK5LOOIIRRIPHAC BXJMGTV2FMXDI5ML3OATRJ6O35L3T64S4TW4UYM37V3K3DICSC4AC DRFE3B3ZKRG4RY2R5Q3SDFD3LH4EXUX3CZCDFBNAXVI2SLDS57PAC QXVD2RIFQPTO3H6J3IJHRHRDRBW6C6MLUWMCEDQ6Z75SW2TMRBIQC SRVDX4I5QKWAH3Y5DX25PG34U7NY55H46ZYG2APH47BUZT3EJ2HAC 23MA4T3GWPOLM5S6JCNQJU2SRT7VQGYZ2JZJN26KA5MKI4LOCC4QC XNFTJHC4QSHNSIWNN7K6QZEZ37GTQYKHS4EPNSVPQCUSWREROGIQC IDGP4BJZTKAD6ZO4RLAWYVN6IFCMIM76G6HJGPTE27K4D6CDBUHQC FS2ITYYHBLFT66YUC3ENPFYI2HOYHOVEPQIN7NQR6KF5MEK4NKZAC App.screen.init{width=800, height=600}edit.run_after_keychord(Editor_state, 'M-right', 'right')edit.run_after_keychord(Editor_state, 'M-right', 'right')edit.draw(Editor_state) -- populate line_cache.startpos for each lineedit.run_after_keychord(Editor_state, 'S-right', 'right')edit.run_after_keychord(Editor_state, 'right', 'right')edit.draw(Editor_state) -- populate line_cache.startpos for each lineedit.draw(Editor_state) -- populate line_cache.startpos for each lineedit.draw(Editor_state) -- populate line_cache.startpos for each lineedit.draw(Editor_state) -- populate line_cache.startpos for each lineedit.run_after_keychord(Editor_state, 'right', 'right')check_eq(Editor_state.screen_top1.line, 2, 'screen_top')check_eq(Editor_state.cursor1.line, 3, 'cursor:line')check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')-- click on first locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- hold down shift and click on a second locationApp.fake_key_press('lshift')edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)-- hold down shift and click at a third locationApp.fake_key_press('lshift')edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height+5, 1)App.fake_key_release('lshift')-- selection is between first and third location. forget the second location, not the first.-- click on first locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- hold down shift and click somewhere elseApp.fake_key_press('lshift')edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)App.fake_key_release('lshift')-- press mouse above first line of textedit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')check_eq(Editor_state.selection1.line, 1, 'selection:line')check_eq(Editor_state.selection1.pos, 1, 'selection:pos')-- press and hold on first locationedit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- drag and release somewhere elseedit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)-- no change to data, selection is resetApp.fake_key_release('lshift')edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)check_eq(Editor_state.cursor1.line, 2, 'line')check_eq(Editor_state.cursor1.pos, 4, 'pos')check_eq(Editor_state.cursor1.pos, 9, 'check')Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{}Text.redraw_all(Editor_state)edit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, 8,Editor_state.top+8, 1)-- cursor skips drawing to always remain on text
-- return y for the next linereturn y--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)State.screen_top1 = Text.previous_screen_top1(State)State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaksendfunction Text.previous_screen_top1(State)-- does not modify State (except to populate line_cache)local loc2 = Text.to2(State, State.screen_top1)if loc2.line == 1 and loc2.screen_line == 1 and loc2.screen_pos == 1 then break endif State.lines[loc2.line].mode == 'text' thenelseif State.lines[loc2.line].mode == 'drawing' theny = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width)loc2 = Text.previous_screen_line(State, loc2)return Text.to1(State, loc2)State.screen_top1 = Text.screen_bottom1(State)end-- return the location of the start of the bottom-most line on screenfunction Text.screen_bottom1(State)-- duplicate some logic from love.draw-- does not modify State (except to populate line_cache)local loc2 = Text.to2(State, State.screen_top1)local y = State.topwhile true doif State.lines[loc2.line].mode == 'text' theny = y + State.line_heightelseif State.lines[loc2.line].mode == 'drawing' theny = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width)endif y + State.line_height > App.screen.height then break endlocal next_loc2 = Text.next_screen_line(State, loc2)if Text.eq2(next_loc2, loc2) then break endloc2 = next_loc2endreturn Text.to1(State, loc2)--? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)local screen_bottom1 = Text.screen_bottom1(State)--? print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos)if State.cursor1.line > screen_bottom1.line thenlocal screen_bottom1 = Text.screen_bottom1(State)local scroll_down = Text.le1(screen_bottom1, State.cursor1)--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)assert(State.lines[loc1.line].mode == 'text')function Text.final_text_loc_on_screen(State)local screen_bottom1 = Text.screen_bottom1(State)if State.lines[screen_bottom1.line].mode == 'text' thenreturn {line=screen_bottom1.line,pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),}endlocal loc2 = Text.to2(State, screen_bottom1)while true doif State.lines[loc2.line].mode == 'text' then break endassert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1) -- elsewhere we're making sure there's always at least one text line on screenloc2 = Text.previous_screen_line(State, loc2)endlocal result = Text.to1(State, loc2)result.pos = Text.pos_at_end_of_screen_line(State, result)return resultend--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)local starty = Text.starty(State, line_index)if starty == nil then return false end -- outside current pageif y < starty then return false endreturn y < starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1)local starty = Text.starty(State, line_index)assert(my >= starty, 'failed to map y pixel to line')local y = startyfunction Text.eq2(a, b)return a.line == b.line and a.screen_line == b.screen_line and a.screen_pos == b.screen_posendendendfunction Text.next_screen_line(State, loc2)if State.lines[loc2.line].mode == 'drawing' thenreturn {line=loc2.line+1, screen_line=1, screen_pos=1}endText.populate_screen_line_starting_pos(State, loc2.line)if loc2.screen_line >= #State.line_cache[loc2.line].screen_line_starting_pos thenif loc2.line < #State.lines thenreturn {line=loc2.line+1, screen_line=1, screen_pos=1}elsereturn loc2endelsereturn {line=loc2.line, screen_line=loc2.screen_line+1, screen_pos=1}local screen_bottom1 = Text.screen_bottom1(State)elseif State.cursor1.line >= screen_bottom1.line thenState.cursor1 = Text.final_text_loc_on_screen(State)endendend-- slightly expensive since it redraws the screenfunction Text.cursor_out_of_screen(State)if Text.lt1(State.cursor1, State.screen_top1) thenState.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}endendfunction Text.offset(s, pos1)if pos1 == 1 then return 1 endlocal result = utf8.offset(s, pos1)if result == nil thenlocal start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos)for screen_line_index = start_screen_line_index,#line_cache.screen_line_starting_pos dolocal screen_line_starting_pos = line_cache.screen_line_starting_pos[screen_line_index]local screen_line_starting_byte_offset = Text.offset(line.data, screen_line_starting_pos)--? print('iter', y, screen_line_index, screen_line_starting_pos, string.sub(line.data, screen_line_starting_byte_offset))-- duplicate some logic from Text.drawend-- convert mx,my in pixels to schema-1 coordinatesText.populate_screen_line_starting_pos(State, line_index)Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks--? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down')local y = App.screen.height - State.line_height-- duplicate some logic from love.drawwhile true dofunction Text.cursor_at_final_screen_line(State)Text.populate_screen_line_starting_pos(State, State.cursor1.line)Text.populate_screen_line_starting_pos(State, loc1.line)local line_cache = State.line_cache[loc1.line]local most_recent_final_pos = utf8.len(State.lines[loc1.line].data)+1for i=#line_cache.screen_line_starting_pos,1,-1 dolocal spos = line_cache.screen_line_starting_pos[i]if spos <= loc1.pos thenreturn most_recent_final_posendmost_recent_final_pos = spos-1endendfunction Text.start_of_line(State)--? print('cursor is NOT at final screen line of its line')local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)Text.populate_screen_line_starting_pos(State, State.cursor1.line)local new_screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos[screen_line_index+1]--? print('switching pos of screen line at cursor from '..tostring(screen_line_starting_pos)..' to '..tostring(new_screen_line_starting_pos))local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, new_screen_line_starting_pos)local s = string.sub(State.lines[State.cursor1.line].data, new_screen_line_starting_byte_offset)--? print('screen top before:', State.screen_top1.line, State.screen_top1.pos)--? print('scroll up preserving cursor')Text.snap_cursor_to_bottom_of_screen(State)--? print('screen top after:', State.screen_top1.line, State.screen_top1.pos)endassert(State.cursor1.pos, 'cursor has no pos')endfunction Text.up(State)State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}endfunction Text.pagedown(State)endendy = y - State.line_heightlocal y = App.screen.height - State.line_heightwhile y >= State.top do-- duplicate some logic from love.draw-- return the top y coordinate of a given line_index,-- or nil if no part of it is on screenfunction Text.starty(State, line_index)-- duplicate some logic from love.draw-- does not modify State (except to populate line_cache)if line_index < State.screen_top1.line then return endlocal loc2 = Text.to2(State, State.screen_top1)local y = State.topwhile true doif loc2.line == line_index then return y endif State.lines[loc2.line].mode == 'text' theny = y + State.line_heightelseif State.lines[loc2.line].mode == 'drawing' theny = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottomendif y + State.line_height > App.screen.height then break endlocal next_loc2 = Text.next_screen_line(State, loc2)if Text.eq2(next_loc2, loc2) then break end -- end of fileloc2 = next_loc2endendif State.lines[loc2.line].mode == 'drawing' theny = y + Drawing_padding_topendendschedule_save(State)record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})elseif chord == 'backspace' thenText.insert_at_cursor(State, '\t')if State.cursor_y > App.screen.height - State.line_height thenText.populate_screen_line_starting_pos(State, State.cursor1.line)Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)Text.insert_at_cursor(State, t)if State.cursor_y > App.screen.height - State.line_height thenText.populate_screen_line_starting_pos(State, State.cursor1.line)Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)endrecord_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})endfunction Text.insert_at_cursor(State, t)endfunction Text.screen_line(line, line_cache, i)local pos = line_cache.screen_line_starting_pos[i]local offset = Text.offset(line.data, pos)if i >= #line_cache.screen_line_starting_pos thenreturn line.data:sub(offset)function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers)
---- On lines that are drawings, pos will be nil.cursor1 = {line=1, pos=1}, -- position of cursor; must be on a text liney = Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers)if Drawing.in_drawing(State, line_index, x, y, State.left,State.right) thenState.selection1 = Text.final_text_loc_on_screen(State)State.cursor1 = Text.final_text_loc_on_screen(State)State.cursor1 = Text.screen_bottom1(State)chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(key) thenedit.draw(State)App.fake_mouse_release(x,y, mouse_button)Text.delete_selection(State, State.left, State.right)endedit.put_cursor_on_next_text_line(State)edit.clean_up_mouse_press(State)endState.lines.current_drawing_index = line_indexState.lines.current_drawing = lineDrawing.before = snapshot(State, line_index)--? print('=> y', y)elseif line.mode == 'drawing' theny = y+Drawing_padding_topDrawing.draw(State, line_index, y)y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottomelsescreen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen
if State.lines[loc2.line].mode == 'text' thenelseif State.lines[loc2.line].mode == 'drawing' theny = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width)
y = y - State.line_height
--? print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos)
print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos)
--? print('screen top before:', State.screen_top1.line, State.screen_top1.pos)--? print('scroll up preserving cursor')
print('screen top before:', State.screen_top1.line, State.screen_top1.pos)print('scroll up preserving cursor')
--? print('switching pos of screen line at cursor from '..tostring(screen_line_starting_pos)..' to '..tostring(new_screen_line_starting_pos))
print('switching pos of screen line at cursor from '..tostring(screen_line_starting_pos)..' to '..tostring(new_screen_line_starting_pos))
if State.lines[screen_bottom1.line].mode == 'text' thenreturn {line=screen_bottom1.line,pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),}endlocal loc2 = Text.to2(State, screen_bottom1)while true doif State.lines[loc2.line].mode == 'text' then break endassert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1) -- elsewhere we're making sure there's always at least one text line on screenloc2 = Text.previous_screen_line(State, loc2)endlocal result = Text.to1(State, loc2)result.pos = Text.pos_at_end_of_screen_line(State, result)return result
return {line=screen_bottom1.line,pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),}
--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print('pageup')
State.screen_top1 = Text.previous_screen_top1(State)State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaksend-- return the top y coordinate of a given line_index,-- or nil if no part of it is on screenfunction Text.starty(State, line_index)
local top2 = Text.to2(State, State.screen_top1)--? print(App.screen.height)
-- does not modify State (except to populate line_cache)if line_index < State.screen_top1.line then return endlocal loc2 = Text.to2(State, State.screen_top1)local y = State.topwhile true doif State.lines[loc2.line].mode == 'drawing' theny = y + Drawing_padding_topendif loc2.line == line_index then return y endif State.lines[loc2.line].mode == 'text' theny = y + State.line_heightelseif State.lines[loc2.line].mode == 'drawing' theny = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottomendif y + State.line_height > App.screen.height then break endlocal next_loc2 = Text.next_screen_line(State, loc2)if Text.eq2(next_loc2, loc2) then break end -- end of fileloc2 = next_loc2endendfunction Text.previous_screen_top1(State)-- duplicate some logic from love.draw-- does not modify State (except to populate line_cache)local loc2 = Text.to2(State, State.screen_top1)
--? print(y, top2.line, top2.screen_line, top2.screen_pos)if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break endif State.lines[State.screen_top1.line].mode == 'text' then
if loc2.line == 1 and loc2.screen_line == 1 and loc2.screen_pos == 1 then break endif State.lines[loc2.line].mode == 'text' then
elseif State.lines[State.screen_top1.line].mode == 'drawing' theny = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
elseif State.lines[loc2.line].mode == 'drawing' theny = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width)
State.screen_top1 = Text.to1(State, top2)State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)--? print(State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)--? print('pageup end')
return Text.to1(State, loc2)
--? print('pagedown')State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}--? print('setting top to', State.screen_top1.line, State.screen_top1.pos)
State.screen_top1 = Text.screen_bottom1(State)
--? print('pagedown end')
end-- return the location of the start of the bottom-most line on screenfunction Text.screen_bottom1(State)-- duplicate some logic from love.draw-- does not modify State (except to populate line_cache)local loc2 = Text.to2(State, State.screen_top1)local y = State.topwhile true doif State.lines[loc2.line].mode == 'text' theny = y + State.line_heightelseif State.lines[loc2.line].mode == 'drawing' theny = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width)endif y + State.line_height > App.screen.height then break endlocal next_loc2 = Text.next_screen_line(State, loc2)if Text.eq2(next_loc2, loc2) then break endloc2 = next_loc2endreturn Text.to1(State, loc2)
--? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
--? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
if State.cursor1.line > State.screen_bottom1.line then
local screen_bottom1 = Text.screen_bottom1(State)--? print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos)if State.cursor1.line > screen_bottom1.line then
--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
endfunction Text.final_text_loc_on_screen(State)local screen_bottom1 = Text.screen_bottom1(State)if State.lines[screen_bottom1.line].mode == 'text' thenreturn {line=screen_bottom1.line,pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),}endlocal loc2 = Text.to2(State, screen_bottom1)while true doif State.lines[loc2.line].mode == 'text' then break endassert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1) -- elsewhere we're making sure there's always at least one text line on screenloc2 = Text.previous_screen_line(State, loc2)endlocal result = Text.to1(State, loc2)result.pos = Text.pos_at_end_of_screen_line(State, result)return result
--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
if line_cache.starty == nil then return false end -- outside current pageif y < line_cache.starty then return false end
local starty = Text.starty(State, line_index)if starty == nil then return false end -- outside current pageif y < starty then return false end
return y < line_cache.starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1)
return y < starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1)
function Text.next_screen_line(State, loc2)if State.lines[loc2.line].mode == 'drawing' thenreturn {line=loc2.line+1, screen_line=1, screen_pos=1}endText.populate_screen_line_starting_pos(State, loc2.line)if loc2.screen_line >= #State.line_cache[loc2.line].screen_line_starting_pos thenif loc2.line < #State.lines thenreturn {line=loc2.line+1, screen_line=1, screen_pos=1}elsereturn loc2endelsereturn {line=loc2.line, screen_line=loc2.screen_line+1, screen_pos=1}endend
--? print('tweak')State.cursor1 = {line=State.screen_bottom1.line,pos=Text.to_pos_on_line(State, State.screen_bottom1.line, State.right-5, App.screen.height-5),}
State.cursor1 = Text.final_text_loc_on_screen(State)
-- this approach is cheaper and almost works, except on the final screen-- where file ends above bottom of screen--? local botpos = Text.pos_at_start_of_screen_line(State, State.cursor1)--? local botline1 = {line=State.cursor1.line, pos=botpos}--? return Text.lt1(State.screen_bottom1, botline1)
chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(chord) then
chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(key) then
love.graphics.rectangle('fill', State.left,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty))
love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty))
love.graphics.rectangle('fill', State.left,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty))
love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty))
local starty = Text.starty(State, drawing_index)local y = starty+10love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty))local starty = Text.starty(State, drawing_index)local y = starty+10love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty))
State.cursor1 = Text.screen_bottom1(State)y = Text.draw(State, line_index, y, startpos)if Drawing.in_drawing(State, line_index, x, y, State.left,State.right) thenedit.draw(State)State.cursor1 = Text.final_text_loc_on_screen(State)State.selection1 = Text.final_text_loc_on_screen(State)
check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
if pmx < State.right and pmy > line_cache.starty and pmy < line_cache.starty+Drawing.pixels(line.h, State.width) then
local starty = Text.starty(State, line_index)if pmx < State.right and pmy > starty and pmy < starty+Drawing.pixels(line.h, State.width) then
local starty = Text.starty(State, line_index)if pmx < State.right and pmy > starty and pmy < starty+Drawing.pixels(line.h, State.width) thenlove.graphics.rectangle('line', State.left,starty, State.width,Drawing.pixels(line.h, State.width))icon[State.current_drawing_mode](State.right-22, starty+4)icon[State.previous_drawing_mode](State.right-22, starty+4)local my = Drawing.coord(pmy-starty, State.width)Drawing.draw_shape(line, shape, starty, State.left,State.right)local function py(y) return Drawing.pixels(y, State.width)+starty endDrawing.draw_pending_shape(line, starty, State.left,State.right)
endfunction Drawing.in_drawing(drawing, line_cache, x,y, left,right)if line_cache.starty == nil then return false end -- outside current pagelocal width = right-leftreturn y >= line_cache.starty and y < line_cache.starty + Drawing.pixels(drawing.h, width) and x >= left and x < right
function Drawing.in_current_drawing(State, x,y, left,right)return Drawing.in_drawing(State, State.lines.current_drawing_index, x,y, left,right)endfunction Drawing.in_drawing(State, line_index, x,y, left,right)assert(State.lines[line_index].mode == 'drawing')local starty = Text.starty(State, line_index)if starty == nil then return false end -- outside current pagelocal drawing = State.lines[line_index]return y >= starty and y < starty + Drawing.pixels(drawing.h, width) and x >= left and x < right
local starty = Text.starty(State, State.lines.current_drawing_index)if starty == nil thenlocal my = Drawing.coord(pmy-starty, State.width)if Drawing.in_current_drawing(State, pmx,pmy, State.left,State.right) thenif Drawing.in_current_drawing(State, pmx, pmy, State.left,State.right) then
local starty = Text.starty(State, State.lines.current_drawing_index)local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), starty+Drawing.pixels(p2.y, State.width))local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
local _,drawing,line_cache = Drawing.current_drawing(State)local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
local drawing_index,drawing = Drawing.current_drawing(State)local starty = Text.starty(State, drawing_index)local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)
local _,drawing,line_cache = Drawing.current_drawing(State)local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
local drawing_index,drawing = Drawing.current_drawing(State)local starty = Text.starty(State, drawing_index)local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)
local line_cache = State.line_cache[drawing_index]if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) thenreturn drawing_index,drawing,line_cache
if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) thenreturn drawing_index,drawing
local line_cache = State.line_cache[drawing_index]if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
local starty = Text.starty(State, drawing_index)if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
local line_cache = State.line_cache[drawing_index]if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
local starty = Text.starty(State, drawing_index)if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
local drawing_index,drawing = Drawing.current_drawing(State)local starty = Text.starty(State, drawing_index)local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)local drawing_index,drawing = Drawing.current_drawing(State)local starty = Text.starty(State, drawing_index)local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)local drawing_index,drawing = Drawing.current_drawing(State)local starty = Text.starty(State, drawing_index)local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)local drawing_index,drawing,_,i,p = Drawing.select_point_at_mouse(State)local drawing_index,drawing,_,point_index,p = Drawing.select_point_at_mouse(State)if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) thenreturn drawing_index,drawinglocal starty = Text.starty(State, drawing_index)if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)return drawing,starty,i,shapelocal starty = Text.starty(State, drawing_index)if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)return drawing_index,drawing,starty,i,pointif Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then