It's just uneconomic to maintain given how little I've used it. I have a bug right now and no time to port the bugfix to all the complexities of the B side.
I briefly considered tossing out the entire source editor. But I have been using it to browse logs across sessions. The live editor doesn't quite cover all my use cases just yet.
We now have duplication in the source editor only for:
G54H3YG2NEZPW2F6OYT5JPV7KSKVMNW5D3QT3FBCXTJHAQYTV5UAC XW7ANEJXQBJYDKERVBPF2FMZFO6KENH3ZS7IRVS7CXOAI6N5RWZQC B4JEWKWIFIOJ7MIWNDPR3L34NIH4WORBELAWFOPAMWL32SVIJ4YQC BH7BT36LM3D7HF3GOHXUPVNKLJ5LFJHOHRLD3KTC5HA627M3II4AC SWZAQHGRX3T5MZVM7YZ4EACZX5ON2NUZG5YXLWVEXLL3WZUZYAFQC QCPXQ2E3USF3Z6R6WJ2JKHTRMPKA6QWXFKKRMLXA3MXABJEL543AC 3MAZEQK5AR3IJJ2ENHHYDPDICIK645NE5QWR54Z52BHGHE6VR5XQC BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC UHB4GARJI5AB5UCDCZRFSCJNXGJSLU5DYGUGX5ITYEXI7Q43Z4CAC KKMFQDR43ZWVCDRHQLWWX3FCWCFA3ZSXYOBRJNPHUQZR2XPKWULAC KMSL74GAMFNTAKGDKZFP2AMQXUMOC3XH373BO4IABZWBEP3YAXKAC OI4FPFINEROK6GNDEMOBTGSPYIULCLRGGT5W3H7VLM7VFH22GMWQC -- 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)
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
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_width
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)
function 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)endx = x + frag_widthlocal frag_len = utf8.len(frag)pos = pos + frag_lenendendfunction 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
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+1end
local 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
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
elseif 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 Bend
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 sideend
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}
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
local 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
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) + 1end
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1
-- 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
assert(State.cursor1.posB > 1)if Text.match(State.lines[State.cursor1.line].dataB, State.cursor1.posB-1, '%s') thenbreakendText.left(State)endendfunction 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)
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)
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
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
function 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,iendendassert(false)end
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.posB
return screen_lines[#screen_lines] <= State.cursor1.pos
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.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.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)
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_lines
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)
-- 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
--? 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))
--? 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) then
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
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) - 1
local 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
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
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)
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 result
function Text.eq1(a, b)return a.line == b.line and a.pos == b.pos
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}end
return {line=loc2.line-1, screen_line=#State.line_cache[loc2.line-1].screen_line_starting_pos, screen_pos=1}
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
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),}
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 screen
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 screen
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)
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)
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},
-- 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
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