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 drawn
function Text.draw(State, line_index, y, startpos, startposB, hide_cursor)
-- return the final y, and position of start of final screen line drawn
function Text.draw(State, line_index, y, startpos, hide_cursor)
line_cache.startposB = startposB
-- draw A side
local overflows_screen, x, pos, screen_line_starting_pos
if startpos then
overflows_screen, x, y, pos, screen_line_starting_pos = Text.draw_wrapping_line(State, line_index, State.left, y, startpos)
if overflows_screen then
return y, screen_line_starting_pos
end
if Focus == 'edit' and State.cursor1.pos then
if not hide_cursor and not State.search_term then
if line_index == State.cursor1.line and State.cursor1.pos == pos then
Text.draw_cursor(State, x, y)
end
end
end
else
x = State.left
end
-- check for B side
--? if line_index == 8 then print('checking for B side') end
if line.dataB == nil then
assert(y)
assert(screen_line_starting_pos)
--? if line_index == 8 then print('return 1') end
return y, screen_line_starting_pos
end
if not State.expanded and not line.expanded then
assert(y)
assert(screen_line_starting_pos)
--? if line_index == 8 then print('return 2') end
button(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 = true
end,
})
return y, screen_line_starting_pos
end
-- draw B side
--? if line_index == 8 then print('drawing B side') end
App.color(Fold_color)
if startposB then
overflows_screen, x, y, pos, screen_line_starting_pos = Text.draw_wrapping_lineB(State, line_index, x,y, startposB)
else
overflows_screen, x, y, pos, screen_line_starting_pos = Text.draw_wrapping_lineB(State, line_index, x+AB_padding,y, 1)
end
if overflows_screen then
return y, nil, screen_line_starting_pos
end
--? if line_index == 8 then print('a') end
if Focus == 'edit' and State.cursor1.posB then
--? if line_index == 8 then print('b') end
if 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) end
if line_index == State.cursor1.line and State.cursor1.posB == pos then
Text.draw_cursor(State, x, y)
end
end
end
return y, nil, screen_line_starting_pos
end
-- 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 drawn
function 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 lines
local x = State.left
local pos = 1
return false, x,y, pos, screen_line_starting_pos
end
function 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 = startpos
Text.compute_fragmentsB(State, line_index, x)
local pos = 1
for _, f in ipairs(line_cache.fragmentsB) do
local frag, frag_text = f.data, f.text
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 fragment
local frag_width = App.width(frag_text)
if x + frag_width > State.right then
assert(x > State.left) -- no overfull lines
y = y + State.line_height
if y + State.line_height > App.screen.height then
return --[[screen filled]] true, x,y, pos, screen_line_starting_pos
end
screen_line_starting_pos = pos
x = State.left
end
if State.selection1.line then
local lo, hi = Text.clip_selection(State, line_index, pos, pos+frag_len)
Text.draw_highlight(State, line, x,y, pos, lo,hi)
end
App.screen.draw(frag_text, x,y)
-- render cursor if necessary
if State.cursor1.posB and line_index == State.cursor1.line then
if pos <= State.cursor1.posB and pos + frag_len > State.cursor1.posB then
if State.search_term then
if State.lines[State.cursor1.line].dataB:sub(State.cursor1.posB, State.cursor1.posB+utf8.len(State.search_term)-1) == State.search_term then
local 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)
end
elseif Focus == 'edit' then
Text.draw_cursor(State, x+Text.x(frag, State.cursor1.posB-pos+1), y)
App.color(Fold_color)
end
end
end
x = x + frag_width
if Focus == 'edit' and not hide_cursor and State.search_term == nil then
if line_index == State.cursor1.line and State.cursor1.pos == pos then
Text.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 then
return
end
-- duplicate some logic from Text.draw
Text.compute_fragmentsB(State, line_index, x)
line_cache.screen_line_starting_posB = {1}
local pos = 1
for _, f in ipairs(line_cache.fragmentsB) do
local frag, frag_text = f.data, f.text
-- render fragment
local frag_width = App.width(frag_text)
if x + frag_width > State.right then
x = State.left
table.insert(line_cache.screen_line_starting_posB, pos)
end
x = x + frag_width
local frag_len = utf8.len(frag)
pos = pos + frag_len
end
end
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 then
return
end
line_cache.fragmentsB = {}
-- try to wrap at word boundaries
for frag in line.dataB:gmatch('%S*%s*') do
local 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 narrow
local 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)
end
x = State.left -- new line
end
if #frag > 0 then
--? print('inserting ^'..frag..'$ of width '..tostring(frag_width)..'px')
table.insert(line_cache.fragmentsB, {data=frag, text=frag_text})
end
x = x + frag_width
end
end
if State.cursor1.pos then
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
else
assert(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+1
end
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
end
elseif State.cursor1.posB then
if State.cursor1.posB > 1 then
before = 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 then
if byte_end then
State.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)
else
State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1)
end
State.cursor1.posB = State.cursor1.posB-1
end
else
-- refuse to delete past beginning of side B
elseif State.cursor1.posB then
if State.cursor1.posB <= utf8.len(State.lines[State.cursor1.line].dataB) then
local 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 then
if byte_end then
State.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)
else
State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1)
end
-- no change to State.cursor1.pos
end
else
-- refuse to delete past end of side B
end
if State.cursor1.pos then
-- when inserting a newline, move any B side to the new line
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), 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 = nil
Text.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 side
end
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 line
local new_cursor_line = State.cursor1.line
while new_cursor_line > 1 do
new_cursor_line = new_cursor_line-1
if State.lines[new_cursor_line].mode == 'text' then
State.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) - 1
break
end
end
elseif screen_line_indexB == 2 then
-- all-B screen-line to potentially A+B screen-line
local xA = Margin_left + Text.screen_line_width(State, State.cursor1.line, #line_cache.screen_line_starting_pos) + AB_padding
if State.cursor_x < xA then
State.cursor1.posB = nil
Text.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) - 1
else
Text.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) - 1
end
else
assert(screen_line_indexB > 2)
-- all-B screen-line to all-B screen-line
Text.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) - 1
end
if Text.lt1(State.cursor1, State.screen_top1) then
local top2 = Text.to2(State, State.screen_top1)
top2 = Text.previous_screen_line(State, top2)
State.screen_top1 = Text.to1(State, top2)
end
end
-- 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) - 1
if scroll_down then
Text.snap_cursor_to_bottom_of_screen(State)
end
if State.cursor1.pos then
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1
else
State.cursor1.posB = utf8.len(State.lines[State.cursor1.line].dataB) + 1
end
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1
-- we can cross the fold, so check side A/B one level down
Text.skip_whitespace_left(State)
Text.left(State)
Text.skip_non_whitespace_left(State)
end
function Text.word_right(State)
-- we can cross the fold, so check side A/B one level down
Text.skip_whitespace_right(State)
Text.right(State)
Text.skip_non_whitespace_right(State)
if Text.cursor_out_of_screen(State) then
Text.snap_cursor_to_bottom_of_screen(State)
end
end
function Text.skip_whitespace_left(State)
if State.cursor1.pos then
Text.skip_whitespace_leftA(State)
else
Text.skip_whitespace_leftB(State)
end
end
function Text.skip_non_whitespace_left(State)
if State.cursor1.pos then
Text.skip_non_whitespace_leftA(State)
else
Text.skip_non_whitespace_leftB(State)
end
end
function 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') then
break
end
Text.left(State)
end
end
function Text.skip_whitespace_right(State)
if State.cursor1.pos then
Text.skip_whitespace_rightA(State)
else
Text.skip_whitespace_rightB(State)
end
end
function Text.skip_non_whitespace_right(State)
if State.cursor1.pos then
Text.skip_non_whitespace_rightA(State)
else
Text.skip_non_whitespace_rightB(State)
end
function Text.skip_non_whitespace_rightB(State)
while true do
if State.cursor1.posB > utf8.len(State.lines[State.cursor1.line].dataB) then
break
end
if Text.match(State.lines[State.cursor1.line].dataB, State.cursor1.posB, '%s') then
break
end
Text.right_without_scroll(State)
if Text.cursor_out_of_screen(State) then
Text.snap_cursor_to_bottom_of_screen(State)
function Text.leftB(State)
if State.cursor1.posB > 1 then
State.cursor1.posB = State.cursor1.posB-1
else
-- overflow back into A side
State.cursor1.posB = nil
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1
end
if Text.lt1(State.cursor1, State.screen_top1) then
local top2 = Text.to2(State, State.screen_top1)
top2 = Text.previous_screen_line(State, top2)
State.screen_top1 = Text.to1(State, top2)
end
end
function Text.right_without_scrollB(State)
if State.cursor1.posB <= utf8.len(State.lines[State.cursor1.line].dataB) then
State.cursor1.posB = State.cursor1.posB+1
else
-- overflow back into A side
local new_cursor_line = State.cursor1.line
while new_cursor_line <= #State.lines-1 do
new_cursor_line = new_cursor_line+1
if State.lines[new_cursor_line].mode == 'text' then
State.cursor1 = {line=new_cursor_line, pos=1}
break
end
end
end
end
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_padding
Text.populate_screen_line_starting_posB(State, loc1.line, x)
for i=#line_cache.screen_line_starting_posB,1,-1 do
local sposB = line_cache.screen_line_starting_posB[i]
if sposB <= loc1.posB then
return sposB,i
end
end
assert(false)
end
if (not State.expanded and not line.expanded) or
line.dataB == nil then
return screen_lines[#screen_lines] <= State.cursor1.pos
end
if State.cursor1.pos then
-- ignore B side
return screen_lines[#screen_lines] <= State.cursor1.pos
end
assert(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_padding
Text.populate_screen_line_starting_posB(State, State.cursor1.line, x)
local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_posB
return screen_lines[#screen_lines] <= State.cursor1.posB
return screen_lines[#screen_lines] <= State.cursor1.pos
if top2.screen_pos then
top2.screen_pos = 1
else
assert(top2.screen_posB)
top2.screen_posB = 1
end
--? 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 = 0
if line_cache.startpos then
Text.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) + 1
end
--? print('#screenlines after A', num_screen_lines)
if line.dataB and (State.expanded or line.expanded) then
local x = Margin_left + Text.screen_line_width(State, line_index, #line_cache.screen_line_starting_pos) + AB_padding
Text.populate_screen_line_starting_posB(State, line_index, x)
--? print('B:', x, #line_cache.screen_line_starting_posB)
if line_cache.startposB then
num_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 side
else
num_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 side
end
end
--? 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 then
local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos)
for screen_line_index = start_screen_line_index,#line_cache.screen_line_starting_pos do
local screen_line_starting_pos = line_cache.screen_line_starting_pos[screen_line_index]
local screen_line_starting_byte_offset = Text.offset(line.data, screen_line_starting_pos)
--? print('iter', y, screen_line_index, screen_line_starting_pos, string.sub(line.data, screen_line_starting_byte_offset))
local nexty = y + State.line_height
if 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]-1
end
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)
local screen_line_posA = Text.nearest_cursor_pos(s, mx, State.left)
if line.dataB == nil then
-- no B side
return screen_line_starting_pos + screen_line_posA - 1
end
if not State.expanded and not line.expanded then
-- B side is not expanded
return screen_line_starting_pos + screen_line_posA - 1
end
local lenA = utf8.len(s)
if screen_line_posA < lenA then
-- mx is within A side
return screen_line_starting_pos + screen_line_posA - 1
end
local 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 side
return screen_line_starting_pos + screen_line_posA - 1
end
mx = mx - max_xA - AB_padding
local screen_line_posB = Text.nearest_cursor_pos(line.dataB, mx, --[[no left margin]] 0)
return nil, screen_line_posB
end
y = nexty
end
end
-- look in screen lines composed entirely of the B side
assert(State.expanded or line.expanded)
local start_screen_line_indexB
if line_cache.startposB then
start_screen_line_indexB = Text.screen_line_indexB(line_cache.screen_line_starting_posB, line_cache.startposB)
else
start_screen_line_indexB = 2 -- skip the first line of side B, which we checked above
end
for screen_line_indexB = start_screen_line_indexB,#line_cache.screen_line_starting_posB do
local 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 do
local screen_line_starting_pos = line_cache.screen_line_starting_pos[screen_line_index]
local screen_line_starting_byte_offset = Text.offset(line.data, screen_line_starting_pos)
--? print('iter', y, screen_line_index, screen_line_starting_pos, string.sub(line.data, screen_line_starting_byte_offset))
--? 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_line
if 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)
end
local screen_line_text = App.newText(love.graphics.getFont(), screen_line)
--? local result = App.width(screen_line_text)
--? print('=>', result)
--? return result
return App.width(screen_line_text)
end
return result
end
function 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_padding
Text.populate_screen_line_starting_posB(State, loc1.line, x)
for i=#line_cache.screen_line_starting_posB,1,-1 do
local sposB = line_cache.screen_line_starting_posB[i]
if sposB <= loc1.posB then
result.screen_lineB = i
result.screen_posB = loc1.posB - sposB + 1
break
end
end
assert(result.screen_posB)
function Text.to1B(State, loc2)
local result = {line=loc2.line, posB=loc2.screen_posB}
if loc2.screen_lineB > 1 then
result.posB = State.line_cache[loc2.line].screen_line_starting_posB[loc2.screen_lineB] + loc2.screen_posB - 1
end
return 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 B
local 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_padding
Text.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 A
return {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 side
return {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-line
return {line=loc2.line, screen_line=#State.line_cache[loc2.line].screen_line_starting_pos, screen_pos=1}
end
end
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 screen
cursor1 = {line=1, pos=1, posB=nil}, -- position of cursor
screen_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 screen
cursor1 = {line=1, pos=1}, -- position of cursor
screen_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 text
elseif chord == 'M-b' then
State.expanded = not State.expanded
Text.redraw_all(State)
if not State.expanded then
for _,line in ipairs(State.lines) do
line.expanded = nil
end
edit.eradicate_locations_after_the_fold(State)
end
elseif chord == 'M-d' then
if State.cursor1.posB == nil then
local before = snapshot(State, State.cursor1.line)
if State.lines[State.cursor1.line].dataB == nil then
State.lines[State.cursor1.line].dataB = ''
end
State.lines[State.cursor1.line].expanded = true
State.cursor1.pos = nil
State.cursor1.posB = 1
if Text.cursor_out_of_screen(State) then
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
end
schedule_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 track
if State.cursor1.posB then
State.cursor1.posB = nil
State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data)
State.cursor1.pos = Text.pos_at_start_of_screen_line(State, State.cursor1)
end
if State.screen_top1.posB then
State.screen_top1.posB = nil
State.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)
end
end