I'm not sure this is very useful. I had an initial idea to stop using screen_bottom1 in final_text_loc_on_screen, by starting from screen_top1 rather than screen_bottom1. But that changes the direction in which we scan for the text line in situations where there is somehow no text on screen (something that should never happen but I have zero confidence in that).
Still, it doesn't seem like a bad thing to drastically reduce the lifetime of some derived state.
Really what I need to do is throw this whole UX out and allow the cursor to be on a drawing as a whole. So up arrow or left arrow below a drawing would focus the whole drawing in a red border, and another up arrow and left arrow would skip the drawing and continue upward. I think that change to the UX will eliminate a whole class of special cases in the code.
ZS5IYZH5EXXPSVIFWS7XW5POEVRRCK6XV6PB36D3EJXJRT22LKOQC 2EKE4XLLUF44XPHJOJ53SAUP3TUDM572HWXMHJ5UVIYIQICRNEVAC 6NYMNNADRZEZWL3ISZ2I7N7DFLZPFRGGLWPO25TDA7QLVX52HWQQC 3TI67SEJNOSADDEHRTI5FSRD7WVNRTXQ5LC77LBSWLXXZK3MCONQC TXI6GSQDOUHU4DWQTCMLVVCA2YSIAUZJWSFMH22QPO3W4NGNRF5AC 4GYPLUDYEF4NPB3HUGSOXAMCZ3UEV5ODM35IRA4DYL5IPDVDHD3QC VDFARWQXPIQUTVRFLZ6QOYSG2ON7CLQVKRHJJZZAEMO7QLTNXXHQC CUFW4EJL75OAA5BS5EXGTM5RMRNJOBBPAXUJADGZ3VLP2ZMKFOTAC 2MGBV7NPY2RVK7EZ7OJRWBYDUMADFZAFSBKLQFX74ERBVZODJLUQC PXSQR2ADSFEOG4SMIRHUZ2HGBKUQIYA73HZU4IRPR2UG2RBAVUJAC G3DLS5OUO77V4MC6754KTETRCTVUBYBHMGR7MTV52IYYM7QA3ROQC FHNPQBLKB5EEJA4XDF2QJBCKB4W56LYWHJLVYA7ZPUSKM27QWWNQC QAMVLUK22RP5RBDTDV5XVPQCSJUWDWESV4TRCUTNUM46E26BH2AQC JYB3RFWHD6IXYGHJ77BEILJ5QNRPPH77DITIH4PKZM4D4OZSHRVQC LF7BWEG4DKQI7NMXMZC4LC2BE5PB42HK5PD6OYBNIDMAZBJASOKQC LXTTOB33N2HCUZFIUDRQGGBVHK2HODRG4NBLH6RXRQZDCHF27BSAC 5BMR5HRT7GN5L4XB4ISP4JJP3ONZESHEEQBCTQE4EVEDL7MBSDGAC EKKFWP4D2MNOHU265UCJU37KIFQV424CRLVASQMHDYUYY5T67D3QC 656FM555BRGLHJ7PTIZXD2IY5T7PGFEYSHG2T3Z7WNX6QZ5KROSAC 6RYLD5ONDIQFWU5CNL4NGHJQ2LNAZZFGTPXQJDNJGLNYAUOTUI7QC YFW4MNNPY452RIUIGW6NO7WAUWPBXXOKOJJBNZGU727PUBZFATSAC 6XCJX4DZB6UEAXEXXUGVVPBCF5SNDYOJGU3Q6BPB4ZMI5DZH4MYQC 7RKFA3VAGK73SLC3NCOKHVMOWLTC4EUQYS47P3LVVKDVLBVGBEHAC ORRSP7FVCHI2TF5GXBRGQYYJAA3JFYXZBM3T663BKSBV22FCZVCAC CVSRHMJ2BM4LPVG67ULIVQMP2NW3YY2JC2ZQBEA6EB5KVM4O2L5AC CNCYMM6ABOXCRI2IP5A4T2OGBO5FQ7GWBXBP2OQYL4YET5BLJCGQC UHB4GARJI5AB5UCDCZRFSCJNXGJSLU5DYGUGX5ITYEXI7Q43Z4CAC ILOA5BYFTQKBSHLFMMZUVPQ2JXBFJD62ERQFBTDK2WSRXUN525VQC LNUHQOGHIOFGJXNGA3DZLYEASLYYDGLN2I3EDZY5ANASQAHCG3YQC APYPFFS3G6TDEUMIHQGMDBJNRNDTCNTPKI5M2AFACJ73P725XQRQC Y4SPXCM3PKARGUU22FNBEDRU7S6CJSNYVAA76JYH4I4EMMMKP6LQC SWZAQHGRX3T5MZVM7YZ4EACZX5ON2NUZG5YXLWVEXLL3WZUZYAFQC LK4ZW4BBDD5LC4JK4XK5DJESSDFAIRVFPDM324S7SCAUXEXYVTLQC FZBXBUFFNRE5ZJO5DLRU375HOXT2B7FO35XD7BTHHUXSARVWDFLQC LLAOOMULEBXFMIGRBY6LRVEK4RXQGPNTFVWMCZNUEJZHWC7UGUEAC MSOQI3A5BC5PY2MZXZQAQ4EQDT4KICQJPN3YUZVDYTWXSPZWBLIAC AMOPICKVRHMQERJLFPMAAEBV7TL5QACGGSBJWRCMV5R5O3KDVETAC BW2IUB3KA4AKD35DYLCUCUM4Z32FMKGZNUBQBAEDIQJJYPA547MAC BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC H2DPLWMVRFYTO2CQTG54FMT2LF3B6UKLXH32CUA22DNQJVP5XBNQC I64IPGJXWRTGHHVAYJUBUIWFR4BY6NM5P7TLTV4JOD7K4BVYDECQC HALS7E5UGKCP3DFY456F7Z3Y6WNGIABOCV2SHT34D5ZAGNCPV5PQC XNFTJHC4QSHNSIWNN7K6QZEZ37GTQYKHS4EPNSVPQCUSWREROGIQC MTJEVRJR5GLWUSK7HMIM4UXM6GS6O6YCRWJT3DUSU2RYMHCQNOEQC 3MAZEQK5AR3IJJ2ENHHYDPDICIK645NE5QWR54Z52BHGHE6VR5XQC Z5HLXU4PJWWJJDBCK52NBD6PIRIA3TAN2BKZB5HBYFGIDBX4F5HAC SPSW74Y5OJ54Y7VQ3SJFCJR5CYDKTR4A3TOEVZODDZLUSDDU2GZAC QCPXQ2E3USF3Z6R6WJ2JKHTRMPKA6QWXFKKRMLXA3MXABJEL543AC IRCKL6VNSFB7TQEKPQUPJCN37N5QW7D54DSZMESVXGK7NEHGSIPAC MXA3RZYKUI4UF2ISY7JEF6VKX6NOPZMZH5SLLCZHRJKFIXXXDPSAC KZ5GAYRPWF2BA5VEIW3A4G2TULATBL7YEDGFJU42GBP5DET7BI3AC ZHLO7K3MQNI6OMK6226SSO2Z6Z4ZXF4T73VOG36DVAG6CHR6OHWAC KOYAJWE4NJ2J4X3SHEAVMRXYZPZGOMTI7OX3PTUQIDIZ2GQI6UKAC 5OALPNN3FGDKFM4K5EQZV6FU6GCKHEVSJDXM6XFFC7LGXES7GLWQC YPHKZVWM2FS7U3VNVDXFRJTBF4RLQ6K7ZWISLHOQJPYSKBELHFEAC TGZAJUEFRK3NTCDMPIIG7U2TGLDHK4U3JDNFAYX7NHXTJYBYEZIAC ZPUQSPQPQFVRUIHGLAWW3IDBYODIWDHO62HAC3WWF5TM3CIJGHNQC OI4FPFINEROK6GNDEMOBTGSPYIULCLRGGT5W3H7VLM7VFH22GMWQC 2LOQ5ALJYHWSMU7ROSKD66BYGMK3O6HYNUQMGCZVKTRDOLEI75NQC GCEF4N3VW2JFTWVXU2ND5XA63BNTMEGRBQQXYA3HULAKGYOYJP7AC OGUV4HSA7XGSQLUVWBAE3AE263Z7Z6G3BZOB4CN2AOYD2DEJMOZAC SVJZZDC3K6AKAXHGRNAZKRE2ZXEKJANNLG7LSSUZJARFBL5F7C4AC HTWAM4NZFOY463TNSKYIM2EWB7QNBGDRRTTGHF5N3Z4TGC7Q3SFAC KOTI3MFGQ4PDS4I75JIJG734LTET6745VGTSMNFYYASVIO6H2KPAC MP2TBKU6CNDMZKENYMBV62F5KQ27ZWEVPVRFS2RESVDQQT2IRR4AC EAEGCJV5JOW46KCZKKPBFKZ4Z3SDB3X4R7TLNXFWCIQN5UCNSXFQC HOSPP2ANSW654DYRTC6CQUQA2GUKV6T2FI7QBKXD2DZS3R32IMGAC WOXIYUTL4NU7ACHQYXEXJDSXCRDLQ2X457KO6C7GEXFQZ43F3L7QC ELJNEPW26FUIIFY6D24274J7KZICRLE3TJHCFNRVLR5NZBNNV37AC YJJ4X4JGABMVA5JBQW5UAWI543P3Y7NDVFTOHA6LIDA5KSFGUFNQC WLJCIXYMSTCNSYCFOEBQNDLBZ5D2Z3WTF4E4WYL5CFGIJ434FKNQC K4OBZSHEBIZBAKPH3F7ASDGCPLB7D5W5QLFJQYSM5XOYDPB4BUHAC QLTJG7Q33ABBTDJ55K3OPLNSYBFBIVRS3UABXEY73RHYMOOJ542QC FKNXK2OAH4U2V2TXCHWE4C3Z5DROBIIPSXUWFKP7Q3DSNOKFFL5AC GNQC72UXBU6KYXW6MXLNRGTLXV2VPQXMVCLYMJT6POTFXSF5ASJAC NHNP76LGNIVNIDMSDILAKEVSWFQ4LKNCYXVQEGKKJ75TSRPEBVEQC KKMFQDR43ZWVCDRHQLWWX3FCWCFA3ZSXYOBRJNPHUQZR2XPKWULAC KMSL74GAMFNTAKGDKZFP2AMQXUMOC3XH373BO4IABZWBEP3YAXKAC 5SM6DRHKPLFWQPCZDTVM4ENVENWBYBQ3Q2KYKSWLWYUTOSEPCRLAC GZ5WULJVEZJJQPQPSQZE7CEPIYPJ2BJDYUJBMZRA5HLOO7TE3DOQC G54H3YG2NEZPW2F6OYT5JPV7KSKVMNW5D3QT3FBCXTJHAQYTV5UAC GFXWHTE6POBIOBUMRAWD5QS22JEO52EF4VTLMB4CDK4RLSCK7HCAC OP643FFG5WQWHLPLYZ2VTDJYXK6VQ3NODRDPJNVDN26CF3ESM5RAC 5ZA3BRNYWKSGEBJ4JLA4UBC3LJPT5JBWYCU7PQYRSGX6MJMEWDIQC 2L5MEZV344TOZLVY3432RHJFIRVXFD6O3GWLL5O4CV66BGAFTURQC WJBZZQE4A4KLYGS2KA254I6VN2DVXDY4XKCNAE76GTMLLQGYCUOQC 3QNOKBFMKBGXBVJIRHR2444JRRMBTABHE4674NR3DT67RRM2X6GAC 4EGQRXDANFLUYXADP3MNHZWP2LBH2P5VBVKNN5RT6ERGMBVSRI2AC endfunction test_pagedown_often_shows_start_of_wrapping_line()-- draw a few lines ending in part of a wrapping lineApp.screen.init{width=50, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def ', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'baseline/screen:3')-- after pagedown we start drawing from the bottom _line_ (multiple screen lines)edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')check_eq(Editor_state.screen_top1.line, 2, 'screen_top:line')check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')check_eq(Editor_state.cursor1.line, 2, 'cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')y = Editor_state.topApp.screen.check(y, 'def ', 'screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'screen:3')
--? 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(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('=>', 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 leaksendfunction Text.previous_screen_top1(State)
local top2 = Text.to2(State, State.screen_top1)--? print(App.screen.height)
-- 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')-- If a line/paragraph gets to a page boundary, I often want to scroll-- before I get to the bottom.-- However, only do this if it makes forward progress.local bot2 = Text.to2(State, State.screen_bottom1)if bot2.screen_line > 1 thenbot2.screen_line = math.max(bot2.screen_line-10, 1)endlocal new_top1 = Text.to1(State, bot2)if Text.lt1(State.screen_top1, new_top1) thenState.screen_top1 = new_top1elseState.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}end--? 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)
--? 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)
endendfunction 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}
--? 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)
--? 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 leaksendfunction Text.previous_screen_top1(State)
--? 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)
--? 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)
endendfunction 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}
-- 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)