Merge lines.love

[?]
Mar 18, 2023, 5:29 AM
2TQUKHBC2EB3WDBD5UL62DQYV7CV6B7OJYK7CHOEDNOZENSOG42AC

Dependencies

  • [2] B4FAIVRA Merge lines.love
  • [3] U3MJNFUY Merge lines.love
  • [4] O4RRXNOK bugfix: disallow font size of 0
  • [5] LDFXFRUO bring a few things in sync between run and source
  • [6] LK4ZW4BB bugfix
  • [7] 3TI67SEJ more bugfix
  • [8] BJ5X5O4A let's prevent the text cursor from ever getting on a drawing
  • [9] A4BSGS2C Merge lines.love
  • [10] 6LJZN727 handle chords
  • [11] LNUHQOGH start passing in Editor_state explicitly
  • [12] LF7BWEG4 group all editor globals
  • [13] XS3PZI7G Merge lines.love
  • [14] FS2ITYYH record a known issue
  • [15] XX7G2FFJ intermingle freehand line drawings with text
  • [16] 6DYSB5DY bugfix: perform matches in the right order
  • [17] CE4LZV4T drop last couple of manual tests
  • [18] 66X36NZN a little more prose describing manual_tests
  • [19] AKZWDWIA Merge lines.love
  • [20] KMSL74GA support selections in the source editor
  • [21] TVCPXAAU rename
  • [22] 73OCE2MC after much struggle, a brute-force undo
  • [23] RSZD5A7G forgot to add json.lua
  • [24] VXORMHME delete experimental REPL
  • [25] ORRSP7FV deduce test names on failures
  • [26] 4SR3Z4Y3 document the version of LÖVE I've been using
  • [27] SCOXD4EO Merge lines.love
  • [28] MD3W5IRA new fork: rip out drawing support
  • [29] VBU5YHLR Merge lines.love
  • [30] APX2PY6G stop tracking wallclock time
  • [31] ETXNVRPT Merge lines.love
  • [32] LXTTOB33 extract a couple of files
  • [33] B4JEWKWI hide editor cursor while in file navigator
  • [34] BULPIBEG beginnings of a module for the text editor
  • [35] 3QNOKBFM beginnings of a test harness
  • [36] ZJOSQFN6 bugfix: path munging on Windows
  • [37] ZTZOO2OQ Merge lines.love
  • [38] ORKN6EOB Merge lines.love
  • [39] AYX33NBC Merge lines.love
  • [40] X3CQLBTR set window title within each app
  • [41] JOPVPUSA editing source code from within the app
  • [42] T4FRZSYL delete an ancient, unused file
  • [43] L2FWWEQL source: remember cursor position of multiple files
  • [44] 4VQGE7RA new test
  • [45] N7VXEGLG yet another bugfix in log parsing
  • [46] 2CTN2IEF Merge lines.love
  • [47] VHQCNMAR several more modules
  • [48] D4B52CQ2 Merge lines.love
  • [49] AOZX2G5F source: no commandline args
  • [50] 4YDBYBA4 clean up memory leak experiments
  • [51] GUOQRUL7 Merge lines.love
  • [52] KKMFQDR4 editing source code from within the app
  • [53] VP5KC4XZ Merge lines.love
  • [54] 7VGDIPLC more robust state validation
  • [55] G54H3YG2 get rid of all bifold text
  • [56] 2L5MEZV3 experiment: new edit namespace
  • [57] OTIBCAUJ love2d scaffold
  • [58] ATQO62TF Merge lines.love
  • [59] R5QXEHUI somebody stop me
  • [60] D2GCFTTT clean up repl functionality
  • [61] 2CK5QI7W make love event names consistent
  • [62] TLOAPLBJ add a license
  • [63] OGUV4HSA remove some memory leaks from rendered fragments
  • [64] K2X6G75Z start writing some tests for drawings
  • [65] K74U4BAU Merge lines.love
  • [66] UN7GKYV5 support hyperlinks in the source editor
  • [67] P3K7UH5C Merge lines.love
  • [68] VHUNJHXB Merge lines.love
  • [69] 3PSFWAIL Merge lines.love
  • [70] BH7BT36L ctrl+a: select entire buffer
  • [71] KYNGDE2C consistent names in a few more places
  • [72] YRJFJNUD bugfix
  • [73] BLWAYPKV extract a module
  • [74] ME7WBLF5 bugfix: log filenames can have 2 formats
  • [75] AVTNUQYR basic test-enabled framework
  • [76] OI4FPFIN support drawings in the source editor

