2QLTVKM6MVC25L44BSHKPZAXOOLK2VAUCGCYRUDRXXNYNSWDIWQAC SPXS2WOLSSEEGOY5XJOITKPBL6MSD7NSXC4ESFVKP63PEFTEP33AC 6SGMETX7K7GYSB23VATVIHRGCAQTB37EHXDO227NL4UTZKLEKVTAC S4PFY4MNRQW6DF6FXN3EJUTXQUSZZD6C6NPSIZGXENBCNZCPFEZQC MYQNSDA3BKAVX3AKPKUDVFOL7B2O53O7U75B6Y673HJZGOULFBYAC TOXPJJYYZY7QRCVXJJDGDAM7NBSHEXNPRW5X4THW55FTSIZ3GKVQC R5QXEHUIZLELJGGCZAE7ATNS3CLRJ7JFRENMGH4XXH24C5WABZDQC JOPVPUSAMMU6RFVDQR4NJC4GNNUFB7GPKVH7OS5FKCYS5QZ53VLQC B6DS4GZC46Q4QSD3TXP5Q7NDERTOBAQV66JBCT5O6FAKKTLEVJLAC ATQO62TFDZ7J4RCOSB3K2QCCB5R6PNYQIIGNXTLZMEFG5UG5PUJQC ZLJYLPOTXIVBVWJ4NTRM2YCQPT2FCSN7446P56MJFEFY45QTB7IAC KKQKPGCIHAG2JESQAWEMCBTAKBDC5AVIQ6LCZ2ORQM2AUCFQYLSQC IM7UEBMKYG6UT2MS34ZUVM5ZLN5YUZXOHJ6GVUSFMHRAK3JKULBQC 5NKF2G4INDOA4PUICLJM2DVFHSVQGFNCYFMGBH3LW45NUX6ADGFQC BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC AQMZJXUR5NFNATJ4LPTVGVLQFIKRKRSPYAICXWHGQCQ4WLMQ2JTQC LLQC2M2IMEJBJQXZTKC3OAKG5WKHSERXKAKCYHQRUZZD6CVRIHAQC I64IPGJXWRTGHHVAYJUBUIWFR4BY6NM5P7TLTV4JOD7K4BVYDECQC ISOFHXB2DX6IRN4HVBYWLADZM7QXQKRNAAS577G542KS4L6G5H3QC ICUW7F3XQLURK4LSNPH5E3NDEFSRHKATEUHH2UPFJTMHYR3ZJF3QC UPCIYZEUIFO2UJ3WPAFOD7VLNZEIIYYGJQGEMJOP5TSSE5PM4ZWAC MD3W5IRAC6UQALQE4LJC52VQNDO3I3HXF3XE2XHDABXBYJBUVAXQC MUJTM6REGQAK3LZTIFWGJRXE2UPCM4HSLXQYSF5ITLXLS6JCVPMQC 44BTGR7UG3PYPZQ5EHUOOSMJ4P5NV2FO23EFHMKVOQLFRTT6TJTQC ZPUQSPQPQFVRUIHGLAWW3IDBYODIWDHO62HAC3WWF5TM3CIJGHNQC Z5HLXU4PJWWJJDBCK52NBD6PIRIA3TAN2BKZB5HBYFGIDBX4F5HAC OCA2NNDNXAHZGHO3TDQVLJSXKYPHMOORTKSOSSWUX3XSTULSDIRQC 5L7K4GBDEAFH44LMLNKVFMHLWDNXXBKRPEI347VE5ZLXVFSMD2FAC CG5PH4DWFEUEICYJAPPGAMDCCTOGUB7475IC5IFKSB4ADL2KCJZQC HTZ3WRQHWJHMRR354RCPYK3L7OB5KRDJPJEL3Q4Y7IVNCYN2TSJQC 4VKEE43Z7MUPNIAOCK36INVBNHRTSWRRN37TIKRPXPH3DRKGHHAQC IWYLK45KJSPRXKW55OD4GEPMLTYMMTXNFJJU26JTZN3RE35DWSCQC VSA3FN7XPU364N34UWUM4UIB4PSYLU3UZB6VMXAAEQY7PJBRWHQAC DAENUOGV7KR6MZVXS36HEN3SZC4RFIS6REGAFVBOFEPO76EUDGIAC KCLRP4VWT2PDYLEG6DB6DYJRC2U7X4ZFCG3NGBOSFOYV7VPNRGZQC IMEJA43L3OX7S5KIYLZJ4F3ITACLAA5SZBHSCIJMULCPRSW7LXBAC GL4Q5WCVMOBEKW7SMBKRSL3DRG2NSTXRI7VQFK77OXAWLBDKWTNQC VSBSWTE4IVQDRXLPQ7VTDIIEBEF7GMGRBHZ2IA73ZR6B2KZWI5JAC UBA2ZUCP3JP5SGM2R5O5X5VP6DY64Y76MH65UPSHNWEUXRPZISPQC H2DPLWMVRFYTO2CQTG54FMT2LF3B6UKLXH32CUA22DNQJVP5XBNQC PTTZ7YRF3AKTO3LKL2PDC67H34NFKY2FRWL5HK6PZ2YWLPT5IUAAC 4GYPLUDYEF4NPB3HUGSOXAMCZ3UEV5ODM35IRA4DYL5IPDVDHD3QC XNFTJHC4QSHNSIWNN7K6QZEZ37GTQYKHS4EPNSVPQCUSWREROGIQC Y36LOGR5X6S6AEJJ3EBVDC4I3RRFRCZ6GXPXSEIOMVXY2B7UCCGQC OYVFFWBK5IL7IPAF5HGFONJ2NEBRR3GTISPFROG7HJDEZYJAM7VQC PFT5Y2ZYGQA6XXOZ5HH75WVUGA4B3KTDRHSFOZRAUKTPSFOPMNRAC QYIFOHW3WDDQMK4ATY6IOSQRFHJOQ5QCPDKRC4GVGWLQEH4HGWVQC LNA7OTZTO3TWABP5VAPWAWFUYXGPHCFBSWL62ELQICRDUHF43SKQC 4AC6J55RTWH6FQYHQ64Y3EGF5A27Q2DHJ2XB2LXWZQEYXM53BYLAC 34BZ5ZKNAB4XQGXOPVBZHBDYD5D3X4V6T72XZSR5LJXF4UIVSWQAC KWIVKQQ7AANRG6R4ZRB5TDBZ2TZTXAXIR2P6JNT362KIAJ7JQ4VQC FYRGHNLC4YQBRP7OZMPILETW6T4BXINL3VDVWKWP3B6NBCBT7A7QC 7YGYHOEOWGHRJ54YP6EEHVAGLUWMDATSRUC4IH4XRRHUBBRZQSVAC KWHC65JIQZP77ZVU2YUF7M7MFZ7OWO3GPGOXJ6Q7JIZPXVVQLKAQC JYZKEDDGZMLIH3GHTMURYCJ6H7IHZKPG4NT2RUZXLMENPV2UTCVAC 5NKJUM5IV5M64YRK3A2UWFJWEJ4V5PFLDEWGDOL263FLZ2SFQAVAC 6Z6WH62W4SGWWX75JQ2HVH2TC3IIWLNMA66UMTWWYSKPV7VS7IXAC 2F5RZ4YSLVCVTYEUDJ4D6H355YD2B65ZRMTPGEEKIA2Q7FGIC3WAC M7SW76FQORGMK7VH5LCFBFZMGONVIAEHYPGQAWTKTKANU7ZAT6CQC RKSQE3PJXJCH4NVBACWIB33H5MMNCEEQ6NHMFARMM2IV55IGBORAC 3VHUIIATPOF7FXB7NTL5MESCV5BCQACII2D7QZ4UIUCBX3CWXMMAC 2TQUKHBC2EB3WDBD5UL62DQYV7CV6B7OJYK7CHOEDNOZENSOG42AC 3PSFWAILGRA4OYXWS2DX7VF332AIBPYBXHEA4GIQY2XEJVD65UMAC 4QCR4O2V63WVOTGBWRP6AQVAF4QQAL5VQMEBA26GJMB6LJXXDLCQC F52PSTYSIIUD7UPEVLEJPVSBQU7IKVHM4GN4YASNSKFP4MRE6S6AC D76IPVZBVKA53JJVNGTA2AQSRLIJY6W5RBU4PTUPR7IGL7BYNZCQC GG2553RBUKMCWIAD3EWJV7C2QH2BIS5V2E3BDTFSTYRHAPXGBF7QC 5XMBCKJZ7YCUOOQWWZRNLXMXD3MUYC2VAF3VKA4BIEHQRPDQMUMAC 7JVSBJIWCEXM3GIHJKAR446MV6RXEJGJVHLVKOXA67NV7LVMP2OAC B4FAIVRAXKCS2ZZXYUHL7HHWVMDMG2MQ3BY6IOJARK7B5XTBAQNAC NAGDS5E2LF4PFXZS72KKDWQ5K5KMZXLV763Q4VCKIT6GOBAWB6MAC 2LIQSQAJT7ID5GPDDOXU44LLWJ4KGFG6V4CFAN3KQY6R3C7QHC6AC TFZGBU3H3UBP7P6BYBNUYVUKJRTRTDAAMM6JIRLQPYJVAAOY56IQC FXI74QCLOZ4BS7UVZ3U2PE3LOL7MX3FWGHZCTGH3DYFXGTXVVIRAC YCWU4GJYJAUYPFHMZNSGSG66EB4SRJV7MKEOY54CGBL4ELEPB5BAC LXTTOB33N2HCUZFIUDRQGGBVHK2HODRG4NBLH6RXRQZDCHF27BSAC HIKLULFQG7Q7L4C5KXR3DV3TBZ2RGWXBJJXIGSE5YQWF37AJOYZAC 4KC7I3E2DIKLIP7LQRKB5WFA2Z5XZXAU46RFHNFQU5BVEJPDX6UQC FNJFBXABRFX5WK6SSRDLZDCNCABN5X36HUPKRMJ7ECQTEK2XJJJQC F5X3ARQXJS5MZNVOEPS2CSQGUY5UXK2NVHXVAK7PTTRSM22Q6ICQC 2R6UORLNH722O3OOQMXEMZNRSRW2JCN5OBVYJZMIA6AIJCKLJ24QC OTIBCAUJ3KDQJLVDN3A536DLZGNRYMGJLORZVR3WLCGXGO6UGO6AC VIGITGW5FAYYF4PDK6XAPI4S43X4FMAMBH77VLGMKLNUY7JJHR4QC VPRGENLAJTF52YSY6NNZAPWQOD2TLP7DJU7DAAWGL4WFHOYAZF5QC KEFZWDCOCLPTLSZJKRV4VYAHRITV5T33YKG2VGT332YAUCOBS3EAC INVLLFPN46D7CXCPILG76LQMPALJHAPFJY3WJSL6IT3AMKJSNYWQC 4I2LMNEXDHZTOTDKBJXHSZRQDHIBDXQOYJDNGQSZYXRNHZGP7YPAC VHQCNMARPMNBSIUFLJG7HVK4QGDNPCGNVFLHS3I4IGNVSV5MRLYQC Editor_state = edit.initialize_state(Margin_top, Margin_left + Line_number_width*font:getWidth('m'), right, font, settings.font_height, math.floor(settings.font_height*1.3))Editor_state = edit.initialize_state(Margin_top, Margin_left + Line_number_width*font:getWidth('m'), App.screen.width-Margin_right, font, font_height, math.floor(font_height*1.3))Editor_state.filename = 'run.lua'Editor_state.filename = settings.filename
love.graphics.line(xleft+50+State.font:getWidth(line.section_name)+2,sectiony, xright,sectiony)love.graphics.line(xleft+50+State.font:getWidth(line.section_name)+2,sectiony, xright,sectiony)love.graphics.print(line.section_stack[i].name, x+State.font_height+5, App.screen.height-State.font:getWidth(line.section_stack[i].name)-5, --[[vertically]] math.pi/2)love.graphics.print(line.section_stack[i].name, x, App.screen.height-State.font:getWidth(line.section_stack[i].name)-5, --[[vertically]] math.pi/2)endendreturn log_browser.right_margin(State, line)endfunction should_show(line)-- Show a line if every single section it's in is expanded.for i=1,#line.section_stack dolocal section = line.section_stack[i]if not section.expanded thenreturn falseendendreturn trueendfunction log_browser.left_margin(State, line)return State.left + #line.section_stack*Section_border_padding_horizontalendfunction log_browser.right_margin(State, line)return State.right - #line.section_stack*Section_border_padding_horizontalendfunction log_browser.update(State, dt)endfunction log_browser.quit(State)endendendreturn log_browser.left_margin(State, line)endfunction render_stack_right_margin(State, line_index, line, y)App.color(Section_border_color)for i=1,#line.section_stack dolocal x = State.right - (i-1)*Section_border_padding_horizontallove.graphics.line(x,y, x,y+log_browser.height(State, line_index))if y < 30 thenlove.graphics.print(line.section_stack[i].name, x, y+5, --[[vertically]] math.pi/2)endif y > App.screen.height-log_browser.height(State, line_index) thenendelseif type(line.data) == 'string' thenlocal old_left, old_right = State.left,State.rightState.left,State.right = xleft,xrightelse assert(line.section_end, "log line has a section name, but it's neither the start nor end of a section")
elseif pos + frag_len == State.cursor1.pos then-- Show cursor at end of line.-- This place also catches end of wrapping screen lines. That doesn't seem worth distinguishing.-- It seems useful to see a cursor whether your eye is on the left or right margin.
endendend-- render fragmentApp.color(Text_color)App.screen.print(screen_line, State.left,y)y = y + State.line_heightif y >= App.screen.height thenbreakendendendreturn y, final_screen_line_starting_posendfunction 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)endlocal endpos = line_cache.screen_line_starting_pos[i+1]-1local end_offset = Text.offset(line.data, endpos)return line.data:sub(offset, end_offset)endfunction Text.draw_cursor(State, x, y)-- blink every 0.5sif math.floor(Cursor_time*2)%2 == 0 thenApp.color(Cursor_color)love.graphics.rectangle('fill', x,y, 3,State.line_height)endState.cursor_x = xState.cursor_y = y+State.line_heightendfunction Text.populate_screen_line_starting_pos(State, line_index)local line = State.lines[line_index]local line_cache = State.line_cache[line_index]if line_cache.screen_line_starting_pos thenreturnendline_cache.screen_line_starting_pos = {1}local x = 0local pos = 1-- try to wrap at word boundariesfor frag in line.data:gmatch('%S*%s*') do
State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1local 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)State.cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1State.cursor1.pos = Text.nearest_cursor_pos(State.font, State.lines[State.cursor1.line].data, State.cursor_x, State.left)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)State.cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1local s = string.sub(line.data, screen_line_starting_byte_offset)--? print('return', mx, Text.nearest_cursor_pos(State.font, s, mx, State.left), '=>', screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, mx, State.left) - 1)return screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, mx, State.left) - 1return State.font:getWidth(screen_line)function Text.nearest_cursor_pos(font, line, x, left)local max_x = left+Text.x(font, line, len+1)local currxmin = left+Text.x(font, line, curr)local currxmax = left+Text.x(font, line, curr+1)function Text.nearest_pos_less_than(font, line, x)local max_x = Text.x_after(font, line, len)local currxmin = Text.x_after(font, line, curr+1)local currxmax = Text.x_after(font, line, curr+2)function Text.x_after(font, s, pos)return font:getWidth(s_before)function Text.x(font, s, pos)return font:getWidth(s_before)frag_width = App.width(frag)
--? print('return', mx, Text.nearest_cursor_pos(s, mx, State.left), '=>', screen_line_starting_pos + Text.nearest_cursor_pos(s, mx, State.left) - 1)return screen_line_starting_pos + Text.nearest_cursor_pos(s, mx, State.left) - 1
--? print('return', mx, Text.nearest_cursor_pos(State.font, s, mx, State.left), '=>', screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, mx, State.left) - 1)return screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, mx, State.left) - 1
Text.draw_cursor(State, State.left+Text.x(screen_line, State.cursor1.pos-pos+1), y)endendend-- render fragmentApp.color(Text_color)App.screen.print(screen_line, State.left,y)y = y + State.line_heightif y >= App.screen.height thenbreakendendendreturn y, final_screen_line_starting_posendfunction 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)endlocal endpos = line_cache.screen_line_starting_pos[i+1]-1local end_offset = Text.offset(line.data, endpos)return line.data:sub(offset, end_offset)endfunction Text.draw_cursor(State, x, y)-- blink every 0.5sif math.floor(Cursor_time*2)%2 == 0 thenApp.color(Cursor_color)love.graphics.rectangle('fill', x,y, 3,State.line_height)endState.cursor_x = xState.cursor_y = y+State.line_heightendfunction Text.populate_screen_line_starting_pos(State, line_index)local line = State.lines[line_index]local line_cache = State.line_cache[line_index]if line_cache.screen_line_starting_pos thenreturnendline_cache.screen_line_starting_pos = {1}local x = 0local pos = 1-- try to wrap at word boundariesfor frag in line.data:gmatch('%S*%s*') dolocal frag_width = App.width(frag)
elseif pos + frag_len == State.cursor1.pos then-- Show cursor at end of line.-- This place also catches end of wrapping screen lines. That doesn't seem worth distinguishing.-- It seems useful to see a cursor whether your eye is on the left or right margin.
--? print('-- frag:', frag, pos, x, frag_width, State.width)while x + frag_width > State.width do--? print('frag:', frag, pos, x, frag_width, State.width)if x < 0.8 * State.width then-- long word; chop it at some letter-- We're not going to reimplement TeX here.
function Text.clip_wikiword_with_screen_line(font, line, line_cache, i, s, e)return font:getWidth(line.data:sub(soff, loff-1)), font:getWidth(line.data:sub(soff, hoff))
if State.lines[State.cursor1.line-1].mode == 'drawing' thentable.remove(State.lines, State.cursor1.line-1)table.remove(State.line_cache, State.cursor1.line-1)else-- join linesState.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1].data)+1State.lines[State.cursor1.line-1].data = State.lines[State.cursor1.line-1].data..State.lines[State.cursor1.line].datatable.remove(State.lines, State.cursor1.line)table.remove(State.line_cache, State.cursor1.line)endState.cursor1.line = State.cursor1.line-1endif State.screen_top1.line > #State.lines thenText.populate_screen_line_starting_pos(State, #State.lines)local line_cache = State.line_cache[#State.line_cache]State.screen_top1 = {line=#State.lines, pos=line_cache.screen_line_starting_pos[#line_cache.screen_line_starting_pos]}elseif Text.lt1(State.cursor1, State.screen_top1) thenState.screen_top1 = {line=State.cursor1.line,pos=Text.pos_at_start_of_screen_line(State, State.cursor1),}Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaksendText.clear_screen_line_cache(State, State.cursor1.line)assert(Text.le1(State.screen_top1, State.cursor1), ('screen_top (line=%d,pos=%d) is below cursor (line=%d,pos=%d)'):format(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos))schedule_save(State)record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})elseif chord == 'delete' thenif State.selection1.line thenText.delete_selection(State, State.left, State.right)schedule_save(State)returnendlocal beforeif State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) thenbefore = snapshot(State, State.cursor1.line)elsebefore = snapshot(State, State.cursor1.line, State.cursor1.line+1)endif State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) thenlocal byte_start = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)local byte_end = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos+1)if byte_start thenif byte_end thenState.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].data, byte_end)elseState.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)end-- no change to State.cursor1.posendelseif State.cursor1.line < #State.lines thenif State.lines[State.cursor1.line+1].mode == 'text' then-- join linesState.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].dataendtable.remove(State.lines, State.cursor1.line+1)table.remove(State.line_cache, State.cursor1.line+1)endText.clear_screen_line_cache(State, State.cursor1.line)schedule_save(State)record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})--== shortcuts that move the cursorelseif chord == 'left' thenText.left(State)State.selection1 = {}elseif chord == 'right' thenText.right(State)State.selection1 = {}elseif chord == 'S-left' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.left(State)elseif chord == 'S-right' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.right(State)-- C- hotkeys reserved for drawings, so we'll use M-elseif chord == 'M-left' thenText.word_left(State)State.selection1 = {}elseif chord == 'M-right' thenText.word_right(State)State.selection1 = {}elseif chord == 'M-S-left' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.word_left(State)elseif chord == 'M-S-right' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.word_right(State)elseif chord == 'home' thenText.start_of_line(State)State.selection1 = {}elseif chord == 'end' thenText.end_of_line(State)State.selection1 = {}elseif chord == 'S-home' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.start_of_line(State)elseif chord == 'S-end' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.end_of_line(State)elseif chord == 'up' thenText.up(State)State.selection1 = {}elseif chord == 'down' thenText.down(State)State.selection1 = {}elseif chord == 'S-up' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.up(State)elseif chord == 'S-down' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.down(State)elseif chord == 'pageup' thenText.pageup(State)State.selection1 = {}elseif chord == 'pagedown' thenText.pagedown(State)State.selection1 = {}elseif chord == 'S-pageup' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.pageup(State)elseif chord == 'S-pagedown' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.pagedown(State)endendfunction Text.insert_return(State)local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)table.insert(State.lines, State.cursor1.line+1, {mode='text', data=string.sub(State.lines[State.cursor1.line].data, byte_offset)})table.insert(State.line_cache, State.cursor1.line+1, {})State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)Text.clear_screen_line_cache(State, State.cursor1.line)State.cursor1 = {line=State.cursor1.line+1, pos=1}endfunction Text.pageup(State)--? print('pageup')-- duplicate some logic from love.drawlocal top2 = Text.to2(State, State.screen_top1)--? print(App.screen.height)local y = App.screen.height - State.line_heightwhile y >= State.top do--? 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' theny = y - State.line_heightelseif State.lines[State.screen_top1.line].mode == 'drawing' theny = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)endtop2 = Text.previous_screen_line(State, top2)endState.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')endfunction Text.pagedown(State)--? 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.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('top now', State.screen_top1.line)Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks--? print('pagedown end')endfunction Text.up(State)assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')--? print('up', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)if screen_line_starting_pos == 1 then--? print('cursor is at first screen line of its line')-- line is done; skip to previous text linelocal new_cursor_line = State.cursor1.linewhile new_cursor_line > 1 donew_cursor_line = new_cursor_line-1if State.lines[new_cursor_line].mode == 'text' then--? print('found previous text line')State.cursor1 = {line=new_cursor_line, pos=nil}Text.populate_screen_line_starting_pos(State, State.cursor1.line)-- previous text line found, pick its final screen line--? print('has multiple screen lines')local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos--? print(#screen_line_starting_pos)screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos]local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos)local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset)State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1breakendendelse-- move up one screen line in current lineassert(screen_line_index > 1, 'bumped up against top screen line in line')local new_screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos[screen_line_index-1]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('return', mx, Text.nearest_cursor_pos(s, mx, State.left), '=>', screen_line_starting_pos + Text.nearest_cursor_pos(s, mx, State.left) - 1)return screen_line_starting_pos + Text.nearest_cursor_pos(s, mx, State.left) - 1
--? print('return', mx, Text.nearest_cursor_pos(State.font, s, mx, State.left), '=>', screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, mx, State.left) - 1)return screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, mx, State.left) - 1
end--? print('return', mx, Text.nearest_cursor_pos(State.font, s, mx, State.left), '=>', screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, mx, State.left) - 1)return screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, mx, State.left) - 1return State.font:getWidth(screen_line)function Text.nearest_cursor_pos(font, line, x, left)local max_x = left+Text.x(font, line, len+1)local currxmin = left+Text.x(font, line, curr)local currxmax = left+Text.x(font, line, curr+1)function Text.nearest_pos_less_than(font, line, x)local max_x = Text.x_after(font, line, len)local currxmin = Text.x_after(font, line, curr+1)local currxmax = Text.x_after(font, line, curr+2)function Text.x_after(font, s, pos)return font:getWidth(s_before)function Text.x(font, s, pos)return font:getWidth(s_before)if State.lines[State.cursor1.line-1].mode == 'drawing' thentable.remove(State.lines, State.cursor1.line-1)table.remove(State.line_cache, State.cursor1.line-1)else-- join linesState.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1].data)+1State.lines[State.cursor1.line-1].data = State.lines[State.cursor1.line-1].data..State.lines[State.cursor1.line].datatable.remove(State.lines, State.cursor1.line)table.remove(State.line_cache, State.cursor1.line)endState.cursor1.line = State.cursor1.line-1endif State.screen_top1.line > #State.lines thenText.populate_screen_line_starting_pos(State, #State.lines)local line_cache = State.line_cache[#State.line_cache]State.screen_top1 = {line=#State.lines, pos=line_cache.screen_line_starting_pos[#line_cache.screen_line_starting_pos]}elseif Text.lt1(State.cursor1, State.screen_top1) thenState.screen_top1 = {line=State.cursor1.line,pos=Text.pos_at_start_of_screen_line(State, State.cursor1),}Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaksendText.clear_screen_line_cache(State, State.cursor1.line)assert(Text.le1(State.screen_top1, State.cursor1), ('screen_top (line=%d,pos=%d) is below cursor (line=%d,pos=%d)'):format(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos))schedule_save(State)record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})elseif chord == 'delete' thenif State.selection1.line thenText.delete_selection(State, State.left, State.right)schedule_save(State)returnendlocal beforeif State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) thenbefore = snapshot(State, State.cursor1.line)elsebefore = snapshot(State, State.cursor1.line, State.cursor1.line+1)endif State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) thenlocal byte_start = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)local byte_end = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos+1)if byte_start thenif byte_end thenState.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].data, byte_end)elseState.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)end-- no change to State.cursor1.posendelseif State.cursor1.line < #State.lines thenif State.lines[State.cursor1.line+1].mode == 'text' then-- join linesState.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].dataendtable.remove(State.lines, State.cursor1.line+1)table.remove(State.line_cache, State.cursor1.line+1)endText.clear_screen_line_cache(State, State.cursor1.line)schedule_save(State)record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})--== shortcuts that move the cursorelseif chord == 'left' thenText.left(State)State.selection1 = {}elseif chord == 'right' thenText.right(State)State.selection1 = {}elseif chord == 'S-left' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.left(State)elseif chord == 'S-right' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.right(State)-- C- hotkeys reserved for drawings, so we'll use M-elseif chord == 'M-left' thenText.word_left(State)State.selection1 = {}elseif chord == 'M-right' thenText.word_right(State)State.selection1 = {}elseif chord == 'M-S-left' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.word_left(State)elseif chord == 'M-S-right' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.word_right(State)elseif chord == 'home' thenText.start_of_line(State)State.selection1 = {}elseif chord == 'end' thenText.end_of_line(State)State.selection1 = {}elseif chord == 'S-home' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.start_of_line(State)elseif chord == 'S-end' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.end_of_line(State)elseif chord == 'up' thenText.up(State)State.selection1 = {}elseif chord == 'down' thenText.down(State)State.selection1 = {}elseif chord == 'S-up' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.up(State)elseif chord == 'S-down' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.down(State)elseif chord == 'pageup' thenText.pageup(State)State.selection1 = {}elseif chord == 'pagedown' thenText.pagedown(State)State.selection1 = {}elseif chord == 'S-pageup' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.pageup(State)elseif chord == 'S-pagedown' thenif State.selection1.line == nil thenState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endText.pagedown(State)endendfunction Text.insert_return(State)local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)table.insert(State.lines, State.cursor1.line+1, {mode='text', data=string.sub(State.lines[State.cursor1.line].data, byte_offset)})table.insert(State.line_cache, State.cursor1.line+1, {})State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)Text.clear_screen_line_cache(State, State.cursor1.line)State.cursor1 = {line=State.cursor1.line+1, pos=1}endfunction Text.pageup(State)--? print('pageup')-- duplicate some logic from love.drawlocal top2 = Text.to2(State, State.screen_top1)--? print(App.screen.height)local y = App.screen.height - State.line_heightwhile y >= State.top do--? 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' theny = y - State.line_heightelseif State.lines[State.screen_top1.line].mode == 'drawing' theny = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)endtop2 = Text.previous_screen_line(State, top2)endState.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')
function Text.pagedown(State)--? 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.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('top now', State.screen_top1.line)Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks--? print('pagedown end')endfunction Text.up(State)assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')--? print('up', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)if screen_line_starting_pos == 1 then--? print('cursor is at first screen line of its line')-- line is done; skip to previous text linelocal new_cursor_line = State.cursor1.linewhile new_cursor_line > 1 donew_cursor_line = new_cursor_line-1if State.lines[new_cursor_line].mode == 'text' then--? print('found previous text line')State.cursor1 = {line=new_cursor_line, pos=nil}Text.populate_screen_line_starting_pos(State, State.cursor1.line)-- previous text line found, pick its final screen line--? print('has multiple screen lines')local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos--? print(#screen_line_starting_pos)screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos]local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos)local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset)State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1breakendendelse-- move up one screen line in current lineassert(screen_line_index > 1, 'bumped up against top screen line in line')local new_screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos[screen_line_index-1]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)State.cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1--? print('cursor pos is now '..tostring(State.cursor1.pos))endif Text.lt1(State.cursor1, State.screen_top1) then
Editor_state = edit.initialize_state(Margin_top, Margin_left + Line_number_width*App.width('m'), right, font, settings.font_height, math.floor(settings.font_height*1.3))
Editor_state = edit.initialize_state(Margin_top, Margin_left + Line_number_width*font:getWidth('m'), right, font, settings.font_height, math.floor(settings.font_height*1.3))
Editor_state = edit.initialize_state(Margin_top, Margin_left + Line_number_width*App.width('m'), App.screen.width-Margin_right, font, font_height, math.floor(font_height*1.3))
Editor_state = edit.initialize_state(Margin_top, Margin_left + Line_number_width*font:getWidth('m'), App.screen.width-Margin_right, font, font_height, math.floor(font_height*1.3))
App.color(Highlight_color)App.color(Text_color)return lo_pxendendfunction Text.mouse_pos(State)
endfor line_index,line in ipairs(State.lines) doif Text.in_line(State, line_index, x,y) thenreturn line_index, Text.to_pos_on_line(State, line_index, x,y)endend
endfunction Text.cut_selection(State)if State.selection1.line == nil then return endlocal result = Text.selection(State)Text.delete_selection(State)return resultendfunction Text.delete_selection(State)if State.selection1.line == nil then return endlocal minl,maxl = minmax(State.selection1.line, State.cursor1.line)local before = snapshot(State, minl, maxl)Text.delete_selection_without_undo(State)record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})endfunction Text.delete_selection_without_undo(State)if State.selection1.line == nil then return end-- min,max = sorted(State.selection1,State.cursor1)local minl,minp = State.selection1.line,State.selection1.poslocal maxl,maxp = State.cursor1.line,State.cursor1.posif minl > maxl thenminl,maxl = maxl,minlminp,maxp = maxp,minpelseif minl == maxl thenif minp > maxp thenminp,maxp = maxp,minpendend-- update State.cursor1 and State.selection1State.cursor1.line = minlState.cursor1.pos = minpif Text.lt1(State.cursor1, State.screen_top1) thenState.screen_top1.line = State.cursor1.lineState.screen_top1.pos = Text.pos_at_start_of_screen_line(State, State.cursor1)endState.selection1 = {}-- delete everything between min (inclusive) and max (exclusive)Text.clear_screen_line_cache(State, minl)local min_offset = Text.offset(State.lines[minl].data, minp)local max_offset = Text.offset(State.lines[maxl].data, maxp)if minl == maxl then--? print('minl == maxl')State.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..State.lines[minl].data:sub(max_offset)returnend
local rhs = State.lines[maxl].data:sub(max_offset)for i=maxl,minl+1,-1 dotable.remove(State.lines, i)table.remove(State.line_cache, i)endState.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..rhsendfunction Text.selection(State)if State.selection1.line == nil then return end-- min,max = sorted(State.selection1,State.cursor1)local minl,minp = State.selection1.line,State.selection1.poslocal maxl,maxp = State.cursor1.line,State.cursor1.posif minl > maxl thenminl,maxl = maxl,minlminp,maxp = maxp,minpelseif minl == maxl thenif minp > maxp thenminp,maxp = maxp,minpendendlocal min_offset = Text.offset(State.lines[minl].data, minp)local max_offset = Text.offset(State.lines[maxl].data, maxp)if minl == maxl thenreturn State.lines[minl].data:sub(min_offset, max_offset-1)end
local result = {State.lines[minl].data:sub(min_offset)}for i=minl+1,maxl-1 dotable.insert(result, State.lines[i].data)endtable.insert(result, State.lines[maxl].data:sub(1, max_offset-1))return table.concat(result, '\n')end
lo_px = State.font:getWidth(before)love.graphics.rectangle('fill', x+lo_px,y, State.font:getWidth(s),State.line_height)
love.graphics.print(line.section_stack[i].name, x+State.font_height+5, App.screen.height-App.width(line.section_stack[i].name)-5, --[[vertically]] math.pi/2)
love.graphics.print(line.section_stack[i].name, x+State.font_height+5, App.screen.height-State.font:getWidth(line.section_stack[i].name)-5, --[[vertically]] math.pi/2)
love.graphics.print(line.section_stack[i].name, x, App.screen.height-App.width(line.section_stack[i].name)-5, --[[vertically]] math.pi/2)
love.graphics.print(line.section_stack[i].name, x, App.screen.height-State.font:getWidth(line.section_stack[i].name)-5, --[[vertically]] math.pi/2)