Merge lines.love
[?]
Mar 18, 2023, 5:29 AM
2TQUKHBC2EB3WDBD5UL62DQYV7CV6B7OJYK7CHOEDNOZENSOG42ACDependencies
- [2]
B4FAIVRAMerge lines.love - [3]
U3MJNFUYMerge lines.love - [4]
O4RRXNOKbugfix: disallow font size of 0 - [5]
LDFXFRUObring a few things in sync between run and source - [6]
LK4ZW4BBbugfix - [7]
3TI67SEJmore bugfix - [8]
BJ5X5O4Alet's prevent the text cursor from ever getting on a drawing - [9]
A4BSGS2CMerge lines.love - [10]
6LJZN727handle chords - [11]
LNUHQOGHstart passing in Editor_state explicitly - [12]
LF7BWEG4group all editor globals - [13]
XS3PZI7GMerge lines.love - [14]
FS2ITYYHrecord a known issue - [15]
XX7G2FFJintermingle freehand line drawings with text - [16]
6DYSB5DYbugfix: perform matches in the right order - [17]
CE4LZV4Tdrop last couple of manual tests - [18]
66X36NZNa little more prose describing manual_tests - [19]
AKZWDWIAMerge lines.love - [20]
KMSL74GAsupport selections in the source editor - [21]
TVCPXAAUrename - [22]
73OCE2MCafter much struggle, a brute-force undo - [23]
RSZD5A7Gforgot to add json.lua - [24]
VXORMHMEdelete experimental REPL - [25]
ORRSP7FVdeduce test names on failures - [26]
4SR3Z4Y3document the version of LÖVE I've been using - [27]
SCOXD4EOMerge lines.love - [28]
MD3W5IRAnew fork: rip out drawing support - [29]
VBU5YHLRMerge lines.love - [30]
APX2PY6Gstop tracking wallclock time - [31]
ETXNVRPTMerge lines.love - [32]
LXTTOB33extract a couple of files - [33]
B4JEWKWIhide editor cursor while in file navigator - [34]
BULPIBEGbeginnings of a module for the text editor - [35]
3QNOKBFMbeginnings of a test harness - [36]
ZJOSQFN6bugfix: path munging on Windows - [37]
ZTZOO2OQMerge lines.love - [38]
ORKN6EOBMerge lines.love - [39]
AYX33NBCMerge lines.love - [40]
X3CQLBTRset window title within each app - [41]
JOPVPUSAediting source code from within the app - [42]
T4FRZSYLdelete an ancient, unused file - [43]
L2FWWEQLsource: remember cursor position of multiple files - [44]
4VQGE7RAnew test - [45]
N7VXEGLGyet another bugfix in log parsing - [46]
2CTN2IEFMerge lines.love - [47]
VHQCNMARseveral more modules - [48]
D4B52CQ2Merge lines.love - [49]
AOZX2G5Fsource: no commandline args - [50]
4YDBYBA4clean up memory leak experiments - [51]
GUOQRUL7Merge lines.love - [52]
KKMFQDR4editing source code from within the app - [53]
VP5KC4XZMerge lines.love - [54]
7VGDIPLCmore robust state validation - [55]
G54H3YG2get rid of all bifold text - [56]
2L5MEZV3experiment: new edit namespace - [57]
OTIBCAUJlove2d scaffold - [58]
ATQO62TFMerge lines.love - [59]
R5QXEHUIsomebody stop me - [60]
D2GCFTTTclean up repl functionality - [61]
2CK5QI7Wmake love event names consistent - [62]
TLOAPLBJadd a license - [63]
OGUV4HSAremove some memory leaks from rendered fragments - [64]
K2X6G75Zstart writing some tests for drawings - [65]
K74U4BAUMerge lines.love - [66]
UN7GKYV5support hyperlinks in the source editor - [67]
P3K7UH5CMerge lines.love - [68]
VHUNJHXBMerge lines.love - [69]
3PSFWAILMerge lines.love - [70]
BH7BT36Lctrl+a: select entire buffer - [71]
KYNGDE2Cconsistent names in a few more places - [72]
YRJFJNUDbugfix - [73]
BLWAYPKVextract a module - [74]
ME7WBLF5bugfix: log filenames can have 2 formats - [75]
AVTNUQYRbasic test-enabled framework - [76]
OI4FPFINsupport drawings in the source editor
Change contents
- file deletion: source_text_tests.lua source_text_tests.lua
endfunction test_up_arrow_scrolls_up_by_one_line_skipping_drawing()-- display lines 3/4/5 with a drawing just off screen at line 2App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', '```lines', '```', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=3, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up to previous text lineedit.run_after_keychord(Editor_state, 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor')endfunction test_up_arrow_scrolls_up_by_one_screen_line() - file deletion: source_text.lua source_text.lua
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),}endendend-- slightly expensive since it redraws the screenfunction Text.cursor_out_of_screen(State)App.draw()return State.cursor_y == nil-- 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)endreturn {line=loc2.line-1, screen_line=#State.line_cache[loc2.line-1].screen_line_starting_pos, screen_pos=1}endendlocal l = State.lines[loc2.line-1]Text.populate_screen_line_starting_pos(State, loc2.line-1)elseif State.lines[loc2.line-1].mode == 'drawing' thenreturn {line=loc2.line-1, screen_line=1, screen_pos=1}elsereturn a.pos <= b.posendif a.line > b.line thenreturn falseendreturn a.pos < b.posendfunction Text.le1(a, b)if a.line < b.line thenreturn trueendfunction Text.eq1(a, b)return a.line == b.line and a.pos == b.posendfunction Text.lt1(a, b)if a.line < b.line thenreturn trueendif a.line > b.line thenreturn falseendlocal s = string.sub(line.data, 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) - 1endy = nextyendassert(false)endfunction Text.screen_line_width(State, line_index, i)local line = State.lines[line_index]local line_cache = State.line_cache[line_index]local start_pos = line_cache.screen_line_starting_pos[i]local start_offset = Text.offset(line.data, start_pos)local screen_lineif i < #line_cache.screen_line_starting_pos thenlocal past_end_pos = line_cache.screen_line_starting_pos[i+1]local past_end_offset = Text.offset(line.data, past_end_pos)screen_line = string.sub(line.data, start_offset, past_end_offset-1)elsescreen_line = string.sub(line.data, start_pos)endlocal screen_line_text = App.newText(love.graphics.getFont(), screen_line)return App.width(screen_line_text)endreturn line_cache.screen_line_starting_pos[screen_line_index+1]-1endif screen_line_index < #line_cache.screen_line_starting_pos and mx > State.left + Text.screen_line_width(State, line_index, screen_line_index) then--? print('past end of non-final line; return')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 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))local nexty = y + State.line_heightif my < nexty then-- On all wrapped screen lines but the final one, clicks past end of-- line position cursor on final character of screen line.-- (The final screen line positions past end of screen line as always.)Text.populate_screen_line_starting_pos(State, line_index)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)end-- convert mx,my in pixels to schema-1 coordinates--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks--? print(y, 'top2:', top2.line, top2.screen_line, top2.screen_pos)if top2.line == 1 and top2.screen_line == 1 then break endtop2.screen_pos = 1 -- start of screen line--? 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('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 do--? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos)-- slide to start of screen line--? print('to2:', State.cursor1.line, State.cursor1.pos)local top2 = Text.to2(State, State.cursor1)return screen_lines[#screen_lines] <= State.cursor1.posendfunction Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)assert(State.lines[State.cursor1.line].mode == 'text')State.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 leaksendendif State.cursor1.pos > 1 thenState.cursor1.pos = State.cursor1.pos-1if Text.cursor_out_of_screen(State) thenText.snap_cursor_to_bottom_of_screen(State)endendfunction Text.match(s, pos, pat)local start_offset = Text.offset(s, pos)assert(start_offset)local end_offset = Text.offset(s, pos+1)assert(end_offset > start_offset)local curr = s:sub(start_offset, end_offset-1)return curr:match(pat)endfunction Text.left(State)function Text.word_right(State)-- skip some whitespacewhile true doif State.cursor1.pos > utf8.len(State.lines[State.cursor1.line].data) thenbreakendif Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos, '%S') thenbreakendText.right_without_scroll(State)end-- skip some non-whitespacewhile true do-- skip some whitespacewhile true doif State.cursor1.pos == 1 thenbreakendif Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos-1, '%S') thenbreakendText.left(State)endState.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1if Text.cursor_out_of_screen(State) thenText.snap_cursor_to_bottom_of_screen(State)endendfunction Text.word_left(State)State.screen_top1 = {line=State.cursor1.line, pos=State.cursor1.pos} -- copyendendfunction Text.end_of_line(State)State.cursor1.pos = 1if Text.lt1(State.cursor1, State.screen_top1) thenelse-- move down one screen line in current linelocal scroll_down = Text.le1(State.screen_bottom1, State.cursor1)--? 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)State.cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1--? print('cursor pos is now', State.cursor1.line, State.cursor1.pos)if scroll_down then--? 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)State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}State.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 leaksendendText.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)State.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)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_top1else-- 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)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')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)endlocal 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)State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}endState.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}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.posendif 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 > 1 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))schedule_save(State)record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})elseif chord == 'delete' thenbefore = snapshot(State, State.cursor1.line)local byte_start = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos-1)local byte_end = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)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)endState.cursor1.pos = State.cursor1.pos-1local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].data, byte_offset)Text.clear_screen_line_cache(State, State.cursor1.line)State.cursor1.pos = State.cursor1.pos+1endreturn y, screen_line_starting_posendfunction 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]if Focus == 'edit' and not hide_cursor and State.search_term == nil thenif line_index == State.cursor1.line and State.cursor1.pos == pos thenText.draw_cursor(State, x, y)endif line_index == State.cursor1.line thenif pos <= State.cursor1.pos and pos + frag_len > State.cursor1.pos thenif State.search_term thenif State.lines[State.cursor1.line].data:sub(State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term)-1) == State.search_term thenlocal lo_px = Text.draw_highlight(State, line, x,y, pos, State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term))App.color(Text_color)love.graphics.print(State.search_term, x+lo_px,y)endelseif Focus == 'edit' thenText.draw_cursor(State, x+Text.x(frag, State.cursor1.pos-pos+1), y)App.color(Text_color)endendendx = x + frag_widthendpos = pos + frag_lenendreturn y, screen_line_starting_posendscreen_line_starting_pos = posx = State.leftfunction ends_with(s, suffix)if #s < #suffix thenreturn falseendfor i=0,#suffix-1 doif s:sub(#s-i,#s-i) ~= suffix:sub(#suffix-i,#suffix-i) thenreturn falseendendreturn trueend-- wrap long lineslocal x = State.leftlocal pos = 1local screen_line_starting_pos = startposText.compute_fragments(State, line_index)local pos = 1initialize_color()for _, f in ipairs(line_cache.fragments) doApp.color(Text_color)local frag, frag_text = f.data, f.textselect_color(frag)local frag_len = utf8.len(frag)--? print('text.draw:', frag, 'at', line_index,pos, 'after', x,y)if pos < startpos then-- render nothing--? print('skipping', frag)else-- render fragmentlocal frag_width = App.width(frag_text)if x + frag_width > State.right thenassert(x > State.left) -- no overfull linesy = y + State.line_heightif y + State.line_height > App.screen.height then-- return the final y, and position of start of final screen line drawnfunction Text.draw(State, line_index, y, startpos, hide_cursor)local line = State.lines[line_index]local line_cache = State.line_cache[line_index]line_cache.starty = yline_cache.startpos = startposfunction starts_with(s, prefix)if #s < #prefix thenreturn falseendfor i=1,#prefix doif s:sub(i,i) ~= prefix:sub(i,i) thenreturn falseendendreturn trueend - file deletion: source_edit.lua source_edit.lua
screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screencursor1 = {line=1, pos=1}, -- position of cursorscreen_bottom1 = {line=1, pos=1}, -- position of start of screen line at bottom of screenState.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1}elseif chord == 'C-c' thenlocal s = Text.selection(State)if s thenApp.setClipboardText(s)endelseif chord == 'C-x' thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrolllocal s = Text.cut_selection(State, State.left, State.right)if s thenApp.setClipboardText(s)endschedule_save(State)elseif chord == 'C-v' thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll-- We don't have a good sense of when to scroll, so we'll be conservative-- and sometimes scroll when we didn't quite need to.local before_line = State.cursor1.linelocal before = snapshot(State, before_line)local clipboard_data = App.getClipboardText()for _,code in utf8.codes(clipboard_data) dolocal c = utf8.char(code)if c == '\n' thenText.insert_return(State)elseText.insert_at_cursor(State, c)endendif Text.cursor_out_of_screen(State) thenText.snap_cursor_to_bottom_of_screen(State, State.left, State.right)endschedule_save(State)record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})cursor={line=State.cursor1.line, pos=State.cursor1.pos},screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos},}assert(State.search_text == nil)State.cursor1.pos = State.cursor1.pos+1Text.search_next(State)elseif chord == 'up' thenText.search_previous(State)endreturnelseif chord == 'C-f' thenState.search_term = ''State.search_backup = {State.cursor1 = {line=line_index,pos=Text.to_pos_on_line(State, line_index, x, y),}--? print('cursor', State.cursor1.line, State.cursor1.pos)if State.mousepress_shift thenif State.old_selection1.line == nil thenState.selection1 = State.old_cursor1elseState.selection1 = State.old_selection1endendState.old_cursor1, State.old_selection1, State.mousepress_shift = nilif eq(State.cursor1, State.selection1) thenState.selection1 = {}endbreakendendend--? print('selection:', State.selection1.line, State.selection1.pos)State.selection1 = {line=line_index,pos=Text.to_pos_on_line(State, line_index, x, y),}--? print('selection', State.selection1.line, State.selection1.pos)breakendelseif line.mode == 'drawing' thenlocal line_cache = State.line_cache[line_index]if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) thenState.lines.current_drawing_index = line_indexState.lines.current_drawing = lineDrawing.before = snapshot(State, line_index)y, State.screen_bottom1.pos = Text.draw(State, line_index, y, startpos, hide_cursor)y = y + State.line_height--? 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_bottomelseprint(line.mode)assert(false)startpos = State.screen_top1.posendif line.data == '' then-- button to insert new drawingbutton(State, 'draw', {x=4,y=y+4, w=12,h=12, color={1,1,0},icon = icon.insert_drawing,onpress1 = function()Drawing.before = snapshot(State, line_index-1, line_index)table.insert(State.lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}})table.insert(State.line_cache, line_index, {})if State.cursor1.line >= line_index thenState.cursor1.line = State.cursor1.line+1endschedule_save(State)record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)})end,})--? print('text.draw', y, line_index)local startpos = 1if line_index == State.screen_top1.line thenState.screen_bottom1 = {line=line_index, pos=nil}if line.mode == 'text' then--? print('draw:', y, line_index, line)if y + State.line_height > App.screen.height then break endprint(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)assert(false)endState.cursor_x = nilState.cursor_y = nillocal y = State.top--? print('== draw')for line_index = State.screen_top1.line,#State.lines dolocal line = State.lines[line_index]selection1 = {},-- some extra state to compute selection between mouse press and releaseold_cursor1 = nil,old_selection1 = nil,mousepress_shift = nil,-- when selecting text, avoid recomputing some state on every single framerecent_mouse = {},lines = {{mode='text', data=''}}, -- array of lines-- Lines can be too long to fit on screen, in which case they _wrap_ into-- multiple _screen lines_.-- rendering wrapped text lines needs some additional short-lived data per line:-- startpos, the index of data the line starts rendering from, can only be >1 for topmost line on screen-- starty, the y coord in pixels the line starts rendering from-- fragments: snippets of rendered love.graphics.Text, guaranteed to not straddle screen lines-- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen lineline_cache = {},-- Given wrapping, any potential location for the text cursor can be described in two ways:-- * schema 1: As a combination of line index and position within a line (in utf8 codepoint units)-- * schema 2: As a combination of line index, screen line index within the line, and a position within the screen line.-- a line is either text or a drawing-- a text is a table with:-- mode = 'text',-- string data,--? print('press', State.selection1.line, State.selection1.pos)if mouse_press_consumed_by_any_button_handler(State, x,y, mouse_button) then-- press on a button and it returned 'true' to short-circuitreturnendfor line_index,line in ipairs(State.lines) do-- give some time for the OS to flush everything to disklove.timer.sleep(0.1)endendleft = math.floor(left),right = math.floor(right),width = right-left,if State.font_height > 2 thenedit.update_font_settings(State, State.font_height-2)Text.redraw_all(State)endelseif chord == 'C-0' thenedit.update_font_settings(State, 20)Text.redraw_all(State)-- undoelseif chord == 'C-z' thenfor _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scrolllocal event = undo_event(State)if event thenlocal src = event.beforeState.screen_top1 = deepcopy(src.screen_top)State.cursor1 = deepcopy(src.cursor) - file deletion: source.lua source.lua
-- environment for a mutable file-- TODO: some initialization is also happening in load_settings/initialize_default_settings. Clean that up. - file deletion: log_browser.lua log_browser.lua
Editor_state.cursor1 = {line=line.line_number, pos=1}-- make sure it's visible-- TODO: handle extremely long linesEditor_state.screen_top1.line = math.max(0, Editor_state.cursor1.line-5)-- show cursorFocus = 'edit'Log_browser_state.cursor1 = {line=1, pos=1}endSection_stack = {}Section_border_color = {r=0.7, g=0.7, b=0.7}Cursor_line_background_color = {r=0.7, g=0.7, b=0, a=0.1}Section_border_padding_horizontal = 30 -- TODO: adjust this based on font height (because we draw text vertically along the bordersSection_border_padding_vertical = 15 -- TODO: adjust this based on font heightlog_browser = {}function log_browser.parse(State)for _,line in ipairs(State.lines) doif line.data ~= '' then - edit in text_tests.lua at line 1177
endfunction test_up_arrow_scrolls_up_by_one_line_skipping_drawing()-- display lines 3/4/5 with a drawing just off screen at line 2App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', '```lines', '```', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=3, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up to previous text lineedit.run_after_keychord(Editor_state, 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor') - edit in source_text_tests.lua at line 1252
endfunction test_up_arrow_scrolls_up_by_one_line_skipping_drawing()-- display lines 3/4/5 with a drawing just off screen at line 2App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', '```lines', '```', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=3, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'baseline/screen:3')-- after hitting the up arrow the screen scrolls up to previous text lineedit.run_after_keychord(Editor_state, 'up')check_eq(Editor_state.screen_top1.line, 1, 'screen_top')check_eq(Editor_state.cursor1.line, 1, 'cursor') - edit in source_text.lua at line 3
AB_padding = 20 -- space in pixels between A side and B side - replacement in source_text.lua at line 5
-- return the final y, and pos,posB of start of final screen line drawnfunction Text.draw(State, line_index, y, startpos, startposB, hide_cursor)-- return the final y, and position of start of final screen line drawnfunction Text.draw(State, line_index, y, startpos, hide_cursor) - replacement in source_text.lua at line 11[8.84307]→[8.84307:84693](∅→∅),[8.84693]→[8.83:139](∅→∅),[8.139]→[8.84732:85916](∅→∅),[8.84732]→[8.84732:85916](∅→∅),[8.86070]→[8.86070:86564](∅→∅),[8.86564]→[8.140:194](∅→∅),[8.194]→[8.86601:87368](∅→∅),[8.86601]→[8.86601:87368](∅→∅)
line_cache.startposB = startposB-- draw A sidelocal overflows_screen, x, pos, screen_line_starting_posif startpos thenoverflows_screen, x, y, pos, screen_line_starting_pos = Text.draw_wrapping_line(State, line_index, State.left, y, startpos)if overflows_screen thenreturn y, screen_line_starting_posendif Focus == 'edit' and State.cursor1.pos thenif not hide_cursor and not State.search_term thenif line_index == State.cursor1.line and State.cursor1.pos == pos thenText.draw_cursor(State, x, y)endendendelsex = State.leftend-- check for B side--? if line_index == 8 then print('checking for B side') endif line.dataB == nil thenassert(y)assert(screen_line_starting_pos)--? if line_index == 8 then print('return 1') endreturn y, screen_line_starting_posendif not State.expanded and not line.expanded thenassert(y)assert(screen_line_starting_pos)--? if line_index == 8 then print('return 2') endbutton(State, 'expand', {x=x+AB_padding, y=y+2, w=App.width(State.em), h=State.line_height-4, color={1,1,1},icon = function(button_params)App.color(Fold_background_color)love.graphics.rectangle('fill', button_params.x, button_params.y, App.width(State.em), State.line_height-4, 2,2)end,onpress1 = function()line.expanded = trueend,})return y, screen_line_starting_posend-- draw B side--? if line_index == 8 then print('drawing B side') endApp.color(Fold_color)if startposB thenoverflows_screen, x, y, pos, screen_line_starting_pos = Text.draw_wrapping_lineB(State, line_index, x,y, startposB)elseoverflows_screen, x, y, pos, screen_line_starting_pos = Text.draw_wrapping_lineB(State, line_index, x+AB_padding,y, 1)endif overflows_screen thenreturn y, nil, screen_line_starting_posend--? if line_index == 8 then print('a') endif Focus == 'edit' and State.cursor1.posB then--? if line_index == 8 then print('b') endif not hide_cursor and not State.search_term then--? if line_index == 8 then print('c', State.cursor1.line, State.cursor1.posB, line_index, pos) endif line_index == State.cursor1.line and State.cursor1.posB == pos thenText.draw_cursor(State, x, y)endendendreturn y, nil, screen_line_starting_posend-- Given an array of fragments, draw the subset starting from pos to screen-- starting from (x,y).-- Return:-- - whether we got to bottom of screen before end of line-- - the final (x,y)-- - the final pos-- - starting pos of the final screen line drawnfunction Text.draw_wrapping_line(State, line_index, x,y, startpos)local line = State.lines[line_index]local line_cache = State.line_cache[line_index]--? print('== line', line_index, '^'..line.data..'$')-- wrap long lineslocal x = State.leftlocal pos = 1 - replacement in source_text.lua at line 34
return --[[screen filled]] true, x,y, pos, screen_line_starting_posreturn y, screen_line_starting_pos - replacement in source_text.lua at line 59
if State.cursor1.pos and line_index == State.cursor1.line thenif line_index == State.cursor1.line then - replacement in source_text.lua at line 77[8.89166]→[8.89166:90188](∅→∅),[8.90188]→[8.20953:21138](∅→∅),[8.21138]→[8.90188:91082](∅→∅),[8.90188]→[8.90188:91082](∅→∅)
return false, x,y, pos, screen_line_starting_posendfunction Text.draw_wrapping_lineB(State, line_index, x,y, startpos)local line = State.lines[line_index]local line_cache = State.line_cache[line_index]local screen_line_starting_pos = startposText.compute_fragmentsB(State, line_index, x)local pos = 1for _, f in ipairs(line_cache.fragmentsB) dolocal frag, frag_text = f.data, f.textlocal frag_len = utf8.len(frag)--? print('text.draw:', frag, 'at', line_index,pos, 'after', x,y)if pos < startpos then-- render nothing--? print('skipping', frag)else-- render fragmentlocal frag_width = App.width(frag_text)if x + frag_width > State.right thenassert(x > State.left) -- no overfull linesy = y + State.line_heightif y + State.line_height > App.screen.height thenreturn --[[screen filled]] true, x,y, pos, screen_line_starting_posendscreen_line_starting_pos = posx = State.leftendif State.selection1.line thenlocal lo, hi = Text.clip_selection(State, line_index, pos, pos+frag_len)Text.draw_highlight(State, line, x,y, pos, lo,hi)endApp.screen.draw(frag_text, x,y)-- render cursor if necessaryif State.cursor1.posB and line_index == State.cursor1.line thenif pos <= State.cursor1.posB and pos + frag_len > State.cursor1.posB thenif State.search_term thenif State.lines[State.cursor1.line].dataB:sub(State.cursor1.posB, State.cursor1.posB+utf8.len(State.search_term)-1) == State.search_term thenlocal lo_px = Text.draw_highlight(State, line, x,y, pos, State.cursor1.posB, State.cursor1.posB+utf8.len(State.search_term))App.color(Fold_color)love.graphics.print(State.search_term, x+lo_px,y)endelseif Focus == 'edit' thenText.draw_cursor(State, x+Text.x(frag, State.cursor1.posB-pos+1), y)App.color(Fold_color)endendendx = x + frag_widthif Focus == 'edit' and not hide_cursor and State.search_term == nil thenif line_index == State.cursor1.line and State.cursor1.pos == pos thenText.draw_cursor(State, x, y) - edit in source_text.lua at line 81
pos = pos + frag_len - replacement in source_text.lua at line 82
return false, x,y, pos, screen_line_starting_posreturn y, screen_line_starting_pos - edit in source_text.lua at line 162
endx = x + frag_widthendendfunction Text.populate_screen_line_starting_posB(State, line_index, x)local line = State.lines[line_index]local line_cache = State.line_cache[line_index]if line_cache.screen_line_starting_posB thenreturnend-- duplicate some logic from Text.drawText.compute_fragmentsB(State, line_index, x)line_cache.screen_line_starting_posB = {1}local pos = 1for _, f in ipairs(line_cache.fragmentsB) dolocal frag, frag_text = f.data, f.text-- render fragmentlocal frag_width = App.width(frag_text)if x + frag_width > State.right thenx = State.lefttable.insert(line_cache.screen_line_starting_posB, pos) - edit in source_text.lua at line 164
local frag_len = utf8.len(frag)pos = pos + frag_len - edit in source_text.lua at line 167
function Text.compute_fragmentsB(State, line_index, x)--? print('compute_fragmentsB', line_index, 'between', x, State.right)local line = State.lines[line_index]local line_cache = State.line_cache[line_index]if line_cache.fragmentsB thenreturnendline_cache.fragmentsB = {}-- try to wrap at word boundariesfor frag in line.dataB:gmatch('%S*%s*') dolocal frag_text = App.newText(love.graphics.getFont(), frag)local frag_width = App.width(frag_text)--? print('x: '..tostring(x)..'; '..tostring(State.right-x)..'px to go')while x + frag_width > State.right do--? print(('checking whether to split fragment ^%s$ of width %d when rendering from %d'):format(frag, frag_width, x))if (x-State.left) < 0.8 * (State.right-State.left) then--? print('splitting')-- long word; chop it at some letter-- We're not going to reimplement TeX here.local bpos = Text.nearest_pos_less_than(frag, State.right - x)--? print('bpos', bpos)if bpos == 0 then break end -- avoid infinite loop when window is too narrowlocal boffset = Text.offset(frag, bpos+1) -- byte _after_ bpos--? print('space for '..tostring(bpos)..' graphemes, '..tostring(boffset-1)..' bytes')local frag1 = string.sub(frag, 1, boffset-1)local frag1_text = App.newText(love.graphics.getFont(), frag1)local frag1_width = App.width(frag1_text)--? print('extracting ^'..frag1..'$ of width '..tostring(frag1_width)..'px')assert(x + frag1_width <= State.right)table.insert(line_cache.fragmentsB, {data=frag1, text=frag1_text})frag = string.sub(frag, boffset)frag_text = App.newText(love.graphics.getFont(), frag)frag_width = App.width(frag_text)endx = State.left -- new lineendif #frag > 0 then--? print('inserting ^'..frag..'$ of width '..tostring(frag_width)..'px')table.insert(line_cache.fragmentsB, {data=frag, text=frag_text})endx = x + frag_widthendend - replacement in source_text.lua at line 181
if State.cursor1.pos thenlocal byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].data, byte_offset)Text.clear_screen_line_cache(State, State.cursor1.line)State.cursor1.pos = State.cursor1.pos+1elseassert(State.cursor1.posB)local byte_offset = Text.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB)State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].dataB, byte_offset)Text.clear_screen_line_cache(State, State.cursor1.line)State.cursor1.posB = State.cursor1.posB+1endlocal byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].data, byte_offset)Text.clear_screen_line_cache(State, State.cursor1.line)State.cursor1.pos = State.cursor1.pos+1 - replacement in source_text.lua at line 219
if State.cursor1.pos and State.cursor1.pos > 1 thenif State.cursor1.pos > 1 then - edit in source_text.lua at line 230
endelseif State.cursor1.posB thenif State.cursor1.posB > 1 thenbefore = snapshot(State, State.cursor1.line)local byte_start = utf8.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB-1)local byte_end = utf8.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB)if byte_start thenif byte_end thenState.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].dataB, byte_end)elseState.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1)endState.cursor1.posB = State.cursor1.posB-1endelse-- refuse to delete past beginning of side B - replacement in source_text.lua at line 250
local top2 = Text.to2(State, State.screen_top1)top2 = Text.previous_screen_line(State, top2, State.left, State.right)State.screen_top1 = Text.to1(State, top2)State.screen_top1 = {line=State.cursor1.line,pos=Text.pos_at_start_of_screen_line(State, State.cursor1),} - replacement in source_text.lua at line 267
if State.cursor1.posB or State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) thenif State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then - replacement in source_text.lua at line 272
if State.cursor1.pos and State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) thenif State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then - edit in source_text.lua at line 282
endelseif State.cursor1.posB thenif State.cursor1.posB <= utf8.len(State.lines[State.cursor1.line].dataB) thenlocal byte_start = utf8.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB)local byte_end = utf8.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB+1)if byte_start thenif byte_end thenState.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].dataB, byte_end)elseState.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1)end-- no change to State.cursor1.posendelse-- refuse to delete past end of side B - edit in source_text.lua at line 287
-- delete side B on first lineState.lines[State.cursor1.line].dataB = State.lines[State.cursor1.line+1].dataB - replacement in source_text.lua at line 303
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} - replacement in source_text.lua at line 308
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} - replacement in source_text.lua at line 320
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} - replacement in source_text.lua at line 325
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} - replacement in source_text.lua at line 336
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} - replacement in source_text.lua at line 341
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} - replacement in source_text.lua at line 352
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} - replacement in source_text.lua at line 357
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} - replacement in source_text.lua at line 368
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} - replacement in source_text.lua at line 373
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} - replacement in source_text.lua at line 380[8.106779]→[8.106779:106965](∅→∅),[8.106965]→[8.4859:5039](∅→∅),[8.5039]→[8.107132:107529](∅→∅),[8.107132]→[8.107132:107529](∅→∅)
if State.cursor1.pos then-- when inserting a newline, move any B side to the new linelocal 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), dataB=State.lines[State.cursor1.line].dataB})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)State.lines[State.cursor1.line].dataB = nilText.clear_screen_line_cache(State, State.cursor1.line)State.cursor1 = {line=State.cursor1.line+1, pos=1}else-- disable enter when cursor is on the B sideendlocal 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} - replacement in source_text.lua at line 396
if State.screen_top1.line == 1 and State.screen_top1.pos and State.screen_top1.pos == 1 then break endif State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end - replacement in source_text.lua at line 405
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos, posB=State.screen_top1.posB}State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} - edit in source_text.lua at line 413
-- 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. - edit in source_text.lua at line 417
if bot2.screen_line > 1 thenbot2.screen_line = math.max(bot2.screen_line-10, 1)end - replacement in source_text.lua at line 424
State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos, posB=State.screen_bottom1.posB}State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos} - replacement in source_text.lua at line 427
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos, posB=State.screen_top1.posB}State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} - edit in source_text.lua at line 436
if State.cursor1.pos thenText.upA(State)elseText.upB(State)endendfunction Text.upA(State) - replacement in source_text.lua at line 469
local top2 = Text.to2(State, State.screen_top1)top2 = Text.previous_screen_line(State, top2)State.screen_top1 = Text.to1(State, top2)State.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 leaks - edit in source_text.lua at line 477[8.111389]→[8.111389:111691](∅→∅),[8.111691]→[8.6570:6750](∅→∅),[8.6750]→[2.2317:2374](∅→∅),[2.2374]→[8.6812:7469](∅→∅),[8.6812]→[8.6812:7469](∅→∅),[8.7469]→[8.112426:114873](∅→∅),[8.112426]→[8.112426:114873](∅→∅)
function Text.upB(State)local line_cache = State.line_cache[State.cursor1.line]local screen_line_starting_posB, screen_line_indexB = Text.pos_at_start_of_screen_lineB(State, State.cursor1)assert(screen_line_indexB >= 1)if screen_line_indexB == 1 then-- move to A side of previous 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' thenState.cursor1 = {line=new_cursor_line, posB=nil}Text.populate_screen_line_starting_pos(State, State.cursor1.line)local prev_line_cache = State.line_cache[State.cursor1.line]local prev_screen_line_starting_pos = prev_line_cache.screen_line_starting_pos[#prev_line_cache.screen_line_starting_pos]local prev_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, prev_screen_line_starting_pos)local s = string.sub(State.lines[State.cursor1.line].data, prev_screen_line_starting_byte_offset)State.cursor1.pos = prev_screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1breakendendelseif screen_line_indexB == 2 then-- all-B screen-line to potentially A+B screen-linelocal xA = Margin_left + Text.screen_line_width(State, State.cursor1.line, #line_cache.screen_line_starting_pos) + AB_paddingif State.cursor_x < xA thenState.cursor1.posB = nilText.populate_screen_line_starting_pos(State, State.cursor1.line)local new_screen_line_starting_pos = line_cache.screen_line_starting_pos[#line_cache.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)State.cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1elseText.populate_screen_line_starting_posB(State, State.cursor1.line)local new_screen_line_starting_posB = line_cache.screen_line_starting_posB[screen_line_indexB-1]local new_screen_line_starting_byte_offsetB = Text.offset(State.lines[State.cursor1.line].dataB, new_screen_line_starting_posB)local s = string.sub(State.lines[State.cursor1.line].dataB, new_screen_line_starting_byte_offsetB)State.cursor1.posB = new_screen_line_starting_posB + Text.nearest_cursor_pos(s, State.cursor_x-xA, State.left) - 1endelseassert(screen_line_indexB > 2)-- all-B screen-line to all-B screen-lineText.populate_screen_line_starting_posB(State, State.cursor1.line)local new_screen_line_starting_posB = line_cache.screen_line_starting_posB[screen_line_indexB-1]local new_screen_line_starting_byte_offsetB = Text.offset(State.lines[State.cursor1.line].dataB, new_screen_line_starting_posB)local s = string.sub(State.lines[State.cursor1.line].dataB, new_screen_line_starting_byte_offsetB)State.cursor1.posB = new_screen_line_starting_posB + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1endif Text.lt1(State.cursor1, State.screen_top1) thenlocal top2 = Text.to2(State, State.screen_top1)top2 = Text.previous_screen_line(State, top2)State.screen_top1 = Text.to1(State, top2)endend-- cursor on final screen line (A or B side) => goes to next screen line on A side-- cursor on A side => move down one screen line (A side) in current line-- cursor on B side => move down one screen line (B side) in current line - replacement in source_text.lua at line 501
elseif State.cursor1.pos then-- move down one screen line (A side) in current lineelse-- move down one screen line in current line - edit in source_text.lua at line 518
else-- move down one screen line (B side) in current linelocal scroll_down = falseif Text.le1(State.screen_bottom1, State.cursor1) thenscroll_down = trueendlocal cursor_line = State.lines[State.cursor1.line]local cursor_line_cache = State.line_cache[State.cursor1.line]local cursor2 = Text.to2(State, State.cursor1)assert(cursor2.screen_lineB < #cursor_line_cache.screen_line_starting_posB)local screen_line_starting_posB, screen_line_indexB = Text.pos_at_start_of_screen_lineB(State, State.cursor1)Text.populate_screen_line_starting_posB(State, State.cursor1.line)local new_screen_line_starting_posB = cursor_line_cache.screen_line_starting_posB[screen_line_indexB+1]local new_screen_line_starting_byte_offsetB = Text.offset(cursor_line.dataB, new_screen_line_starting_posB)local s = string.sub(cursor_line.dataB, new_screen_line_starting_byte_offsetB)State.cursor1.posB = new_screen_line_starting_posB + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1if scroll_down thenText.snap_cursor_to_bottom_of_screen(State)end - replacement in source_text.lua at line 523
if State.cursor1.pos thenState.cursor1.pos = 1elseState.cursor1.posB = 1endState.cursor1.pos = 1 - replacement in source_text.lua at line 525
State.screen_top1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} -- copyState.screen_top1 = {line=State.cursor1.line, pos=State.cursor1.pos} -- copy - replacement in source_text.lua at line 530
if State.cursor1.pos thenState.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1elseState.cursor1.posB = utf8.len(State.lines[State.cursor1.line].dataB) + 1endState.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1 - replacement in source_text.lua at line 537
-- we can cross the fold, so check side A/B one level downText.skip_whitespace_left(State)Text.left(State)Text.skip_non_whitespace_left(State)endfunction Text.word_right(State)-- we can cross the fold, so check side A/B one level downText.skip_whitespace_right(State)Text.right(State)Text.skip_non_whitespace_right(State)if Text.cursor_out_of_screen(State) thenText.snap_cursor_to_bottom_of_screen(State)endendfunction Text.skip_whitespace_left(State)if State.cursor1.pos thenText.skip_whitespace_leftA(State)elseText.skip_whitespace_leftB(State)endendfunction Text.skip_non_whitespace_left(State)if State.cursor1.pos thenText.skip_non_whitespace_leftA(State)elseText.skip_non_whitespace_leftB(State)endendfunction Text.skip_whitespace_leftA(State)-- skip some whitespace - replacement in source_text.lua at line 547
endfunction Text.skip_whitespace_leftB(State)-- skip some non-whitespace - edit in source_text.lua at line 549
if State.cursor1.posB == 1 thenbreakendif Text.match(State.lines[State.cursor1.line].dataB, State.cursor1.posB-1, '%S') thenbreakend - edit in source_text.lua at line 550
endendfunction Text.skip_non_whitespace_leftA(State)while true do - edit in source_text.lua at line 555
breakendText.left(State)endendfunction Text.skip_non_whitespace_leftB(State)while true doif State.cursor1.posB == 1 thenbreakendassert(State.cursor1.posB > 1)if Text.match(State.lines[State.cursor1.line].dataB, State.cursor1.posB-1, '%s') then - edit in source_text.lua at line 557
Text.left(State) - replacement in source_text.lua at line 560
function Text.skip_whitespace_right(State)if State.cursor1.pos thenText.skip_whitespace_rightA(State)elseText.skip_whitespace_rightB(State)endendfunction Text.skip_non_whitespace_right(State)if State.cursor1.pos thenText.skip_non_whitespace_rightA(State)elseText.skip_non_whitespace_rightB(State)endendfunction Text.skip_whitespace_rightA(State)function Text.word_right(State)-- skip some whitespace - edit in source_text.lua at line 571
endfunction Text.skip_whitespace_rightB(State) - edit in source_text.lua at line 572
if State.cursor1.posB > utf8.len(State.lines[State.cursor1.line].dataB) thenbreakendif Text.match(State.lines[State.cursor1.line].dataB, State.cursor1.posB, '%S') thenbreakend - edit in source_text.lua at line 573
endendfunction Text.skip_non_whitespace_rightA(State)while true do - edit in source_text.lua at line 579
Text.right_without_scroll(State) - replacement in source_text.lua at line 580
endfunction Text.skip_non_whitespace_rightB(State)while true doif State.cursor1.posB > utf8.len(State.lines[State.cursor1.line].dataB) thenbreakendif Text.match(State.lines[State.cursor1.line].dataB, State.cursor1.posB, '%s') thenbreakendText.right_without_scroll(State)if Text.cursor_out_of_screen(State) thenText.snap_cursor_to_bottom_of_screen(State) - replacement in source_text.lua at line 595
if State.cursor1.pos thenText.leftA(State)elseText.leftB(State)endendfunction Text.leftA(State)assert(State.lines[State.cursor1.line].mode == 'text') - replacement in source_text.lua at line 612
local top2 = Text.to2(State, State.screen_top1)top2 = Text.previous_screen_line(State, top2)State.screen_top1 = Text.to1(State, top2)State.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 leaks - edit in source_text.lua at line 620
function Text.leftB(State)if State.cursor1.posB > 1 thenState.cursor1.posB = State.cursor1.posB-1else-- overflow back into A sideState.cursor1.posB = nilState.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1endif Text.lt1(State.cursor1, State.screen_top1) thenlocal top2 = Text.to2(State, State.screen_top1)top2 = Text.previous_screen_line(State, top2)State.screen_top1 = Text.to1(State, top2)endend - edit in source_text.lua at line 629
if State.cursor1.pos thenText.right_without_scrollA(State)elseText.right_without_scrollB(State)endendfunction Text.right_without_scrollA(State) - edit in source_text.lua at line 643[8.124465]→[8.124465:124634](∅→∅),[8.124634]→[8.8764:8771](∅→∅),[8.8771]→[8.124685:124718](∅→∅),[8.124685]→[8.124685:124718](∅→∅),[8.124718]→[8.8772:9052](∅→∅),[8.9052]→[8.124773:124784](∅→∅),[8.124773]→[8.124773:124784](∅→∅)
function Text.right_without_scrollB(State)if State.cursor1.posB <= utf8.len(State.lines[State.cursor1.line].dataB) thenState.cursor1.posB = State.cursor1.posB+1else-- overflow back into A sidelocal new_cursor_line = State.cursor1.linewhile new_cursor_line <= #State.lines-1 donew_cursor_line = new_cursor_line+1if State.lines[new_cursor_line].mode == 'text' thenState.cursor1 = {line=new_cursor_line, pos=1}breakendendendend - edit in source_text.lua at line 650
endendassert(false)endfunction Text.pos_at_start_of_screen_lineB(State, loc1)Text.populate_screen_line_starting_pos(State, loc1.line)local line_cache = State.line_cache[loc1.line]local x = Margin_left + Text.screen_line_width(State, loc1.line, #line_cache.screen_line_starting_pos) + AB_paddingText.populate_screen_line_starting_posB(State, loc1.line, x)for i=#line_cache.screen_line_starting_posB,1,-1 dolocal sposB = line_cache.screen_line_starting_posB[i]if sposB <= loc1.posB thenreturn sposB,i - edit in source_text.lua at line 657
local line = State.lines[State.cursor1.line] - replacement in source_text.lua at line 659
if (not State.expanded and not line.expanded) orline.dataB == nil thenreturn screen_lines[#screen_lines] <= State.cursor1.posendif State.cursor1.pos then-- ignore B sidereturn screen_lines[#screen_lines] <= State.cursor1.posendassert(State.cursor1.posB)local line_cache = State.line_cache[State.cursor1.line]local x = Margin_left + Text.screen_line_width(State, State.cursor1.line, #line_cache.screen_line_starting_pos) + AB_paddingText.populate_screen_line_starting_posB(State, State.cursor1.line, x)local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_posBreturn screen_lines[#screen_lines] <= State.cursor1.posBreturn screen_lines[#screen_lines] <= State.cursor1.pos - replacement in source_text.lua at line 687
--? print('to2:', State.cursor1.line, State.cursor1.pos, State.cursor1.posB)--? print('to2:', State.cursor1.line, State.cursor1.pos) - replacement in source_text.lua at line 689
--? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos, top2.screen_lineB, top2.screen_posB)--? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos) - replacement in source_text.lua at line 691
if top2.screen_pos thentop2.screen_pos = 1elseassert(top2.screen_posB)top2.screen_posB = 1end--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.cursor1.posB, State.screen_bottom1.line, State.screen_bottom1.pos, State.screen_bottom1.posB)top2.screen_pos = 1 -- start of screen line--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) - replacement in source_text.lua at line 697
--? print(y, 'top2:', State.lines[top2.line].data, top2.line, top2.screen_line, top2.screen_pos, top2.screen_lineB, top2.screen_posB)--? print(y, 'top2:', top2.line, top2.screen_line, top2.screen_pos) - replacement in source_text.lua at line 722
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.cursor1.posB, State.screen_bottom1.line, State.screen_bottom1.pos, State.screen_bottom1.posB)--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) - replacement in source_text.lua at line 731
local num_screen_lines = 0if line_cache.startpos thenText.populate_screen_line_starting_pos(State, line_index)num_screen_lines = num_screen_lines + #line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1end--? print('#screenlines after A', num_screen_lines)if line.dataB and (State.expanded or line.expanded) thenlocal x = Margin_left + Text.screen_line_width(State, line_index, #line_cache.screen_line_starting_pos) + AB_paddingText.populate_screen_line_starting_posB(State, line_index, x)--? print('B:', x, #line_cache.screen_line_starting_posB)if line_cache.startposB thennum_screen_lines = num_screen_lines + #line_cache.screen_line_starting_posB - Text.screen_line_indexB(line_cache.screen_line_starting_posB, line_cache.startposB) -- no +1; first screen line of B side overlaps with A sideelsenum_screen_lines = num_screen_lines + #line_cache.screen_line_starting_posB - Text.screen_line_indexB(line_cache.screen_line_starting_posB, 1) -- no +1; first screen line of B side overlaps with A sideendend--? print('#screenlines after B', num_screen_lines)return y < line_cache.starty + State.line_height*num_screen_linesText.populate_screen_line_starting_pos(State, line_index)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) - edit in source_text.lua at line 736
-- returns: pos, posB-- scenarios:-- line without B side-- line with B side collapsed-- line with B side expanded-- line starting rendering in A side (startpos ~= nil)-- line starting rendering in B side (startposB ~= nil)-- my on final screen line of A side-- mx to right of A side with no B side-- mx to right of A side but left of B side-- mx to right of B side-- preconditions:-- startpos xor startposB-- expanded -> dataB - replacement in source_text.lua at line 742
--? print('click', line_index, my, 'with line starting at', y, #line_cache.screen_line_starting_pos) -- , #line_cache.screen_line_starting_posB)if line_cache.startpos 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))local nexty = y + State.line_heightif my < nexty then-- On all wrapped screen lines but the final one, clicks past end of-- line position cursor on final character of screen line.-- (The final screen line positions past end of screen line as always.)if screen_line_index < #line_cache.screen_line_starting_pos and mx > State.left + Text.screen_line_width(State, line_index, screen_line_index) then--? print('past end of non-final line; return')return line_cache.screen_line_starting_pos[screen_line_index+1]-1endlocal s = string.sub(line.data, 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)local screen_line_posA = Text.nearest_cursor_pos(s, mx, State.left)if line.dataB == nil then-- no B sidereturn screen_line_starting_pos + screen_line_posA - 1endif not State.expanded and not line.expanded then-- B side is not expandedreturn screen_line_starting_pos + screen_line_posA - 1endlocal lenA = utf8.len(s)if screen_line_posA < lenA then-- mx is within A sidereturn screen_line_starting_pos + screen_line_posA - 1endlocal max_xA = State.left+Text.x(s, lenA+1)if mx < max_xA + AB_padding then-- mx is in the space between A and B sidereturn screen_line_starting_pos + screen_line_posA - 1endmx = mx - max_xA - AB_paddinglocal screen_line_posB = Text.nearest_cursor_pos(line.dataB, mx, --[[no left margin]] 0)return nil, screen_line_posBendy = nextyendend-- look in screen lines composed entirely of the B sideassert(State.expanded or line.expanded)local start_screen_line_indexBif line_cache.startposB thenstart_screen_line_indexB = Text.screen_line_indexB(line_cache.screen_line_starting_posB, line_cache.startposB)elsestart_screen_line_indexB = 2 -- skip the first line of side B, which we checked aboveendfor screen_line_indexB = start_screen_line_indexB,#line_cache.screen_line_starting_posB dolocal screen_line_starting_posB = line_cache.screen_line_starting_posB[screen_line_indexB]local screen_line_starting_byte_offsetB = Text.offset(line.dataB, screen_line_starting_posB)--? print('iter2', y, screen_line_indexB, screen_line_starting_posB, string.sub(line.dataB, screen_line_starting_byte_offsetB))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 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)) - replacement in source_text.lua at line 752
--? print('aa', mx, State.left, Text.screen_line_widthB(State, line_index, screen_line_indexB))if screen_line_indexB < #line_cache.screen_line_starting_posB and mx > State.left + Text.screen_line_widthB(State, line_index, screen_line_indexB) thenif screen_line_index < #line_cache.screen_line_starting_pos and mx > State.left + Text.screen_line_width(State, line_index, screen_line_index) then - replacement in source_text.lua at line 754
return nil, line_cache.screen_line_starting_posB[screen_line_indexB+1]-1return line_cache.screen_line_starting_pos[screen_line_index+1]-1 - replacement in source_text.lua at line 756
local s = string.sub(line.dataB, screen_line_starting_byte_offsetB)--? print('return', mx, Text.nearest_cursor_pos(s, mx, State.left), '=>', screen_line_starting_posB + Text.nearest_cursor_pos(s, mx, State.left) - 1)return nil, screen_line_starting_posB + Text.nearest_cursor_pos(s, mx, State.left) - 1local s = string.sub(line.data, 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 - edit in source_text.lua at line 782
function Text.screen_line_widthB(State, line_index, i)local line = State.lines[line_index]local line_cache = State.line_cache[line_index]local start_posB = line_cache.screen_line_starting_posB[i]local start_offsetB = Text.offset(line.dataB, start_posB)local screen_lineif i < #line_cache.screen_line_starting_posB then--? print('non-final', i)local past_end_posB = line_cache.screen_line_starting_posB[i+1]local past_end_offsetB = Text.offset(line.dataB, past_end_posB)--? print('between', start_offsetB, past_end_offsetB)screen_line = string.sub(line.dataB, start_offsetB, past_end_offsetB-1)else--? print('final', i)--? print('after', start_offsetB)screen_line = string.sub(line.dataB, start_offsetB)endlocal screen_line_text = App.newText(love.graphics.getFont(), screen_line)--? local result = App.width(screen_line_text)--? print('=>', result)--? return resultreturn App.width(screen_line_text)end - edit in source_text.lua at line 790
function Text.screen_line_indexB(screen_line_starting_posB, posB)if posB == nil thenreturn 0endassert(screen_line_starting_posB)for i = #screen_line_starting_posB,1,-1 doif screen_line_starting_posB[i] <= posB thenreturn iendendend - edit in source_text.lua at line 881
endif loc1.pos thenreturn Text.to2A(State, loc1)elsereturn Text.to2B(State, loc1) - edit in source_text.lua at line 882
endfunction Text.to2A(State, loc1) - edit in source_text.lua at line 894
return resultendfunction Text.to2B(State, loc1)local result = {line=loc1.line}local line_cache = State.line_cache[loc1.line]Text.populate_screen_line_starting_pos(State, loc1.line)local x = Margin_left + Text.screen_line_width(State, loc1.line, #line_cache.screen_line_starting_pos) + AB_paddingText.populate_screen_line_starting_posB(State, loc1.line, x)for i=#line_cache.screen_line_starting_posB,1,-1 dolocal sposB = line_cache.screen_line_starting_posB[i]if sposB <= loc1.posB thenresult.screen_lineB = iresult.screen_posB = loc1.posB - sposB + 1breakendendassert(result.screen_posB) - edit in source_text.lua at line 898
if loc2.screen_pos thenreturn Text.to1A(State, loc2)elsereturn Text.to1B(State, loc2)endendfunction Text.to1A(State, loc2) - replacement in source_text.lua at line 905
function Text.to1B(State, loc2)local result = {line=loc2.line, posB=loc2.screen_posB}if loc2.screen_lineB > 1 thenresult.posB = State.line_cache[loc2.line].screen_line_starting_posB[loc2.screen_lineB] + loc2.screen_posB - 1endreturn resultfunction Text.eq1(a, b)return a.line == b.line and a.pos == b.pos - replacement in source_text.lua at line 916
-- A side < B sideif a.pos and not b.pos thenreturn a.pos < b.posendfunction Text.le1(a, b)if a.line < b.line then - replacement in source_text.lua at line 923
if not a.pos and b.pos thenif a.line > b.line then - replacement in source_text.lua at line 926
if a.pos thenreturn a.pos < b.poselsereturn a.posB < b.posBendreturn a.pos <= b.pos - edit in source_text.lua at line 929
function Text.le1(a, b)return eq(a, b) or Text.lt1(a, b)end - edit in source_text.lua at line 940
if loc2.screen_pos thenreturn Text.previous_screen_lineA(State, loc2)elsereturn Text.previous_screen_lineB(State, loc2)endendfunction Text.previous_screen_lineA(State, loc2) - edit in source_text.lua at line 944
elseif State.lines[loc2.line-1].mode == 'drawing' thenreturn {line=loc2.line-1, screen_line=1, screen_pos=1} - edit in source_text.lua at line 947
local l = State.lines[loc2.line-1] - replacement in source_text.lua at line 949
if State.lines[loc2.line-1].dataB == nil or(not State.expanded and not State.lines[loc2.line-1].expanded) then--? print('c1', loc2.line-1, State.lines[loc2.line-1].data, '==', State.lines[loc2.line-1].dataB, State.line_cache[loc2.line-1].fragmentsB)return {line=loc2.line-1, screen_line=#State.line_cache[loc2.line-1].screen_line_starting_pos, screen_pos=1}end-- try to switch to Blocal prev_line_cache = State.line_cache[loc2.line-1]local x = Margin_left + Text.screen_line_width(State, loc2.line-1, #prev_line_cache.screen_line_starting_pos) + AB_paddingText.populate_screen_line_starting_posB(State, loc2.line-1, x)local screen_line_starting_posB = State.line_cache[loc2.line-1].screen_line_starting_posB--? print('c', loc2.line-1, State.lines[loc2.line-1].data, '==', State.lines[loc2.line-1].dataB, '==', #screen_line_starting_posB, 'starting from x', x)if #screen_line_starting_posB > 1 then--? print('c2')return {line=loc2.line-1, screen_lineB=#State.line_cache[loc2.line-1].screen_line_starting_posB, screen_posB=1}else--? print('c3')-- if there's only one screen line, assume it overlaps with A, so remain in Areturn {line=loc2.line-1, screen_line=#State.line_cache[loc2.line-1].screen_line_starting_pos, screen_pos=1}endreturn {line=loc2.line-1, screen_line=#State.line_cache[loc2.line-1].screen_line_starting_pos, screen_pos=1} - edit in source_text.lua at line 953
function Text.previous_screen_lineB(State, loc2)if loc2.screen_lineB > 2 then -- first screen line of B side overlaps with A sidereturn {line=loc2.line, screen_lineB=loc2.screen_lineB-1, screen_posB=1}else-- switch to A side-- TODO: handle case where fold lands precisely at end of a new screen-linereturn {line=loc2.line, screen_line=#State.line_cache[loc2.line].screen_line_starting_pos, screen_pos=1}endend - replacement in source_text.lua at line 982
local pos,posB = Text.to_pos_on_line(State, State.screen_bottom1.line, State.right-5, App.screen.height-5)State.cursor1 = {line=State.screen_bottom1.line, pos=pos, posB=posB}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),} - edit in source_text.lua at line 1022
State.line_cache[line_index].fragmentsB = nil - edit in source_text.lua at line 1023
State.line_cache[line_index].screen_line_starting_posB = nil - replacement in source_text.lua at line 1037
function starts_with(s, sub)return s:find(sub, 1, --[[no escapes]] true) == 1function starts_with(s, prefix)if #s < #prefix thenreturn falseendfor i=1,#prefix doif s:sub(i,i) ~= prefix:sub(i,i) thenreturn falseendendreturn true - replacement in source_text.lua at line 1049
function ends_with(s, sub)return s:reverse():find(sub:reverse(), 1, --[[no escapes]] true) == 1function ends_with(s, suffix)if #s < #suffix thenreturn falseendfor i=0,#suffix-1 doif s:sub(#s-i,#s-i) ~= suffix:sub(#suffix-i,#suffix-i) thenreturn falseendendreturn true - edit in source_edit.lua at line 13
Fold_color = {r=0, g=0.6, b=0}Fold_background_color = {r=0, g=0.7, b=0} - replacement in source_edit.lua at line 29
-- a line is either bifold text or a drawing-- a line of bifold text consists of an A side and an optional B side-- a line is either text or a drawing-- a text is a table with: - edit in source_edit.lua at line 33
-- string dataB,-- expanded: whether to show B side - replacement in source_edit.lua at line 49
lines = {{mode='text', data='', dataB=nil, expanded=nil}}, -- array of lineslines = {{mode='text', data=''}}, -- array of lines - edit in source_edit.lua at line 64
-- Positions (and screen line indexes) can be in either the A or the B side. - replacement in source_edit.lua at line 70
screen_top1 = {line=1, pos=1, posB=nil}, -- position of start of screen line at top of screencursor1 = {line=1, pos=1, posB=nil}, -- position of cursorscreen_bottom1 = {line=1, pos=1, posB=nil}, -- position of start of screen line at bottom of screenscreen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screencursor1 = {line=1, pos=1}, -- position of cursorscreen_bottom1 = {line=1, pos=1}, -- position of start of screen line at bottom of screen - replacement in source_edit.lua at line 94
left = left,right = right,left = math.floor(left),right = math.floor(right), - replacement in source_edit.lua at line 152
print(State.screen_top1.line, State.screen_top1.pos, State.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.cursor1.posB)print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) - replacement in source_edit.lua at line 161
--? print('draw:', y, line_index, line, line.mode)--? print('draw:', y, line_index, line) - replacement in source_edit.lua at line 163
State.screen_bottom1 = {line=line_index, pos=nil, posB=nil}State.screen_bottom1 = {line=line_index, pos=nil} - replacement in source_edit.lua at line 165
--? print('text.draw', y, line_index)local startpos, startposB = 1, nil--? print('text.draw', y, line_index)local startpos = 1 - replacement in source_edit.lua at line 168
if State.screen_top1.pos thenstartpos = State.screen_top1.poselsestartpos, startposB = nil, State.screen_top1.posBendstartpos = State.screen_top1.pos - replacement in source_edit.lua at line 186
y, State.screen_bottom1.pos, State.screen_bottom1.posB = Text.draw(State, line_index, y, startpos, startposB, hide_cursor)y, State.screen_bottom1.pos = Text.draw(State, line_index, y, startpos, hide_cursor) - edit in source_edit.lua at line 221
-- give some time for the OS to flush everything to disklove.timer.sleep(0.1) - replacement in source_edit.lua at line 228
--? print('press')--? print('press', State.selection1.line, State.selection1.pos) - replacement in source_edit.lua at line 249
local pos,posB = Text.to_pos_on_line(State, line_index, x, y)--? print(x,y, 'setting cursor:', line_index, pos, posB)State.selection1 = {line=line_index, pos=pos, posB=posB}--? print('selection', State.selection1.line, State.selection1.pos, State.selection1.posB)State.selection1 = {line=line_index,pos=Text.to_pos_on_line(State, line_index, x, y),}--? print('selection', State.selection1.line, State.selection1.pos) - replacement in source_edit.lua at line 284
local pos,posB = Text.to_pos_on_line(State, line_index, x, y)State.cursor1 = {line=line_index, pos=pos, posB=posB}--? print('cursor', State.cursor1.line, State.cursor1.pos, State.cursor1.posB)State.cursor1 = {line=line_index,pos=Text.to_pos_on_line(State, line_index, x, y),}--? print('cursor', State.cursor1.line, State.cursor1.pos) - replacement in source_edit.lua at line 357
if State.cursor1.pos thenState.cursor1.pos = State.cursor1.pos+1elseState.cursor1.posB = State.cursor1.posB+1endState.cursor1.pos = State.cursor1.pos+1 - replacement in source_edit.lua at line 366
cursor={line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB},screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos, posB=State.screen_top1.posB},cursor={line=State.cursor1.line, pos=State.cursor1.pos},screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos}, - edit in source_edit.lua at line 370[8.159552]→[8.159552:159569](∅→∅),[8.159569]→[8.25858:25887](∅→∅),[8.25887]→[8.159598:159838](∅→∅),[8.159598]→[8.159598:159838](∅→∅),[8.159838]→[8.25888:25917](∅→∅),[8.21581]→[8.159867:160455](∅→∅),[8.25917]→[8.159867:160455](∅→∅),[8.159867]→[8.159867:160455](∅→∅)
-- bifold textelseif chord == 'M-b' thenState.expanded = not State.expandedText.redraw_all(State)if not State.expanded thenfor _,line in ipairs(State.lines) doline.expanded = nilendedit.eradicate_locations_after_the_fold(State)endelseif chord == 'M-d' thenif State.cursor1.posB == nil thenlocal before = snapshot(State, State.cursor1.line)if State.lines[State.cursor1.line].dataB == nil thenState.lines[State.cursor1.line].dataB = ''endState.lines[State.cursor1.line].expanded = trueState.cursor1.pos = nilState.cursor1.posB = 1if Text.cursor_out_of_screen(State) thenText.snap_cursor_to_bottom_of_screen(State, State.left, State.right)endschedule_save(State)record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})end - replacement in source_edit.lua at line 375
edit.update_font_settings(State, State.font_height-2)Text.redraw_all(State)if State.font_height > 2 thenedit.update_font_settings(State, State.font_height-2)Text.redraw_all(State)end - replacement in source_edit.lua at line 417
State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1, posB=nil}State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1} - edit in source_edit.lua at line 492
function edit.eradicate_locations_after_the_fold(State)-- eradicate side B from any locations we trackif State.cursor1.posB thenState.cursor1.posB = nilState.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data)State.cursor1.pos = Text.pos_at_start_of_screen_line(State, State.cursor1)endif State.screen_top1.posB thenState.screen_top1.posB = nilState.screen_top1.pos = utf8.len(State.lines[State.screen_top1.line].data)State.screen_top1.pos = Text.pos_at_start_of_screen_line(State, State.screen_top1)endend - replacement in source.lua at line 78
-- environment for a mutable file of bifolded text-- environment for a mutable file - edit in source.lua at line 92
-- We currently start out with side B collapsed.-- Other options:-- * save all expanded state by line-- * expand all if any location is in side B - edit in source.lua at line 98
edit.eradicate_locations_after_the_fold(Editor_state) - edit in source.lua at line 251
-- convert any bifold files here - edit in source.lua at line 253
function source.convert_bifold_text(infilename, outfilename)local contents = love.filesystem.read(infilename)contents = contents:gsub('\u{1e}', ';')love.filesystem.write(outfilename, contents)end - replacement in log_browser.lua at line 15
Log_browser_state.cursor1 = {line=1, pos=nil}Log_browser_state.cursor1 = {line=1, pos=1} - replacement in log_browser.lua at line 240
Editor_state.cursor1 = {line=line.line_number, pos=1, posB=nil}Editor_state.cursor1 = {line=line.line_number, pos=1} - edit in log_browser.lua at line 246
-- expand B sideEditor_state.expanded = true - resurrect zombie in edit.lua at line 82
or not Text.le1(State.screen_top1, State.cursor1) thenState.screen_top1 = {line=1, pos=1} - resurrect zombie in edit.lua at line 85[3.955]→[8.518:648](∅→∅),[8.518]→[8.518:648](∅→∅),[8.518]→[8.518:648](∅→∅),[8.790]→[8.790:795](∅→∅),[8.790]→[8.790:795](∅→∅)
endendfunction edit.invalid1(State, loc1)return loc1.line > #State.linesor loc1.pos > #State.lines[loc1.line].dataend