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 line
edit.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 line
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.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 location
edit.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 location
App.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 location
App.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 location
edit.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 else
App.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 text
edit.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 location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- drag and release somewhere else
edit.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 reset
App.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 line
return 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 leaks
end
function 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 end
if State.lines[loc2.line].mode == 'text' then
elseif State.lines[loc2.line].mode == 'drawing' then
y = 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 screen
function 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.top
while true do
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width)
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end
loc2 = next_loc2
end
return 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 then
local 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' then
return {
line=screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),
}
end
local loc2 = Text.to2(State, screen_bottom1)
while true do
if State.lines[loc2.line].mode == 'text' then break end
assert(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 screen
loc2 = Text.previous_screen_line(State, loc2)
end
local result = Text.to1(State, loc2)
result.pos = Text.pos_at_end_of_screen_line(State, result)
return result
end
--? 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 page
if y < starty then return false end
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)
local starty = Text.starty(State, line_index)
assert(my >= starty, 'failed to map y pixel to line')
local y = starty
function Text.eq2(a, b)
return a.line == b.line and a.screen_line == b.screen_line and a.screen_pos == b.screen_pos
end
end
end
function Text.next_screen_line(State, loc2)
if State.lines[loc2.line].mode == 'drawing' then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
end
Text.populate_screen_line_starting_pos(State, loc2.line)
if loc2.screen_line >= #State.line_cache[loc2.line].screen_line_starting_pos then
if loc2.line < #State.lines then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
else
return loc2
end
else
return {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 then
State.cursor1 = Text.final_text_loc_on_screen(State)
end
end
end
-- slightly expensive since it redraws the screen
function Text.cursor_out_of_screen(State)
if Text.lt1(State.cursor1, State.screen_top1) then
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
end
end
function Text.offset(s, pos1)
if pos1 == 1 then return 1 end
local result = utf8.offset(s, pos1)
if result == nil then
local 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 do
local 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.draw
end
-- convert mx,my in pixels to schema-1 coordinates
Text.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.draw
while true do
function 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)+1
for i=#line_cache.screen_line_starting_pos,1,-1 do
local spos = line_cache.screen_line_starting_pos[i]
if spos <= loc1.pos then
return most_recent_final_pos
end
most_recent_final_pos = spos-1
end
end
function 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)
end
assert(State.cursor1.pos, 'cursor has no pos')
end
function Text.up(State)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
end
function Text.pagedown(State)
end
end
y = y - State.line_height
local y = App.screen.height - State.line_height
while 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 screen
function 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 end
local loc2 = Text.to2(State, State.screen_top1)
local y = State.top
while true do
if loc2.line == line_index then return y end
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottom
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end -- end of file
loc2 = next_loc2
end
end
if State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_top
end
end
schedule_save(State)
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
elseif chord == 'backspace' then
Text.insert_at_cursor(State, '\t')
if State.cursor_y > App.screen.height - State.line_height then
Text.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 then
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
end
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
end
function Text.insert_at_cursor(State, t)
end
function 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 then
return 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 line
y = 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) then
State.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) then
edit.draw(State)
App.fake_mouse_release(x,y, mouse_button)
Text.delete_selection(State, State.left, State.right)
end
edit.put_cursor_on_next_text_line(State)
edit.clean_up_mouse_press(State)
end
State.lines.current_drawing_index = line_index
State.lines.current_drawing = line
Drawing.before = snapshot(State, line_index)
--? print('=> y', y)
elseif line.mode == 'drawing' then
y = y+Drawing_padding_top
Drawing.draw(State, line_index, y)
y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom
else
screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen
if State.lines[loc2.line].mode == 'text' then
elseif State.lines[loc2.line].mode == 'drawing' then
y = 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' then
return {
line=screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),
}
end
local loc2 = Text.to2(State, screen_bottom1)
while true do
if State.lines[loc2.line].mode == 'text' then break end
assert(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 screen
loc2 = Text.previous_screen_line(State, loc2)
end
local 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 leaks
end
-- return the top y coordinate of a given line_index,
-- or nil if no part of it is on screen
function 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 end
local loc2 = Text.to2(State, State.screen_top1)
local y = State.top
while true do
if State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_top
end
if loc2.line == line_index then return y end
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottom
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end -- end of file
loc2 = next_loc2
end
end
function 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 end
if 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 end
if State.lines[loc2.line].mode == 'text' then
elseif State.lines[State.screen_top1.line].mode == 'drawing' then
y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
elseif State.lines[loc2.line].mode == 'drawing' then
y = 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 screen
function 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.top
while true do
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width)
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end
loc2 = next_loc2
end
return 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)
end
function Text.final_text_loc_on_screen(State)
local screen_bottom1 = Text.screen_bottom1(State)
if State.lines[screen_bottom1.line].mode == 'text' then
return {
line=screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),
}
end
local loc2 = Text.to2(State, screen_bottom1)
while true do
if State.lines[loc2.line].mode == 'text' then break end
assert(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 screen
loc2 = Text.previous_screen_line(State, loc2)
end
local 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 page
if y < line_cache.starty then return false end
local starty = Text.starty(State, line_index)
if starty == nil then return false end -- outside current page
if 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' then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
end
Text.populate_screen_line_starting_pos(State, loc2.line)
if loc2.screen_line >= #State.line_cache[loc2.line].screen_line_starting_pos then
if loc2.line < #State.lines then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
else
return loc2
end
else
return {line=loc2.line, screen_line=loc2.screen_line+1, screen_pos=1}
end
end
--? 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+10
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+10
love.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) then
edit.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) then
love.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 end
Drawing.draw_pending_shape(line, starty, State.left,State.right)
end
function Drawing.in_drawing(drawing, line_cache, x,y, left,right)
if line_cache.starty == nil then return false end -- outside current page
local width = right-left
return 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)
end
function 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 page
local 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 then
local my = Drawing.coord(pmy-starty, State.width)
if Drawing.in_current_drawing(State, pmx,pmy, State.left,State.right) then
if 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) then
return drawing_index,drawing,line_cache
if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
return drawing_index,drawing
local line_cache = State.line_cache[drawing_index]
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
local 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) then
local 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) then
local 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) then
local 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) then
return drawing_index,drawing
local starty = Text.starty(State, drawing_index)
if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
return drawing,starty,i,shape
local starty = Text.starty(State, drawing_index)
if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
return drawing_index,drawing,starty,i,point
if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then