resolve conflicts

akkartik
Nov 18, 2023, 8:51 PM
VLTU33KW2BVZFSNENNEGWMFZNWSWSTOXS6NHG7NWW43N3NDCNLTAC

Dependencies

  • [2] RO35V4H4 Merge text.love
  • [3] NUJMQXN6 Merge text.love
  • [4] CAG7PP5Y Merge text.love
  • [5] KKQKPGCI resolve conflicts
  • [6] 3QNOKBFM beginnings of a test harness
  • [7] 73OCE2MC after much struggle, a brute-force undo
  • [8] XNFTJHC4 split keyboard handling between Text and Drawing
  • [9] FS2ITYYH record a known issue
  • [10] 2TQUKHBC Merge lines.love
  • [11] 3PSFWAIL Merge lines.love
  • [12] VXORMHME delete experimental REPL
  • [13] K2X6G75Z start writing some tests for drawings
  • [14] EIPRCZQC Merge text.love
  • [15] REAIVN7W Merge lines.love
  • [16] SGMA5JLE save the list of tests in repo
  • [17] ZLJYLPOT Merge lines.love
  • [18] AYX33NBC Merge lines.love
  • [19] 34BZ5ZKN Merge lines.love
  • [20] 4CTZOJPC stop pretending globals are local
  • [21] G3DLS5OU audit all asserts
  • [22] M5JXTW56 Merge text.love
  • [23] RU4HIK43 Merge lines.love
  • [24] ED4Z6ORC cleaner API for file-system access
  • [25] N2NUGNN4 include a brief reference enabling many useful apps
  • [26] QXVD2RIF add state arg to Drawing.mouse_released
  • [27] JIK7ZRYI bugfix: imprecision in drawing
  • [28] C3GUE45I Merge text.love
  • [29] TVCPXAAU rename
  • [30] KMSL74GA support selections in the source editor
  • [31] A4STVUZI fix support for absolute paths in Windows
  • [32] XX7G2FFJ intermingle freehand line drawings with text
  • [33] R5QXEHUI somebody stop me
  • [34] C3NYQP57 Merge lines.love
  • [35] AVTNUQYR basic test-enabled framework
  • [36] RIAFJLMZ Merge text.love
  • [37] KWIVKQQ7 Merge lines.love
  • [38] RSZD5A7G forgot to add json.lua
  • [39] KKMFQDR4 editing source code from within the app
  • [40] VXRYVZ74 Merge text.love
  • [41] VP5KC4XZ Merge lines.love
  • [42] 66X36NZN a little more prose describing manual_tests
  • [43] 6VJTQKW7 start supporting LÖVE v12
  • [44] 4YDBYBA4 clean up memory leak experiments
  • [45] BULPIBEG beginnings of a module for the text editor
  • [46] 6LJZN727 handle chords
  • [47] CE4LZV4T drop last couple of manual tests
  • [48] OTIBCAUJ love2d scaffold
  • [49] BLWAYPKV extract a module
  • [50] ORKN6EOB Merge lines.love
  • [51] HYEAFRZ2 split mouse_pressed events between Text and Drawing
  • [52] 3G723RV5 Merge text.love
  • [53] PJ5PQAQE record support for multiple versions
  • [54] 2344TV56 Merge lines.love
  • [55] MGJZHZC4 Merge lines.love
  • [56] YF2ATH2Q Merge lines.love
  • [57] ATQO62TF Merge lines.love
  • [58] LWPFEZBI Merge lines.love
  • [59] VHUNJHXB Merge lines.love
  • [60] LYN3L74W correct commit f3abc2cbf2
  • [61] NFI42KGX more correct absolute path detection
  • [62] LXTTOB33 extract a couple of files
  • [63] 4SR3Z4Y3 document the version of LÖVE I've been using
  • [64] T4FRZSYL delete an ancient, unused file
  • [65] KYNGDE2C consistent names in a few more places
  • [66] QD4LOFQR Merge text.love
  • [67] JDZVBFEI Merge lines.love
  • [68] D2GCFTTT clean up repl functionality
  • [69] TLOAPLBJ add a license
  • [70] JOPVPUSA editing source code from within the app
  • [71] 5SM6DRHK port inscript's bugfix to source editor
  • [72] D4B52CQ2 Merge lines.love
  • [73] MD3W5IRA new fork: rip out drawing support
  • [74] ONHKBLLC Merge lines.love
  • [75] WPW3AVFS more precise shape selection
  • [76] 2CK5QI7W make love event names consistent
  • [77] IGBTDA6Y Merge text.love
  • [78] 4HR3G5ZD Merge text.love
  • [79] X43ZIKR3 Merge text.love
  • [80] 2L5MEZV3 experiment: new edit namespace
  • [81] 2CFLXLIE Merge text.love
  • [82] Q6RXCILQ Merge text.love
  • [83] VBU5YHLR Merge lines.love
  • [84] FM5LDKGT Merge text.love
  • [85] D2TYFYG2 Merge text.love
  • [86] BJ5X5O4A let's prevent the text cursor from ever getting on a drawing
  • [87] OGUV4HSA remove some memory leaks from rendered fragments
  • [88] MXSAHZN4 Merge lines.love
  • [89] TBTRYEBP Merge lines.love
  • [90] VHQCNMAR several more modules
  • [91] ISOFHXB2 App.width can no longer take a Text
  • [92] 36Z442IV back to commit 8123959e52f without code editing
  • [93] 2CTN2IEF Merge lines.love
  • [94] DRFE3B3Z mouse buttons are integers, not strings
  • [95] 3OTESDW6 move drawing.starty into line cache
  • [*] JCXL74WV bring back everything from commit a68647ae22

