Merge text.love

[?]
Mar 18, 2023, 5:46 AM
3G723RV5YPWQQQ52OMKDQAQ2OXV6NGLFXLPNB7YXH5WZIYC7PQXQC

Dependencies

  • [2] STYH5IVG Merge text.love
  • [3] R56CKHYZ Merge text.love
  • [4] 2TQUKHBC Merge lines.love
  • [5] Q434NJPR Merge lines.love
  • [6] BLWAYPKV extract a module
  • [7] TVCPXAAU rename
  • [8] LXTTOB33 extract a couple of files
  • [9] 6LJZN727 handle chords
  • [10] ECBDENZ4 Merge text.love
  • [11] AT3LVCMP Merge text.love
  • [12] KKMFQDR4 editing source code from within the app
  • [13] RSZD5A7G forgot to add json.lua
  • [14] 25V2GA6J taking stock
  • [15] AKZWDWIA Merge lines.love
  • [16] RXNR3U5E Merge text.love
  • [17] ORKN6EOB Merge lines.love
  • [18] C3GUE45I Merge text.love
  • [19] EX43CDDI Merge text.love
  • [20] R5QXEHUI somebody stop me
  • [21] D4B52CQ2 Merge lines.love
  • [22] VP5KC4XZ Merge lines.love
  • [23] VYAFKS7R Merge lines.love
  • [24] 73OCE2MC after much struggle, a brute-force undo
  • [25] ATQO62TF Merge lines.love
  • [26] 3QNOKBFM beginnings of a test harness
  • [27] A4BSGS2C Merge lines.love
  • [28] JOPVPUSA editing source code from within the app
  • [29] 3PSFWAIL Merge lines.love
  • [30] 3TI67SEJ more bugfix
  • [31] KOTI3MFG bugfix in previous commit
  • [32] BJ5X5O4A let's prevent the text cursor from ever getting on a drawing
  • [33] TPGGOVD4 Merge text.love
  • [34] 4SR3Z4Y3 document the version of LÖVE I've been using
  • [35] ZTZOO2OQ Merge lines.love
  • [36] 2L5MEZV3 experiment: new edit namespace
  • [37] 242L3OQX bugfix: ensure Cursor_line is always on a text line
  • [38] UAYCSFSK Merge text.love
  • [39] LF7BWEG4 group all editor globals
  • [40] K74U4BAU Merge lines.love
  • [41] 4YDBYBA4 clean up memory leak experiments
  • [42] TRNWIQN6 more precise height calculation when scrolling up as much as possible while keeping cursor on screen
  • [43] KMSL74GA support selections in the source editor
  • [44] T4FRZSYL delete an ancient, unused file
  • [45] MXA3RZYK deduce left/right from state where possible
  • [46] 2CTN2IEF Merge lines.love
  • [47] 3OKKTUT4 up and down arrow now moving by screen line where possible
  • [48] OGUV4HSA remove some memory leaks from rendered fragments
  • [49] CE4LZV4T drop last couple of manual tests
  • [50] OI4FPFIN support drawings in the source editor
  • [51] P3K7UH5C Merge lines.love
  • [52] XS3PZI7G Merge lines.love
  • [53] 66X36NZN a little more prose describing manual_tests
  • [54] GUOQRUL7 Merge lines.love
  • [55] WKKABOJ6 one issue less
  • [56] AYX33NBC Merge lines.love
  • [57] VHQCNMAR several more modules
  • [58] VBU5YHLR Merge lines.love
  • [59] OTIBCAUJ love2d scaffold
  • [60] TLOAPLBJ add a license
  • [61] XX7G2FFJ intermingle freehand line drawings with text
  • [62] VXORMHME delete experimental REPL
  • [63] FS2ITYYH record a known issue
  • [64] K2X6G75Z start writing some tests for drawings
  • [65] 5OALPNN3 add args to some functions
  • [66] MD3W5IRA new fork: rip out drawing support
  • [67] JCXL74WV bring back everything from commit a68647ae22
  • [68] SCOXD4EO Merge lines.love
  • [69] U3MJNFUY Merge lines.love
  • [70] D4FEFHQC flesh out Readme
  • [71] ETXNVRPT Merge lines.love
  • [72] AVTNUQYR basic test-enabled framework
  • [73] BULPIBEG beginnings of a module for the text editor
  • [74] VHUNJHXB Merge lines.love
  • [75] 36Z442IV back to commit 8123959e52f without code editing
  • [76] QCPXQ2E3 add state arg to a few functions
  • [77] D2GCFTTT clean up repl functionality

Change contents

  • file deletion: source_text_tests.lua (----------)source_text_tests.lua (----------)
    [6.2][6.83739:83784](),[6.2][6.83739:83784](),[6.83784][6.3561:3561]()
    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 (----------)
    [6.2][6.147125:147164](),[6.2][6.147125:147164](),[6.147164][6.83786:83786]()
    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
    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
    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)
    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
    assert(State.lines[State.cursor1.line].mode == 'text')
    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)
    end
    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
    State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
    Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
    --? print('top now', State.screen_top1.line)
    Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
    --? print('pagedown end')
    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
    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
    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
    if State.cursor1.pos > 1 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
    -- 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
  • file deletion: source_edit.lua (----------)source_edit.lua (----------)
    [6.2][6.165788:165827](),[6.2][6.165788:165827](),[6.165827][6.152503:152503]()
    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)})
    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)
    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)
    --? 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
    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]
    left = math.floor(left),
    right = math.floor(right),
    width = right-left,
    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
    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,
  • file deletion: source.lua (----------)source.lua (----------)
    [6.2][6.177715:177749](),[6.2][6.177715:177749](),[6.177749][6.165829:165829]()
    -- 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 (----------)
    [6.2][6.202684:202723](),[6.2][6.202684:202723](),[6.202723][6.191243:191243]()
    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.lua at line 622
    [6.580][6.651:834]()
    --? print('snap', State.screen_top1.line, State.screen_top1.pos, State.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
  • resurrect zombie in README.md at line 62
    [6.107][6.35:36](),[6.107][6.35:36]()