Change contents

  • file deletion: source_text_tests.lua (----------)source_text_tests.lua (----------)
    [8.2][8.83676:83721](),[8.2][8.83676:83721](),[8.83721][8.3498:3498]()
    end
    function test_up_arrow_scrolls_up_by_one_line_skipping_drawing()
    -- display lines 3/4/5 with a drawing just off screen at line 2
    App.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.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to previous text line
    edit.run_after_keychord(Editor_state, 'up')
    check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'cursor')
    end
    function test_up_arrow_scrolls_up_by_one_screen_line()
  • file deletion: source_text.lua (----------)source_text.lua (----------)
    [8.2][8.147062:147101](),[8.2][8.147062:147101](),[8.147101][8.83723:83723]()
    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),
    }
    end
    end
    end
    -- slightly expensive since it redraws the screen
    function 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)
    end
    return {line=loc2.line-1, screen_line=#State.line_cache[loc2.line-1].screen_line_starting_pos, screen_pos=1}
    end
    end
    local l = State.lines[loc2.line-1]
    Text.populate_screen_line_starting_pos(State, loc2.line-1)
    elseif State.lines[loc2.line-1].mode == 'drawing' then
    return {line=loc2.line-1, screen_line=1, screen_pos=1}
    else
    return a.pos <= b.pos
    end
    if a.line > b.line then
    return false
    end
    return a.pos < b.pos
    end
    function Text.le1(a, b)
    if a.line < b.line then
    return true
    end
    function Text.eq1(a, b)
    return a.line == b.line and a.pos == b.pos
    end
    function Text.lt1(a, b)
    if a.line < b.line then
    return true
    end
    if a.line > b.line then
    return false
    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)
    return screen_line_starting_pos + Text.nearest_cursor_pos(s, mx, State.left) - 1
    end
    y = nexty
    end
    assert(false)
    end
    function 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_line
    if i < #line_cache.screen_line_starting_pos then
    local 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)
    else
    screen_line = string.sub(line.data, start_pos)
    end
    local screen_line_text = App.newText(love.graphics.getFont(), screen_line)
    return App.width(screen_line_text)
    end
    return line_cache.screen_line_starting_pos[screen_line_index+1]-1
    end
    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')
    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.)
    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 end
    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)
    --? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down')
    local y = App.screen.height - State.line_height
    -- duplicate some logic from love.draw
    while true do
    --? 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.pos
    end
    function 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 leaks
    end
    end
    if State.cursor1.pos > 1 then
    State.cursor1.pos = State.cursor1.pos-1
    if Text.cursor_out_of_screen(State) then
    Text.snap_cursor_to_bottom_of_screen(State)
    end
    end
    function 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)
    end
    function Text.left(State)
    function Text.word_right(State)
    -- skip some whitespace
    while true do
    if State.cursor1.pos > utf8.len(State.lines[State.cursor1.line].data) then
    break
    end
    if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos, '%S') then
    break
    end
    Text.right_without_scroll(State)
    end
    -- skip some non-whitespace
    while true do
    -- skip some whitespace
    while true do
    if State.cursor1.pos == 1 then
    break
    end
    if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos-1, '%S') then
    break
    end
    Text.left(State)
    end
    State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1
    if Text.cursor_out_of_screen(State) then
    Text.snap_cursor_to_bottom_of_screen(State)
    end
    end
    function Text.word_left(State)
    State.screen_top1 = {line=State.cursor1.line, pos=State.cursor1.pos} -- copy
    end
    end
    function Text.end_of_line(State)
    State.cursor1.pos = 1
    if Text.lt1(State.cursor1, State.screen_top1) then
    else
    -- move down one screen line in current line
    local 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 leaks
    end
    end
    Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
    --? print('top now', State.screen_top1.line)
    Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
    --? print('pagedown end')
    end
    function 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 then
    bot2.screen_line = math.max(bot2.screen_line-10, 1)
    end
    local new_top1 = Text.to1(State, bot2)
    if Text.lt1(State.screen_top1, new_top1) then
    State.screen_top1 = new_top1
    else
    -- 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')
    end
    function Text.pagedown(State)
    --? print('pagedown')
    if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end
    if State.lines[State.screen_top1.line].mode == 'text' then
    y = y - State.line_height
    elseif State.lines[State.screen_top1.line].mode == 'drawing' then
    y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
    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}
    end
    function Text.pageup(State)
    --? print('pageup')
    -- duplicate some logic from love.draw
    local top2 = Text.to2(State, State.screen_top1)
    --? print(App.screen.height)
    local y = App.screen.height - State.line_height
    while y >= State.top do
    --? print(y, top2.line, top2.screen_line, top2.screen_pos)
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
    end
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
    end
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
    end
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
    end
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
    end
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
    end
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
    end
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
    end
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
    end
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
    end
    if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
    local 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 then
    if byte_end then
    State.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)
    else
    State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)
    end
    -- no change to State.cursor1.pos
    end
    if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
    before = snapshot(State, State.cursor1.line)
    else
    before = snapshot(State, State.cursor1.line, State.cursor1.line+1)
    end
    if State.cursor1.pos > 1 then
    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
    end
    Text.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' then
    before = 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 then
    if byte_end then
    State.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)
    else
    State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_start-1)
    end
    State.cursor1.pos = State.cursor1.pos-1
    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
    return y, screen_line_starting_pos
    end
    function Text.draw_cursor(State, x, y)
    -- blink every 0.5s
    if math.floor(Cursor_time*2)%2 == 0 then
    App.color(Cursor_color)
    love.graphics.rectangle('fill', x,y, 3,State.line_height)
    end
    State.cursor_x = x
    State.cursor_y = y+State.line_height
    end
    function 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 then
    if line_index == State.cursor1.line and State.cursor1.pos == pos then
    Text.draw_cursor(State, x, y)
    end
    if line_index == State.cursor1.line then
    if pos <= State.cursor1.pos and pos + frag_len > State.cursor1.pos then
    if State.search_term then
    if State.lines[State.cursor1.line].data:sub(State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term)-1) == State.search_term then
    local 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)
    end
    elseif Focus == 'edit' then
    Text.draw_cursor(State, x+Text.x(frag, State.cursor1.pos-pos+1), y)
    App.color(Text_color)
    end
    end
    end
    x = x + frag_width
    end
    pos = pos + frag_len
    end
    return y, screen_line_starting_pos
    end
    screen_line_starting_pos = pos
    x = State.left
    function ends_with(s, suffix)
    if #s < #suffix then
    return false
    end
    for i=0,#suffix-1 do
    if s:sub(#s-i,#s-i) ~= suffix:sub(#suffix-i,#suffix-i) then
    return false
    end
    end
    return true
    end
    -- wrap long lines
    local x = State.left
    local pos = 1
    local screen_line_starting_pos = startpos
    Text.compute_fragments(State, line_index)
    local pos = 1
    initialize_color()
    for _, f in ipairs(line_cache.fragments) do
    App.color(Text_color)
    local frag, frag_text = f.data, f.text
    select_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 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 the final y, and position of start of final screen line drawn
    function 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 = y
    line_cache.startpos = startpos
    function starts_with(s, prefix)
    if #s < #prefix then
    return false
    end
    for i=1,#prefix do
    if s:sub(i,i) ~= prefix:sub(i,i) then
    return false
    end
    end
    return true
    end
  • file deletion: source_edit.lua (----------)source_edit.lua (----------)
    [8.2][8.165725:165764](),[8.2][8.165725:165764](),[8.165764][8.152440:152440]()
    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
    State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1}
    elseif chord == 'C-c' then
    local s = Text.selection(State)
    if s then
    App.setClipboardText(s)
    end
    elseif chord == 'C-x' then
    for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
    local s = Text.cut_selection(State, State.left, State.right)
    if s then
    App.setClipboardText(s)
    end
    schedule_save(State)
    elseif chord == 'C-v' then
    for _,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.line
    local before = snapshot(State, before_line)
    local clipboard_data = App.getClipboardText()
    for _,code in utf8.codes(clipboard_data) do
    local c = utf8.char(code)
    if c == '\n' then
    Text.insert_return(State)
    else
    Text.insert_at_cursor(State, c)
    end
    end
    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, 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+1
    Text.search_next(State)
    elseif chord == 'up' then
    Text.search_previous(State)
    end
    return
    elseif chord == 'C-f' then
    State.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 then
    if State.old_selection1.line == nil then
    State.selection1 = State.old_cursor1
    else
    State.selection1 = State.old_selection1
    end
    end
    State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
    if eq(State.cursor1, State.selection1) then
    State.selection1 = {}
    end
    break
    end
    end
    end
    --? 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)
    break
    end
    elseif line.mode == 'drawing' then
    local line_cache = State.line_cache[line_index]
    if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
    State.lines.current_drawing_index = line_index
    State.lines.current_drawing = line
    Drawing.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' then
    y = y+Drawing_padding_top
    Drawing.draw(State, line_index, y)
    y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom
    else
    print(line.mode)
    assert(false)
    startpos = State.screen_top1.pos
    end
    if line.data == '' then
    -- button to insert new drawing
    button(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 then
    State.cursor1.line = State.cursor1.line+1
    end
    schedule_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 = 1
    if line_index == State.screen_top1.line then
    State.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 end
    print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
    assert(false)
    end
    State.cursor_x = nil
    State.cursor_y = nil
    local y = State.top
    --? print('== draw')
    for line_index = State.screen_top1.line,#State.lines do
    local line = State.lines[line_index]
    selection1 = {},
    -- some extra state to compute selection between mouse press and release
    old_cursor1 = nil,
    old_selection1 = nil,
    mousepress_shift = nil,
    -- when selecting text, avoid recomputing some state on every single frame
    recent_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 line
    line_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-circuit
    return
    end
    for line_index,line in ipairs(State.lines) do
    -- give some time for the OS to flush everything to disk
    love.timer.sleep(0.1)
    end
    end
    left = math.floor(left),
    right = math.floor(right),
    width = right-left,
    if State.font_height > 2 then
    edit.update_font_settings(State, State.font_height-2)
    Text.redraw_all(State)
    end
    elseif chord == 'C-0' then
    edit.update_font_settings(State, 20)
    Text.redraw_all(State)
    -- undo
    elseif chord == 'C-z' then
    for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
    local event = undo_event(State)
    if event then
    local src = event.before
    State.screen_top1 = deepcopy(src.screen_top)
    State.cursor1 = deepcopy(src.cursor)
  • file deletion: source.lua (----------)source.lua (----------)
    [8.2][8.177652:177686](),[8.2][8.177652:177686](),[8.177686][8.165766:165766]()
    -- 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 (----------)
    [8.2][8.203223:203262](),[8.2][8.203223:203262](),[8.203262][8.191782:191782]()
    Editor_state.cursor1 = {line=line.line_number, pos=1}
    -- make sure it's visible
    -- TODO: handle extremely long lines
    Editor_state.screen_top1.line = math.max(0, Editor_state.cursor1.line-5)
    -- show cursor
    Focus = 'edit'
    Log_browser_state.cursor1 = {line=1, pos=1}
    end
    Section_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 borders
    Section_border_padding_vertical = 15 -- TODO: adjust this based on font height
    log_browser = {}
    function log_browser.parse(State)
    for _,line in ipairs(State.lines) do
    if line.data ~= '' then
  • edit in text_tests.lua at line 1177
    [8.12204][6.2:965]()
    end
    function test_up_arrow_scrolls_up_by_one_line_skipping_drawing()
    -- display lines 3/4/5 with a drawing just off screen at line 2
    App.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.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to previous text line
    edit.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
    [8.11690]
    [8.49070]
    end
    function test_up_arrow_scrolls_up_by_one_line_skipping_drawing()
    -- display lines 3/4/5 with a drawing just off screen at line 2
    App.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.top
    App.screen.check(y, 'def', 'baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to previous text line
    edit.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
    [8.83876][8.83876:83938]()
    AB_padding = 20 -- space in pixels between A side and B side
  • replacement in source_text.lua at line 5
    [8.84027][8.84027:84099](),[8.84099][8.7:82]()
    -- 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)
    [8.84027]
    [8.84161]
    -- return the final y, and position of start of final screen line drawn
    function 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 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..'$')
    [8.84307]
    [8.87368]
    -- wrap long lines
    local x = State.left
    local pos = 1
  • replacement in source_text.lua at line 34
    [8.88090][8.88090:88168]()
    return --[[screen filled]] true, x,y, pos, screen_line_starting_pos
    [8.88090]
    [8.88168]
    return y, screen_line_starting_pos
  • replacement in source_text.lua at line 59
    [8.88326][8.88326:88395]()
    if State.cursor1.pos and line_index == State.cursor1.line then
    [8.88326]
    [8.88395]
    if 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_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
    [8.89166]
    [8.91082]
    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)
  • edit in source_text.lua at line 81
    [8.91090][8.91090:91115]()
    pos = pos + frag_len
  • replacement in source_text.lua at line 82
    [8.91121][8.91121:91172]()
    return false, x,y, pos, screen_line_starting_pos
    [8.91121]
    [8.91172]
    return y, screen_line_starting_pos
  • edit in source_text.lua at line 162
    [8.94299][8.94299:94996]()
    end
    x = x + frag_width
    end
    end
    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)
  • edit in source_text.lua at line 164
    [8.95027][8.95027:95088]()
    local frag_len = utf8.len(frag)
    pos = pos + frag_len
  • edit in source_text.lua at line 167
    [8.95099][8.95099:97167]()
    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
  • replacement in source_text.lua at line 181
    [8.97898][8.97898:98723]()
    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
    [8.97898]
    [8.98723]
    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
  • replacement in source_text.lua at line 219
    [8.100157][8.100157:100213]()
    if State.cursor1.pos and State.cursor1.pos > 1 then
    [8.100157]
    [8.100213]
    if State.cursor1.pos > 1 then
  • edit in source_text.lua at line 230
    [8.100865][8.100865:101706]()
    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
  • replacement in source_text.lua at line 250
    [8.102575][8.102575:102754]()
    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)
    [8.102575]
    [8.102754]
    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
    [8.103137][8.103137:103239]()
    if State.cursor1.posB or State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
    [8.103137]
    [8.103239]
    if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
  • replacement in source_text.lua at line 272
    [8.103380][8.103380:103482]()
    if State.cursor1.pos and State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
    [8.103380]
    [8.103482]
    if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
  • edit in source_text.lua at line 282
    [8.104077][8.104077:104898]()
    end
    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
  • edit in source_text.lua at line 287
    [8.4721][8.4721:4848]()
    -- delete side B on first line
    State.lines[State.cursor1.line].dataB = State.lines[State.cursor1.line+1].dataB
  • replacement in source_text.lua at line 303
    [8.21621][8.21621:21720]()
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    [8.21621]
    [8.21720]
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
  • replacement in source_text.lua at line 308
    [8.21770][8.21770:21869]()
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    [8.21770]
    [8.21869]
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
  • replacement in source_text.lua at line 320
    [8.21973][8.21973:22072]()
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    [8.21973]
    [8.22072]
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
  • replacement in source_text.lua at line 325
    [8.22122][8.22122:22221]()
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    [8.22122]
    [8.22221]
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
  • replacement in source_text.lua at line 336
    [8.22325][8.22325:22424]()
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    [8.22325]
    [8.22424]
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
  • replacement in source_text.lua at line 341
    [8.22474][8.22474:22573]()
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    [8.22474]
    [8.22573]
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
  • replacement in source_text.lua at line 352
    [8.22677][8.22677:22776]()
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    [8.22677]
    [8.22776]
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
  • replacement in source_text.lua at line 357
    [8.22826][8.22826:22925]()
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    [8.22826]
    [8.22925]
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
  • replacement in source_text.lua at line 368
    [8.23029][8.23029:23128]()
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    [8.23029]
    [8.23128]
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos}
  • replacement in source_text.lua at line 373
    [8.23178][8.23178:23277]()
    State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
    [8.23178]
    [8.23277]
    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 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
    [8.106779]
    [8.107529]
    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}
  • replacement in source_text.lua at line 396
    [8.107845][8.107845:107952]()
    if State.screen_top1.line == 1 and State.screen_top1.pos and State.screen_top1.pos == 1 then break end
    [8.107845]
    [8.5040]
    if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end
  • replacement in source_text.lua at line 405
    [8.108082][8.108082:108186]()
    State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos, posB=State.screen_top1.posB}
    [8.108082]
    [8.108186]
    State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
  • edit in source_text.lua at line 413
    [8.108453]
    [8.108453]
    -- 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
    [8.108506]
    [8.108506]
    if bot2.screen_line > 1 then
    bot2.screen_line = math.max(bot2.screen_line-10, 1)
    end
  • replacement in source_text.lua at line 424
    [8.108635][8.5320:5439]()
    State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos, posB=State.screen_bottom1.posB}
    [8.108635]
    [8.108722]
    State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
  • replacement in source_text.lua at line 427
    [8.108805][8.108805:108909]()
    State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos, posB=State.screen_top1.posB}
    [8.108805]
    [8.108909]
    State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
  • edit in source_text.lua at line 436
    [8.5497][8.109190:109301](),[8.109190][8.109190:109301]()
    if State.cursor1.pos then
    Text.upA(State)
    else
    Text.upB(State)
    end
    end
    function Text.upA(State)
  • replacement in source_text.lua at line 469
    [8.111230][8.111230:111378]()
    local top2 = Text.to2(State, State.screen_top1)
    top2 = Text.previous_screen_line(State, top2)
    State.screen_top1 = Text.to1(State, top2)
    [8.111230]
    [8.111378]
    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 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
  • replacement in source_text.lua at line 501
    [8.115806][8.115806:115896]()
    elseif State.cursor1.pos then
    -- move down one screen line (A side) in current line
    [8.115806]
    [8.115896]
    else
    -- move down one screen line in current line
  • edit in source_text.lua at line 518
    [8.117112][8.117112:118238]()
    else
    -- move down one screen line (B side) in current line
    local scroll_down = false
    if Text.le1(State.screen_bottom1, State.cursor1) then
    scroll_down = true
    end
    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
  • replacement in source_text.lua at line 523
    [8.118441][8.118441:118535]()
    if State.cursor1.pos then
    State.cursor1.pos = 1
    else
    State.cursor1.posB = 1
    end
    [8.118441]
    [8.118535]
    State.cursor1.pos = 1
  • replacement in source_text.lua at line 525
    [8.118588][8.7955:8062]()
    State.screen_top1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} -- copy
    [8.118588]
    [8.118654]
    State.screen_top1 = {line=State.cursor1.line, pos=State.cursor1.pos} -- copy
  • replacement in source_text.lua at line 530
    [8.118698][8.118698:118891]()
    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
    [8.118698]
    [8.118891]
    State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1
  • replacement in source_text.lua at line 537
    [8.119024][8.119024:119857]()
    -- 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)
    [8.119024]
    [8.119857]
    -- skip some whitespace
  • replacement in source_text.lua at line 547
    [8.120063][8.120063:120111]()
    end
    function Text.skip_whitespace_leftB(State)
    [8.120063]
    [8.120111]
    -- skip some non-whitespace
  • edit in source_text.lua at line 549
    [8.120127][8.120127:120293]()
    if State.cursor1.posB == 1 then
    break
    end
    if Text.match(State.lines[State.cursor1.line].dataB, State.cursor1.posB-1, '%S') then
    break
    end
  • edit in source_text.lua at line 550
    [8.120314][8.120314:120388]()
    end
    end
    function Text.skip_non_whitespace_leftA(State)
    while true do
  • edit in source_text.lua at line 555
    [8.120565][8.120565:120861]()
    break
    end
    Text.left(State)
    end
    end
    function Text.skip_non_whitespace_leftB(State)
    while true do
    if State.cursor1.posB == 1 then
    break
    end
    assert(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
    [8.120881][8.120881:120902]()
    Text.left(State)
  • replacement in source_text.lua at line 560
    [8.120913][8.120913:121303]()
    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
    end
    function Text.skip_whitespace_rightA(State)
    [8.120913]
    [8.121303]
    function Text.word_right(State)
    -- skip some whitespace
  • edit in source_text.lua at line 571
    [8.121567][8.121567:121616]()
    end
    function Text.skip_whitespace_rightB(State)
  • edit in source_text.lua at line 572
    [8.121632][8.121632:121841]()
    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
  • edit in source_text.lua at line 573
    [8.121878][8.121878:121953]()
    end
    end
    function Text.skip_non_whitespace_rightA(State)
    while true do
  • edit in source_text.lua at line 579
    [8.122158][8.122158:122195]()
    Text.right_without_scroll(State)
  • replacement in source_text.lua at line 580
    [8.122201][8.122201:122516]()
    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)
    [8.122201]
    [8.122516]
    if Text.cursor_out_of_screen(State) then
    Text.snap_cursor_to_bottom_of_screen(State)
  • replacement in source_text.lua at line 595
    [8.122810][8.122810:122927]()
    if State.cursor1.pos then
    Text.leftA(State)
    else
    Text.leftB(State)
    end
    end
    function Text.leftA(State)
    [8.122810]
    [8.122927]
    assert(State.lines[State.cursor1.line].mode == 'text')
  • replacement in source_text.lua at line 612
    [8.123228][8.123228:123376]()
    local top2 = Text.to2(State, State.screen_top1)
    top2 = Text.previous_screen_line(State, top2)
    State.screen_top1 = Text.to1(State, top2)
    [8.123228]
    [8.123376]
    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
    [8.123387][8.123387:123855]()
    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
  • edit in source_text.lua at line 629
    [8.8475][8.124061:124226](),[8.124061][8.124061:124226]()
    if State.cursor1.pos then
    Text.right_without_scrollA(State)
    else
    Text.right_without_scrollB(State)
    end
    end
    function 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) 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
  • edit in source_text.lua at line 650
    [8.125105][8.125105:125649]()
    end
    end
    assert(false)
    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
  • edit in source_text.lua at line 657
    [8.125801][8.125801:125848]()
    local line = State.lines[State.cursor1.line]
  • replacement in source_text.lua at line 659
    [8.125993][8.125993:126685]()
    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
    [8.125993]
    [8.126685]
    return screen_lines[#screen_lines] <= State.cursor1.pos
  • replacement in source_text.lua at line 687
    [8.127017][8.127017:127096]()
    --? print('to2:', State.cursor1.line, State.cursor1.pos, State.cursor1.posB)
    [8.127017]
    [8.127096]
    --? print('to2:', State.cursor1.line, State.cursor1.pos)
  • replacement in source_text.lua at line 689
    [8.127142][8.127142:127248]()
    --? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos, top2.screen_lineB, top2.screen_posB)
    [8.127142]
    [8.127248]
    --? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos)
  • replacement in source_text.lua at line 691
    [8.127283][8.127283:127630]()
    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)
    [8.127283]
    [8.127630]
    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
    [8.127858][8.127858:127996]()
    --? print(y, 'top2:', State.lines[top2.line].data, top2.line, top2.screen_line, top2.screen_pos, top2.screen_lineB, top2.screen_posB)
    [8.127858]
    [8.127996]
    --? print(y, 'top2:', top2.line, top2.screen_line, top2.screen_pos)
  • replacement in source_text.lua at line 722
    [8.128406][8.128406:128639]()
    --? 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)
    [8.128406]
    [8.128639]
    --? 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
    [8.129011][8.129011:130281]()
    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
    [8.129011]
    [8.130281]
    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)
  • edit in source_text.lua at line 736
    [8.130337][8.130337:130803]()
    -- 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
    [8.131053][8.131053:134381]()
    --? 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))
    [8.131053]
    [8.134381]
    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))
  • replacement in source_text.lua at line 752
    [8.134662][8.134662:134922]()
    --? 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
    [8.134662]
    [8.134922]
    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
  • replacement in source_text.lua at line 754
    [8.134978][8.134978:135059]()
    return nil, line_cache.screen_line_starting_posB[screen_line_indexB+1]-1
    [8.134978]
    [8.135059]
    return line_cache.screen_line_starting_pos[screen_line_index+1]-1
  • replacement in source_text.lua at line 756
    [8.135069][8.135069:135392]()
    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
    [8.135069]
    [8.135392]
    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
  • edit in source_text.lua at line 782
    [8.136158][8.136158:137142]()
    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
  • edit in source_text.lua at line 790
    [8.137330][8.137330:137601]()
    function Text.screen_line_indexB(screen_line_starting_posB, posB)
    if posB == nil then
    return 0
    end
    assert(screen_line_starting_posB)
    for i = #screen_line_starting_posB,1,-1 do
    if screen_line_starting_posB[i] <= posB then
    return i
    end
    end
    end
  • edit in source_text.lua at line 881
    [8.10486][8.10486:10492](),[8.10492][8.139954:140048](),[8.139954][8.139954:140048]()
    end
    if loc1.pos then
    return Text.to2A(State, loc1)
    else
    return Text.to2B(State, loc1)
  • edit in source_text.lua at line 882
    [8.140054][8.140054:140091]()
    end
    function Text.to2A(State, loc1)
  • edit in source_text.lua at line 894
    [8.140500][8.140500:141153]()
    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)
  • edit in source_text.lua at line 898
    [8.141205][8.141205:141349]()
    if loc2.screen_pos then
    return Text.to1A(State, loc2)
    else
    return Text.to1B(State, loc2)
    end
    end
    function Text.to1A(State, loc2)
  • replacement in source_text.lua at line 905
    [8.141572][8.141572:141829]()
    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
    [8.141572]
    [8.141829]
    function Text.eq1(a, b)
    return a.line == b.line and a.pos == b.pos
  • replacement in source_text.lua at line 916
    [8.141955][8.141955:142006]()
    -- A side < B side
    if a.pos and not b.pos then
    [8.141955]
    [8.142006]
    return a.pos < b.pos
    end
    function Text.le1(a, b)
    if a.line < b.line then
  • replacement in source_text.lua at line 923
    [8.142028][8.142028:142058]()
    if not a.pos and b.pos then
    [8.142028]
    [8.142058]
    if a.line > b.line then
  • replacement in source_text.lua at line 926
    [8.142081][8.142081:142162]()
    if a.pos then
    return a.pos < b.pos
    else
    return a.posB < b.posB
    end
    [8.142081]
    [8.142162]
    return a.pos <= b.pos
  • edit in source_text.lua at line 929
    [8.142167][8.142167:142232]()
    function Text.le1(a, b)
    return eq(a, b) or Text.lt1(a, b)
    end
  • edit in source_text.lua at line 940
    [8.142472][8.142472:142667]()
    if loc2.screen_pos then
    return Text.previous_screen_lineA(State, loc2)
    else
    return Text.previous_screen_lineB(State, loc2)
    end
    end
    function Text.previous_screen_lineA(State, loc2)
  • edit in source_text.lua at line 944
    [8.142855]
    [8.142855]
    elseif State.lines[loc2.line-1].mode == 'drawing' then
    return {line=loc2.line-1, screen_line=1, screen_pos=1}
  • edit in source_text.lua at line 947
    [8.142862]
    [8.142862]
    local l = State.lines[loc2.line-1]
  • replacement in source_text.lua at line 949
    [8.142925][8.142925:144268]()
    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
    [8.142925]
    [8.144268]
    return {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
    [8.144279][8.144279:144721]()
    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
  • replacement in source_text.lua at line 982
    [8.145860][8.145860:146048]()
    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}
    [8.145860]
    [8.146048]
    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
    [8.146778][8.146778:146826]()
    State.line_cache[line_index].fragmentsB = nil
  • edit in source_text.lua at line 1023
    [8.146888][8.146888:146951]()
    State.line_cache[line_index].screen_line_starting_posB = nil
  • replacement in source_text.lua at line 1037
    [8.1068][8.1068:1149]()
    function starts_with(s, sub)
    return s:find(sub, 1, --[[no escapes]] true) == 1
    [8.1068]
    [8.1149]
    function starts_with(s, prefix)
    if #s < #prefix then
    return false
    end
    for i=1,#prefix do
    if s:sub(i,i) ~= prefix:sub(i,i) then
    return false
    end
    end
    return true
  • replacement in source_text.lua at line 1049
    [8.1154][8.1154:1253]()
    function ends_with(s, sub)
    return s:reverse():find(sub:reverse(), 1, --[[no escapes]] true) == 1
    [8.1154]
    [8.147120]
    function ends_with(s, suffix)
    if #s < #suffix then
    return false
    end
    for i=0,#suffix-1 do
    if s:sub(#s-i,#s-i) ~= suffix:sub(#suffix-i,#suffix-i) then
    return false
    end
    end
    return true
  • edit in source_edit.lua at line 13
    [8.16742][8.152736:152809](),[8.152736][8.152736:152809]()
    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
    [8.153050][8.16957:17080]()
    -- a line is either bifold text or a drawing
    -- a line of bifold text consists of an A side and an optional B side
    [8.153050]
    [8.17080]
    -- a line is either text or a drawing
    -- a text is a table with:
  • edit in source_edit.lua at line 33
    [8.17128][8.17128:17195]()
    -- string dataB,
    -- expanded: whether to show B side
  • replacement in source_edit.lua at line 49
    [8.18044][8.18044:18126]()
    lines = {{mode='text', data='', dataB=nil, expanded=nil}}, -- array of lines
    [8.18044]
    [8.153260]
    lines = {{mode='text', data=''}}, -- array of lines
  • edit in source_edit.lua at line 64
    [8.154198][8.154198:154279]()
    -- Positions (and screen line indexes) can be in either the A or the B side.
  • replacement in source_edit.lua at line 70
    [8.154525][8.154525:154793]()
    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
    [8.154525]
    [8.23290]
    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
  • replacement in source_edit.lua at line 94
    [8.155033][8.155033:155069]()
    left = left,
    right = right,
    [8.155033]
    [8.155069]
    left = math.floor(left),
    right = math.floor(right),
  • replacement in source_edit.lua at line 152
    [8.155593][8.155593:155733]()
    print(State.screen_top1.line, State.screen_top1.pos, State.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.cursor1.posB)
    [8.155593]
    [8.155733]
    print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
  • replacement in source_edit.lua at line 161
    [8.155947][8.18439:18494]()
    --? print('draw:', y, line_index, line, line.mode)
    [8.155947]
    [8.155991]
    --? print('draw:', y, line_index, line)
  • replacement in source_edit.lua at line 163
    [8.156055][8.156055:156119]()
    State.screen_bottom1 = {line=line_index, pos=nil, posB=nil}
    [8.156055]
    [8.18495]
    State.screen_bottom1 = {line=line_index, pos=nil}
  • replacement in source_edit.lua at line 165
    [8.18527][8.156119:156161](),[8.156119][8.156119:156161](),[8.156161][8.18528:18569]()
    --? print('text.draw', y, line_index)
    local startpos, startposB = 1, nil
    [8.18527]
    [8.18569]
    --? print('text.draw', y, line_index)
    local startpos = 1
  • replacement in source_edit.lua at line 168
    [8.18620][8.18620:18786]()
    if State.screen_top1.pos then
    startpos = State.screen_top1.pos
    else
    startpos, startposB = nil, State.screen_top1.posB
    end
    [8.18620]
    [8.18786]
    startpos = State.screen_top1.pos
  • replacement in source_edit.lua at line 186
    [8.156405][8.239:368]()
    y, State.screen_bottom1.pos, State.screen_bottom1.posB = Text.draw(State, line_index, y, startpos, startposB, hide_cursor)
    [8.156405]
    [8.19763]
    y, State.screen_bottom1.pos = Text.draw(State, line_index, y, startpos, hide_cursor)
  • edit in source_edit.lua at line 221
    [8.157110]
    [8.157110]
    -- give some time for the OS to flush everything to disk
    love.timer.sleep(0.1)
  • replacement in source_edit.lua at line 228
    [8.157214][8.20091:20112]()
    --? print('press')
    [8.157214]
    [8.157280]
    --? print('press', State.selection1.line, State.selection1.pos)
  • replacement in source_edit.lua at line 249
    [8.24234][8.20196:20331](),[8.20196][8.20196:20331](),[8.20331][8.24235:24399]()
    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)
    [8.24234]
    [8.20393]
    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
    [8.24583][8.24583:24808]()
    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)
    [8.24583]
    [8.24808]
    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
    [8.158969][8.158969:159120]()
    if State.cursor1.pos then
    State.cursor1.pos = State.cursor1.pos+1
    else
    State.cursor1.posB = State.cursor1.posB+1
    end
    [8.158969]
    [8.159120]
    State.cursor1.pos = State.cursor1.pos+1
  • replacement in source_edit.lua at line 366
    [8.159317][8.159317:159509]()
    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},
    [8.159317]
    [8.159509]
    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 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
  • replacement in source_edit.lua at line 375
    [8.160608][8.160608:160693]()
    edit.update_font_settings(State, State.font_height-2)
    Text.redraw_all(State)
    [8.160608]
    [8.160693]
    if State.font_height > 2 then
    edit.update_font_settings(State, State.font_height-2)
    Text.redraw_all(State)
    end
  • replacement in source_edit.lua at line 417
    [8.233][8.233:331]()
    State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1, posB=nil}
    [8.233]
    [8.161892]
    State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1}
  • edit in source_edit.lua at line 492
    [8.163349][8.163349:163912]()
    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
  • replacement in source.lua at line 78
    [8.167384][8.167384:167435]()
    -- environment for a mutable file of bifolded text
    [8.167384]
    [8.167435]
    -- environment for a mutable file
  • edit in source.lua at line 92
    [8.167957][8.167957:168116]()
    -- 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
    [8.168338][8.168338:168394]()
    edit.eradicate_locations_after_the_fold(Editor_state)
  • edit in source.lua at line 251
    [8.173516][8.173516:173551]()
    -- convert any bifold files here
  • edit in source.lua at line 253
    [8.173556][8.173556:173763]()
    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
    [8.192064][8.192064:192112]()
    Log_browser_state.cursor1 = {line=1, pos=nil}
    [8.192064]
    [8.192112]
    Log_browser_state.cursor1 = {line=1, pos=1}
  • replacement in log_browser.lua at line 240
    [8.200109][8.200109:200175]()
    Editor_state.cursor1 = {line=line.line_number, pos=1, posB=nil}
    [8.200109]
    [8.200175]
    Editor_state.cursor1 = {line=line.line_number, pos=1}
  • edit in log_browser.lua at line 246
    [8.200351][8.200351:200401]()
    -- expand B side
    Editor_state.expanded = true
  • resurrect zombie in edit.lua at line 82
    [8.371][8.371:472](),[8.371][8.371:472]()
    or not Text.le1(State.screen_top1, State.cursor1) then
    State.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]()
    end
    end
    function edit.invalid1(State, loc1)
    return loc1.line > #State.lines
    or loc1.pos > #State.lines[loc1.line].data
    end