Change contents

  • file deletion: geom.lua (----------)geom.lua (----------)
    [6.2][6.15049:15081](),[6.15081][6.11579:11579](),[6.2][6.15049:15081]()
    geom = {}
    function geom.on_shape(x,y, drawing, shape)
    if shape.mode == 'freehand' then
    return geom.on_freehand(x,y, drawing, shape)
    elseif shape.mode == 'line' then
    return geom.on_line(x,y, drawing, shape)
    elseif shape.mode == 'manhattan' then
    local p1 = drawing.points[shape.p1]
    local p2 = drawing.points[shape.p2]
    if p1.x == p2.x then
    if x ~= p1.x then return false end
    local y1,y2 = p1.y, p2.y
    if y1 > y2 then
    y1,y2 = y2,y1
    end
    return y >= y1-2 and y <= y2+2
    elseif p1.y == p2.y then
    if y ~= p1.y then return false end
    local x1,x2 = p1.x, p2.x
    if x1 > x2 then
    x1,x2 = x2,x1
    end
    return x >= x1-2 and x <= x2+2
    end
    elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
    return geom.on_polygon(x,y, drawing, shape)
    elseif shape.mode == 'circle' then
    local center = drawing.points[shape.center]
    local dist = geom.dist(center.x,center.y, x,y)
    return dist > shape.radius*0.95 and dist < shape.radius*1.05
    elseif shape.mode == 'arc' then
    local center = drawing.points[shape.center]
    local dist = geom.dist(center.x,center.y, x,y)
    if dist < shape.radius*0.95 or dist > shape.radius*1.05 then
    return false
    end
    return geom.angle_between(center.x,center.y, x,y, shape.start_angle,shape.end_angle)
    elseif shape.mode == 'deleted' then
    else
    assert(false, ('unknown drawing mode %s'):format(shape.mode))
    end
    end
    function geom.on_freehand(x,y, drawing, shape)
    local prev
    for _,p in ipairs(shape.points) do
    if prev then
    if geom.on_line(x,y, drawing, {p1=prev, p2=p}) then
    return true
    end
    end
    prev = p
    end
    return false
    end
    function geom.on_line(x,y, drawing, shape)
    local p1,p2
    if type(shape.p1) == 'number' then
    p1 = drawing.points[shape.p1]
    p2 = drawing.points[shape.p2]
    else
    p1 = shape.p1
    p2 = shape.p2
    end
    if p1.x == p2.x then
    if math.abs(p1.x-x) > 2 then
    return false
    end
    local y1,y2 = p1.y,p2.y
    if y1 > y2 then
    y1,y2 = y2,y1
    end
    return y >= y1-2 and y <= y2+2
    end
    -- has the right slope and intercept
    local m = (p2.y - p1.y) / (p2.x - p1.x)
    local yp = p1.y + m*(x-p1.x)
    if yp < y-2 or yp > y+2 then
    return false
    end
    -- between endpoints
    local k = (x-p1.x) / (p2.x-p1.x)
    return k > -0.005 and k < 1.005
    end
    function geom.on_polygon(x,y, drawing, shape)
    local prev
    for _,p in ipairs(shape.vertices) do
    if prev then
    if geom.on_line(x,y, drawing, {p1=prev, p2=p}) then
    return true
    end
    end
    prev = p
    end
    return geom.on_line(x,y, drawing, {p1=shape.vertices[1], p2=shape.vertices[#shape.vertices]})
    end
    -- are (x3,y3) and (x4,y4) on the same side of the line between (x1,y1) and (x2,y2)
    function geom.same_side(x1,y1, x2,y2, x3,y3, x4,y4)
    if x1 == x2 then
    return math.sign(x3-x1) == math.sign(x4-x1)
    end
    if y1 == y2 then
    return math.sign(y3-y1) == math.sign(y4-y1)
    end
    local m = (y2-y1)/(x2-x1)
    return math.sign(m*(x3-x1) + y1-y3) == math.sign(m*(x4-x1) + y1-y4)
    end
    function math.sign(x)
    if x > 0 then
    return 1
    elseif x == 0 then
    return 0
    elseif x < 0 then
    return -1
    end
    end
    function geom.angle_with_hint(x1, y1, x2, y2, hint)
    local result = geom.angle(x1,y1, x2,y2)
    if hint then
    -- Smooth the discontinuity where angle goes from positive to negative.
    -- The hint is a memory of which way we drew it last time.
    while result > hint+math.pi/10 do
    result = result-math.pi*2
    end
    while result < hint-math.pi/10 do
    result = result+math.pi*2
    end
    end
    return result
    end
    -- result is from -π/2 to 3π/2, approximately adding math.atan2 from Lua 5.3
    -- (LÖVE is Lua 5.1)
    function geom.angle(x1,y1, x2,y2)
    local result = math.atan((y2-y1)/(x2-x1))
    if x2 < x1 then
    result = result+math.pi
    end
    return result
    end
    -- is the line between x,y and cx,cy at an angle between s and e?
    function geom.angle_between(ox,oy, x,y, s,e)
    local angle = geom.angle(ox,oy, x,y)
    if s > e then
    s,e = e,s
    end
    -- I'm not sure this is right or ideal..
    angle = angle-math.pi*2
    if s <= angle and angle <= e then
    return true
    end
    angle = angle+math.pi*2
    if s <= angle and angle <= e then
    return true
    end
    angle = angle+math.pi*2
    return s <= angle and angle <= e
    end
    function geom.dist(x1,y1, x2,y2) return ((x2-x1)^2+(y2-y1)^2)^0.5 end
    end
    end
    function geom.on_freehand(x,y, drawing, shape)
    local prev
    for _,p in ipairs(shape.points) do
    if prev then
    if geom.on_line(x,y, drawing, {p1=prev, p2=p}) then
    return true
    end
    end
    prev = p
    end
    return false
    end
    function geom.on_line(x,y, drawing, shape)
    local p1,p2
    if type(shape.p1) == 'number' then
    p1 = drawing.points[shape.p1]
    p2 = drawing.points[shape.p2]
    else
    p1 = shape.p1
    p2 = shape.p2
    end
    if p1.x == p2.x then
  • file deletion: drawing.lua (----------)drawing.lua (----------)
    [6.2][6.1542:1577](),[6.2][6.1542:1577](),[6.1577][6.98:98]()
    assert(false, ('unknown drawing mode %s'):format(shape.mode))
    assert(false, ('unknown drawing mode %s'):format(shape.mode))
    assert(false, ('unknown drawing mode %s'):format(State.current_drawing_mode))
    assert(drawing.mode == 'drawing', 'Drawing.update: line is not a drawing')
    assert(#drawing.pending.vertices <= 2, 'Drawing.mouse_release: rectangle has too many pending vertices')
    assert(#drawing.pending.vertices <= 2, 'Drawing.mouse_release: square has too many pending vertices')
    assert(false, ('unknown drawing mode %s'):format(drawing.pending.mode))
    assert(idx, 'point to delete is not in vertices')
    assert(false, ('unknown drawing mode %s'):format(shape.mode))
    assert(shape.mode == 'freehand', 'can only smoothen freehand shapes')
    for _=1,7 do
    for i=2,#shape.points-1 do
    local a = shape.points[i-1]
    local b = shape.points[i]
    local c = shape.points[i+1]
    b.x = round((a.x + b.x + c.x)/3)
    b.y = round((a.y + b.y + c.y)/3)
    end
    end
    end
    function round(num)
    return math.floor(num+.5)
    end
    function Drawing.find_or_insert_point(points, x,y, width)
    -- check if UI would snap the two points together
    for i,point in ipairs(points) do
    if Drawing.near(point, x,y, width) then
    return i
    end
    end
    table.insert(points, {x=x, y=y})
    return #points
    end
    function Drawing.near(point, x,y, width)
    local px,py = Drawing.pixels(x, width),Drawing.pixels(y, width)
    local cx,cy = Drawing.pixels(point.x, width), Drawing.pixels(point.y, width)
    return (cx-px)*(cx-px) + (cy-py)*(cy-py) < Same_point_distance*Same_point_distance
    end
    function Drawing.pixels(n, width) -- parts to pixels
    return math.floor(n*width/256)
    end
    function Drawing.coord(n, width) -- pixels to parts
    return math.floor(n*256/width)
    end
    function table.find(h, x)
    for k,v in pairs(h) do
    if v == x then
    return k
    end
    end
    end
    end
    end
    function Drawing.smoothen(shape)
    table.remove(shape.vertices, idx)
    if #shape.vertices < 3 then
    shape.mode = 'deleted'
    end
    else
    shape.mode = 'deleted'
    end
    end
    end
    drawing.points[i].deleted = true
    end
    local drawing,_,_,shape = Drawing.select_shape_at_mouse(State)
    if drawing then
    shape.mode = 'deleted'
    end
    elseif chord == 'C-h' and not App.mouse_down(1) then
    local drawing = Drawing.select_drawing_at_mouse(State)
    if drawing then
    drawing.show_help = true
    end
    elseif chord == 'escape' and App.mouse_down(1) then
    local _,drawing = Drawing.current_drawing(State)
    drawing.pending = {}
    end
    end
    function Drawing.complete_rectangle(firstx,firsty, secondx,secondy, x,y)
    if firstx == secondx then
    return x,secondy, x,firsty
    end
    if firsty == secondy then
    return secondx,y, firstx,y
    end
    local first_slope = (secondy-firsty)/(secondx-firstx)
    -- slope of second edge:
    -- -1/first_slope
    -- equation of line containing the second edge:
    -- y-secondy = -1/first_slope*(x-secondx)
    -- => 1/first_slope*x + y + (- secondy - secondx/first_slope) = 0
    -- now we want to find the point on this line that's closest to the mouse pointer.
    -- https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_an_equation
    local a = 1/first_slope
    local c = -secondy - secondx/first_slope
    local thirdx = round(((x-a*y) - a*c) / (a*a + 1))
    local thirdy = round((a*(-x + a*y) - c) / (a*a + 1))
    -- slope of third edge = first_slope
    -- equation of line containing third edge:
    -- y - thirdy = first_slope*(x-thirdx)
    -- => -first_slope*x + y + (-thirdy + thirdx*first_slope) = 0
    -- now we want to find the point on this line that's closest to the first point
    local a = -first_slope
    local c = -thirdy + thirdx*first_slope
    local fourthx = round(((firstx-a*firsty) - a*c) / (a*a + 1))
    local fourthy = round((a*(-firstx + a*firsty) - c) / (a*a + 1))
    return thirdx,thirdy, fourthx,fourthy
    end
    function Drawing.complete_square(firstx,firsty, secondx,secondy, x,y)
    -- use x,y only to decide which side of the first edge to complete the square on
    local deltax = secondx-firstx
    local deltay = secondy-firsty
    local thirdx = secondx+deltay
    local thirdy = secondy-deltax
    if not geom.same_side(firstx,firsty, secondx,secondy, thirdx,thirdy, x,y) then
    deltax = -deltax
    deltay = -deltay
    thirdx = secondx+deltay
    thirdy = secondy-deltax
    end
    local fourthx = firstx+deltay
    local fourthy = firsty-deltax
    return thirdx,thirdy, fourthx,fourthy
    end
    function Drawing.current_drawing(State)
    local x, y = App.mouse_x(), App.mouse_y()
    for drawing_index,drawing in ipairs(State.lines) do
    if drawing.mode == 'drawing' then
    local line_cache = State.line_cache[drawing_index]
    if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
    return drawing_index,drawing,line_cache
    end
    end
    end
    return nil
    end
    function Drawing.select_shape_at_mouse(State)
    for drawing_index,drawing in ipairs(State.lines) do
    if drawing.mode == 'drawing' then
    local x, y = App.mouse_x(), App.mouse_y()
    local line_cache = State.line_cache[drawing_index]
    if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
    local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
    for i,shape in ipairs(drawing.shapes) do
    function Drawing.keychord_press(State, chord)
    end
    State.lines.current_drawing.pending = {}
    State.lines.current_drawing = nil
    end
    end
    end
    if #drawing.pending.vertices == 2 then
    local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
    if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
    local first = drawing.points[drawing.pending.vertices[1]]
    local second = drawing.points[drawing.pending.vertices[2]]
    local thirdx,thirdy, fourthx,fourthy = Drawing.complete_square(first.x,first.y, second.x,second.y, mx,my)
    table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, thirdx,thirdy, State.width))
    table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, fourthx,fourthy, State.width))
    table.insert(drawing.shapes, drawing.pending)
    end
    end
    elseif drawing.pending.mode == 'circle' then
    local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
    if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
    local center = drawing.points[drawing.pending.center]
    drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))
    table.insert(drawing.shapes, drawing.pending)
    end
    elseif drawing.pending.mode == 'arc' then
    local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
    if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
    local center = drawing.points[drawing.pending.center]
    drawing.pending.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, drawing.pending.end_angle)
    table.insert(drawing.shapes, drawing.pending)
    end
    elseif drawing.pending.mode == 'name' then
    -- drop it
    else
    if #drawing.pending.vertices == 2 then
    local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)
    if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
    local first = drawing.points[drawing.pending.vertices[1]]
    local second = drawing.points[drawing.pending.vertices[2]]
    local thirdx,thirdy, fourthx,fourthy = Drawing.complete_rectangle(first.x,first.y, second.x,second.y, mx,my)
    table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, thirdx,thirdy, State.width))
    table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, fourthx,fourthy, State.width))
    table.insert(drawing.shapes, drawing.pending)
    end
    else
    -- too few points; draw nothing
    end
    elseif drawing.pending.mode == 'square' then
    function Drawing.mouse_release(State, x,y, mouse_button)
    local pmx, pmy = App.mouse_x(), App.mouse_y()
    local mx = Drawing.coord(pmx-State.left, State.width)
    local my = Drawing.coord(pmy-line_cache.starty, State.width)
    if App.mouse_down(1) then
    if Drawing.in_drawing(drawing, line_cache, pmx,pmy, State.left,State.right) then
    if drawing.pending.mode == 'freehand' then
    table.insert(drawing.pending.points, {x=mx, y=my})
    elseif drawing.pending.mode == 'move' then
    drawing.pending.target_point.x = mx
    drawing.pending.target_point.y = my
    Drawing.relax_constraints(drawing, drawing.pending.target_point_index)
    end
    end
    elseif State.current_drawing_mode == 'move' then
    if Drawing.in_drawing(drawing, line_cache, pmx, pmy, State.left,State.right) then
    drawing.pending.target_point.x = mx
    drawing.pending.target_point.y = my
    Drawing.relax_constraints(drawing, drawing.pending.target_point_index)
    end
    else
    -- do nothing
    end
    end
    function Drawing.relax_constraints(drawing, p)
    for _,shape in ipairs(drawing.shapes) do
    if shape.mode == 'manhattan' then
    if shape.p1 == p then
    shape.mode = 'line'
    elseif shape.p2 == p then
    shape.mode = 'line'
    end
    elseif shape.mode == 'rectangle' or shape.mode == 'square' then
    for _,v in ipairs(shape.vertices) do
    if v == p then
    shape.mode = 'polygon'
    end
    end
    end
    end
    end
    end
    end
    -- a couple of operations on drawings need to constantly check the state of the mouse
    function Drawing.update(State)
    if State.lines.current_drawing == nil then return end
    local drawing = State.lines.current_drawing
    local line_cache = State.line_cache[State.lines.current_drawing_index]
    function Drawing.mouse_press(State, drawing_index, x,y, mouse_button)
    end
    end
    function Drawing.in_drawing(drawing, line_cache, x,y, left,right)
    if line_cache.starty == nil then return false end -- outside current page
    local width = right-left
    return y >= line_cache.starty and y < line_cache.starty + Drawing.pixels(drawing.h, width) and x >= left and x < right
    end
    -- after mouse_release
    end
    end
    function Drawing.draw_pending_shape(drawing, top, left,right)
    local width = right-left
    local pmx,pmy = App.mouse_x(), App.mouse_y()
    local function px(x) return Drawing.pixels(x, width)+left end
    local function py(y) return Drawing.pixels(y, width)+top end
    local mx = Drawing.coord(pmx-left, width)
    local my = Drawing.coord(pmy-top, width)
    -- recreate pixels from coords to precisely mimic how the drawing will look
  • file deletion: source_undo.lua (----------)source_undo.lua (----------)
    [6.2][6.3520:3559](),[6.2][6.3520:3559](),[6.3559][6.68:68]()
    assert(s, 'failed to snapshot operation for undo history')
    assert(#State.lines > 0, 'failed to snapshot operation for undo history')
    assert(false, ('unknown line mode %s'):format(line.mode))
    assert(from.start_line == to.start_line, 'failed to patch undo operation')
    assert(#to.lines == to.end_line-to.start_line+1, 'failed to patch undo operation')
    assert(from.start_line == to.start_line, 'failed to patch undo operation')
    assert(#to.lines == to.end_line-to.start_line+1, 'failed to patch undo operation')
    for i=1,#to.lines do
    table.insert(line_cache, to.start_line+i-1, {})
    end
    end
    -- https://stackoverflow.com/questions/640642/how-do-you-copy-a-lua-table-by-value/26367080#26367080
    function deepcopy(obj, seen)
    if type(obj) ~= 'table' then return obj end
    if seen and seen[obj] then return seen[obj] end
    local s = seen or {}
    local result = setmetatable({}, getmetatable(obj))
    s[obj] = result
    for k,v in pairs(obj) do
    result[deepcopy(k, s)] = deepcopy(v, s)
    end
    return result
    end
    function minmax(a, b)
    return math.min(a,b), math.max(a,b)
    end
    for i=from.end_line,from.start_line,-1 do
    table.remove(line_cache, i)
    end
    for i=1,#to.lines do
    table.insert(lines, to.start_line+i-1, to.lines[i])
    end
    end
    function patch_placeholders(line_cache, from, to)
    for i=from.end_line,from.start_line,-1 do
    table.remove(lines, i)
    end
    end
    if s < 1 then s = 1 end
    if s > #State.lines then s = #State.lines end
    if e < 1 then e = 1 end
    if e > #State.lines then e = #State.lines end
    -- compare with App.initialize_globals
    local event = {
    screen_top=deepcopy(State.screen_top1),
    selection=deepcopy(State.selection1),
    cursor=deepcopy(State.cursor1),
    if e == nil then
    e = s
    end
  • file deletion: source_select.lua (----------)source_select.lua (----------)
    [6.2][6.30050:30091](),[6.2][6.30050:30091](),[6.30091][6.23288:23288]()
    assert(maxl == line_index, ('maxl %d not equal to line_index %d'):format(maxl, line_index))
    assert(minl == line_index, ('minl %d not equal to line_index %d'):format(minl, line_index))
    assert(minl == maxl and minl == line_index, ('minl %d, maxl %d and line_index %d are not all equal'):format(minl, maxl, line_index))
    assert(minl < maxl, ('minl %d not < maxl %d'):format(minl, maxl))
    assert(minl < maxl, ('minl %d not < maxl %d'):format(minl, maxl))
    local result = {State.lines[minl].data:sub(min_offset)}
    for i=minl+1,maxl-1 do
    if State.lines[i].mode == 'text' then
    table.insert(result, State.lines[i].data)
    end
    end
    table.insert(result, State.lines[maxl].data:sub(1, max_offset-1))
    return table.concat(result, '\n')
    end
    local rhs = State.lines[maxl].data:sub(max_offset)
    for i=maxl,minl+1,-1 do
    table.remove(State.lines, i)
    table.remove(State.line_cache, i)
    end
    State.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..rhs
    end
    function Text.selection(State)
    if State.selection1.line == nil then return end
    -- min,max = sorted(State.selection1,State.cursor1)
    local minl,minp = State.selection1.line,State.selection1.pos
    local maxl,maxp = State.cursor1.line,State.cursor1.pos
    if minl > maxl then
    minl,maxl = maxl,minl
    minp,maxp = maxp,minp
    elseif minl == maxl then
    if minp > maxp then
    minp,maxp = maxp,minp
    end
    end
    local min_offset = Text.offset(State.lines[minl].data, minp)
    local max_offset = Text.offset(State.lines[maxl].data, maxp)
    if minl == maxl then
    return State.lines[minl].data:sub(min_offset, max_offset-1)
    end
    return minp,maxp
    end
    end
    -- draw highlight for line corresponding to (lo,hi) given an approximate x,y and pos on the same screen line
    -- Creates text objects every time, so use this sparingly.
    -- Returns some intermediate computation useful elsewhere.
    function Text.draw_highlight(State, line, x,y, pos, lo,hi)
    if lo then
    local lo_offset = Text.offset(line.data, lo)
    local hi_offset = Text.offset(line.data, hi)
    local pos_offset = Text.offset(line.data, pos)
    local lo_px
    if pos == lo then
    lo_px = 0
    else
    local before = line.data:sub(pos_offset, lo_offset-1)
    return minp,bpos
    else
    return apos,maxp
    elseif b_lt then
  • file deletion: source_text.lua (----------)source_text.lua (----------)
    [6.2][6.147125:147164](),[6.2][6.147125:147164](),[6.147164][6.83786:83786]()
    assert(#line_cache.screen_line_starting_pos >= 1, 'line cache missing screen line info')
    assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
    assert(Text.le1(State.screen_top1, State.cursor1), ('screen_top (line=%d,pos=%d) is below cursor (line=%d,pos=%d)'):format(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos))
    assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
    assert(screen_line_index > 1, 'bumped up against top screen line in line')
    assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
    assert(State.cursor1.pos, 'cursor has no pos')
    assert(State.cursor1.pos > 1, 'bumped up against start of line')
    assert(end_offset > start_offset, ('end_offset %d not > start_offset %d'):format(end_offset, start_offset))
    assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
    assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
    assert(false, ('invalid pos %d'):format(loc1.pos))
    assert(false, ('invalid pos %d'):format(loc1.pos))
    assert(State.cursor1.line == #State.lines+1, 'tried to ensure bottom line of file is text, but failed')
    assert(top2.line > 1, 'tried to snap cursor to buttom of screen but failed')
    assert(State.lines[top2.line-1].mode == 'drawing', "expected a drawing but it's not")
    assert(my >= line_cache.starty, 'failed to map y pixel to line')
    assert(false, 'failed to map y pixel to line')
    assert(false, 'failed to map x pixel to pos')
    assert(false, 'failed to map x pixel to pos')
    assert(result.screen_pos, 'failed to convert schema-1 coordinate to schema-2')
    assert(result, "Text.offset returned nil; this is likely a failure to handle utf8")
    return result
    end
    function Text.previous_screen_line(State, loc2)
    return result
    end
    function Text.to1(State, loc2)
    end
    function Text.x_after(s, pos)
    local offset = Text.offset(s, math.min(pos+1, #s+1))
    local s_before = s:sub(1, offset-1)
    --? print('^'..s_before..'$')
    end
    -- return the nearest index of line (in utf8 code points) which lies entirely
    -- within x pixels of the left margin
    -- result: 0 to len+1
    function Text.nearest_pos_less_than(line, x)
    --? print('', '-- nearest_pos_less_than', line, x)
    local len = utf8.len(line)
    local max_x = Text.x_after(line, len)
    if x > max_x then
    return len+1
    end
    local left, right = 0, len+1
    while true do
    local curr = math.floor((left+right)/2)
    local currxmin = Text.x_after(line, curr+1)
    local currxmax = Text.x_after(line, curr+2)
    --? print('', x, left, right, curr, currxmin, currxmax)
    if currxmin <= x and x < currxmax then
    return curr
    end
    if left >= right-1 then
    return left
    end
    if currxmin > x then
    right = curr
    else
    left = curr
    end
    end
    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
    -- duplicate some logic from Text.draw
    local y = line_cache.starty
    -- We currently can't draw partial drawings, so either skip it entirely
    -- or not at all.
    local h = Drawing_padding_height + Drawing.pixels(State.lines[top2.line-1].h, State.width)
    if y - h < State.top then
    break
    end
    --? print('skipping drawing of height', h)
    y = y - h
    table.insert(State.lines, {mode='text', data=''})
    table.insert(State.line_cache, {})
    end
    --? print(y, App.screen.height, App.screen.height-State.line_height)
    if y > App.screen.height - State.line_height then
    end
    end
    if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
    State.cursor1.pos = State.cursor1.pos+1
    if State.cursor1.pos > 1 then
    State.cursor1.pos = State.cursor1.pos-1
    local curr = s:sub(start_offset, end_offset-1)
    return curr:match(pat)
    end
    function Text.left(State)
    if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos-1, '%s') then
    if Text.cursor_at_final_screen_line(State) then
    -- line is done, skip to next text line
    --? print('cursor at final screen line of its line')
    --? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
    local new_screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos[screen_line_index-1]
    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 '..tostring(State.cursor1.pos))
    end
    if Text.lt1(State.cursor1, State.screen_top1) then
    --? print('up', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
    local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)
    if screen_line_starting_pos == 1 then
    --? print('cursor is at first screen line of its line')
    -- line is done; skip to previous text line
    schedule_save(State)
    record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
    elseif chord == 'delete' 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
    for i=1,#line_cache.screen_line_starting_pos do
    local pos = line_cache.screen_line_starting_pos[i]
  • file deletion: source_file.lua (----------)source_file.lua (----------)
    [6.2][6.152462:152501](),[6.2][6.152462:152501](),[6.152501][6.150304:150304]()
    assert(line, 'drawing in file is incomplete')
    assert(false, ('unknown drawing mode %s'):format(shape.mode))
    assert(false, ('unknown drawing mode %s'):format(shape.mode))
    assert(i, 'drawing in array is incomplete')
    assert(false, ('unknown drawing mode %s'):format(shape.mode))
    end
    table.insert(drawing.shapes, shape)
    end
    return i, drawing
    end
    --? print(i)
    if line == '```' then break end
    local shape = json.decode(line)
    if shape.mode == 'freehand' then
    -- no changes needed
    elseif shape.mode == 'line' or shape.mode == 'manhattan' then
    local name = shape.p1.name
    shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600)
    drawing.points[shape.p1].name = name
    name = shape.p2.name
    shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600)
    drawing.points[shape.p2].name = name
    elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
    for i,p in ipairs(shape.vertices) do
    local name = p.name
    shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600)
    drawing.points[shape.vertices[i]].name = name
    end
    elseif shape.mode == 'circle' or shape.mode == 'arc' then
    local name = shape.center.name
    shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600)
    drawing.points[shape.center].name = name
    elseif shape.mode == 'deleted' then
    -- ignore
    else
    end
    end
    outfile:write('```\n')
    end
    table.insert(drawing.shapes, shape)
    end
    return drawing
    end
    function store_drawing(outfile, drawing)
    outfile:write('```lines\n')
    for _,shape in ipairs(drawing.shapes) do
    if shape.mode == 'freehand' then
    if line == '```' then break end
    local shape = json.decode(line)
    if shape.mode == 'freehand' then
    -- no changes needed
    elseif shape.mode == 'line' or shape.mode == 'manhattan' then
    local name = shape.p1.name
    shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600)
    drawing.points[shape.p1].name = name
    name = shape.p2.name
    shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600)
    drawing.points[shape.p2].name = name
    elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
    for i,p in ipairs(shape.vertices) do
    local name = p.name
    shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600)
    drawing.points[shape.vertices[i]].name = name
    end
    elseif shape.mode == 'circle' or shape.mode == 'arc' then
    local name = shape.center.name
    shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600)
    drawing.points[shape.center].name = name
    elseif shape.mode == 'deleted' then
    -- ignore
    else
  • file deletion: source_edit.lua (----------)source_edit.lua (----------)
    [6.2][6.165788:165827](),[6.2][6.165788:165827](),[6.165827][6.152503:152503]()
    assert(#State.lines == #State.line_cache, ('line_cache is out of date; %d elements when it should be %d'):format(#State.line_cache, #State.lines))
    assert(Text.le1(State.screen_top1, State.cursor1), ('screen_top (line=%d,pos=%d) is below cursor (line=%d,pos=%d)'):format(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos))
    assert(false, ('unknown line mode %s'):format(line.mode))
    end
    State.cursor_x = nil
    State.cursor_y = nil
    local y = State.top
  • file deletion: log_browser.lua (----------)log_browser.lua (----------)
    [6.2][6.202684:202723](),[6.2][6.202684:202723](),[6.202723][6.191243:191243]()
    assert(#State.lines == #State.line_cache, ('line_cache is out of date; %d elements when it should be %d'):format(#State.line_cache, #State.lines))
    else assert(line.section_end, "log line has a section name, but it's neither the start nor end of a section")
    local sectiony = y+State.line_height-Section_border_padding_vertical
    love.graphics.line(xleft,y, xleft,sectiony)
    love.graphics.line(xright,y, xright,sectiony)
    love.graphics.line(xleft,sectiony, xleft+50-2,sectiony)
    local mouse_line_index = log_browser.line_index(State, App.mouse_x(), App.mouse_y())
    local y = State.top
    for line_index = State.screen_top1.line,#State.lines do
    App.color(Text_color)
    local line = State.lines[line_index]
    if y + State.line_height > App.screen.height then break end
    local height = State.line_height
    if should_show(line) then
    local xleft = render_stack_left_margin(State, line_index, line, y)
    local xright = render_stack_right_margin(State, line_index, line, y)
    if line.section_name then
    App.color(Section_border_color)
  • file deletion: commands.lua (----------)commands.lua (----------)
    [6.2][6.207182:207218](),[6.2][6.207182:207218](),[6.207218][6.203826:203826]()
    assert(index, 'file missing from manifest')
    table.remove(File_navigation.all_candidates, index)
    table.insert(File_navigation.all_candidates, 1, s)
    end
    function reset_file_navigator()
    Show_file_navigator = false
    File_navigation.index = 1
    File_navigation.filter = ''
    File_navigation.candidates = File_navigation.all_candidates
    end
  • edit in file.lua at line 79
    [6.1296][5.8401:8401](),[6.1296][5.8401:8401]()
  • resolve order conflict in file.lua at line 79
    [97.3530]