Merge lines.love

[?]
Dec 24, 2022, 3:47 AM
ORKN6EOBUFVAD2TXYW5OIKSL55RU24LOFDTTTXHDZUZ57QRDCY7QC

Dependencies

  • [2] EG5CRNWC Merge lines.love
  • [3] VZJHGWSP Merge lines.love
  • [4] XRP727K3 Merge lines.love
  • [5] KOTNETIM repeat changes on source editor
  • [6] FQZ3U3YA streamline one more test name
  • [7] 2CK5QI7W make love event names consistent
  • [8] KYNGDE2C consistent names in a few more places
  • [9] DCO5BQWV Merge lines.love
  • [10] LUNH47XX make text and drawings the same width
  • [11] MTJEVRJR add state arg to a few functions
  • [12] 3QNOKBFM beginnings of a test harness
  • [13] SCOXD4EO Merge lines.love
  • [14] DRFE3B3Z mouse buttons are integers, not strings
  • [15] RZKXJYFK switch a bunch of tests to textinput events
  • [16] JMUD7T3O get rid of ugly side-effects in tests
  • [17] AVQ5MC5D finish uppercasing all globals
  • [18] TFM6F5OD Merge lines.love
  • [19] X3CQLBTR set window title within each app
  • [20] TGZAJUEF bring back a set of constants
  • [21] 32V6ZHQB Merge lines.love
  • [22] OWK3U6VD tests for drawing polygons
  • [23] MKPXANB5 bugfix: mouse clicks on file navigator above log browser side
  • [24] ZTK4QTZT extract a couple of functions
  • [25] AJB4LFRB try to maintain a reasonable line width
  • [26] 2Y7YH7UP infrastructure for caching LÖVE text objects
  • [27] 3HVBAZPA add state arg to a few functions
  • [28] KVHUFUFV reorg
  • [29] LF7BWEG4 group all editor globals
  • [30] BJ5X5O4A let's prevent the text cursor from ever getting on a drawing
  • [31] OTIBCAUJ love2d scaffold
  • [32] XNFTJHC4 split keyboard handling between Text and Drawing
  • [33] S2YQBEYC snapshot: test for a new regression
  • [34] ENENSZLK bugfix: source margins when toggling log browser
  • [35] VP5KC4XZ Merge lines.love
  • [36] 42LVB4DE test: naming a point
  • [37] FISC4HIN repeat bugfix on source editor
  • [38] PX7DDEMO autosave slightly less aggressively
  • [39] TXDMRA5J bugfix: alt-tab shouldn't emit keypress events
  • [40] 5UKUADTW distinguish consistently between mouse buttons and other buttons
  • [41] LYN3L74W correct commit f3abc2cbf2
  • [42] D3FLL7SL start showing source menu file navigation state graphically
  • [43] Z4KNS42N to open a file without a terminal, drag it on!
  • [44] 5HEZU3YS consume a mouse click when switching sides
  • [45] JIK7ZRYI bugfix: imprecision in drawing
  • [46] C45WCXJ2 keep drawings within the line width slider as well
  • [47] C4VTBATA .
  • [48] QFC3WRDZ chunking by simple local variable
  • [49] JJDUDMVX Merge lines.love
  • [50] GLABQJQQ repeat bugfix on source editor X-(
  • [51] GN3C6AGM bugfix in changing shape mid-stroke
  • [52] AKZWDWIA Merge lines.love
  • [53] BULPIBEG beginnings of a module for the text editor
  • [54] D2GCFTTT clean up repl functionality
  • [55] T7SJSJIH test: undo naming a point
  • [56] TLOAPLBJ add a license
  • [57] OGUV4HSA remove some memory leaks from rendered fragments
  • [58] BLWAYPKV extract a module
  • [59] OI4FPFIN support drawings in the source editor
  • [60] DJGC4ZEF simplify hysteresis logic
  • [61] KKMFQDR4 editing source code from within the app
  • [62] K2X6G75Z start writing some tests for drawings
  • [63] VHQCNMAR several more modules
  • [64] 23MA4T3G add state arg to Drawing.keychord_pressed
  • [65] P5QNVXSN drop final mention of state global beyond main.lua
  • [66] 6LJZN727 handle chords
  • [67] 52ZZ5TIE switch to line index in a function
  • [68] S7ZZA3YE ugh, handle absolute as well as relative paths
  • [69] ME7WBLF5 bugfix: log filenames can have 2 formats
  • [70] VO2ZVTWK Merge lines.love
  • [71] 4YDBYBA4 clean up memory leak experiments
  • [72] KMSL74GA support selections in the source editor
  • [73] PTDO2SOT add state arg to schedule_save
  • [74] HYEAFRZ2 split mouse_pressed events between Text and Drawing
  • [75] 2CTN2IEF Merge lines.love
  • [76] KAUD3YIK tests: deleting points/shapes
  • [77] W6XUYQKP source: show files in MRU order
  • [78] ETXNVRPT Merge lines.love
  • [79] APYPFFS3 call edit rather than App callbacks in tests
  • [80] 6DE7RBZ6 move mouse_released events to Drawing
  • [81] R3JZDBI2 drop heavyweight near check on file load/store
  • [82] RT6EV6OP delegate update events to drawings
  • [83] VBU5YHLR Merge lines.love
  • [84] 73OCE2MC after much struggle, a brute-force undo
  • [85] AYX33NBC Merge lines.love
  • [86] OAHNWDYG .
  • [87] CRYGI3LR more drawing tests
  • [88] HRWN5V6J Devine's suggestion to try to live with just freehand
  • [89] D4B52CQ2 Merge lines.love
  • [90] BH7BT36L ctrl+a: select entire buffer
  • [91] R3XGABER chunk up some long lines
  • [92] QS3YLNKZ Merge lines.love
  • [93] M6TH7VSZ rip out notion of Line_width
  • [94] JOPVPUSA editing source code from within the app
  • [95] YT5P6TO6 bugfix: save previous file when dropping a new one on
  • [96] TYFAGQWS repeat bugfix on source editor
  • [97] TVCPXAAU rename
  • [98] NMRUNROT Merge lines.love
  • [99] UYCVUHI5 Merge lines.love
  • [100] 5DOTWNVM right margin
  • [101] BJ2C6F2B ignore 'name' mode in a few places
  • [102] Z6HI3K55 correct a comment
  • [103] 66X36NZN a little more prose describing manual_tests
  • [104] 3PSFWAIL Merge lines.love
  • [105] APX2PY6G stop tracking wallclock time
  • [106] ELIVOJ4N bugfix: zoom in/out hotkeys
  • [107] KTZQ57HV replace globals with args in a few functions
  • [108] R5QXEHUI somebody stop me
  • [109] 3OTESDW6 move drawing.starty into line cache
  • [110] QCPXQ2E3 add state arg to a few functions
  • [111] MUJTM6RE bring back a level of wrapping
  • [112] XX7G2FFJ intermingle freehand line drawings with text
  • [113] CQYKYJJU remember window positions across restart/ctrl+e
  • [114] JRLBUB6L more intuitive point delete from polygons
  • [115] VHUNJHXB Merge lines.love
  • [116] QXVD2RIF add state arg to Drawing.mouse_released
  • [117] UHB4GARJ left/right margin -> left/right coordinates
  • [118] MDGHRTIF source: up/down in file navigator
  • [119] CE4LZV4T drop last couple of manual tests
  • [120] VXORMHME delete experimental REPL
  • [121] RSZD5A7G forgot to add json.lua
  • [122] 2JLVAYHB start decoupling editor tests from App
  • [123] RQUVBX62 filter candidates in file navigator
  • [124] AVTNUQYR basic test-enabled framework
  • [125] 2L5MEZV3 experiment: new edit namespace
  • [126] DLQAEAC7 add state arg to Drawing.mouse_pressed
  • [127] 2WGHUWE6 self-documenting 0 Test_right_margin
  • [128] U2TKUOID bugfix: undo drawing creation
  • [129] EMG7SDLW bugfix: cold start
  • [130] PK5U572C drop some extra args
  • [131] LNUHQOGH start passing in Editor_state explicitly
  • [132] T4FRZSYL delete an ancient, unused file
  • [133] JCSLDGAH beginnings of support for multiple shapes
  • [134] FS2ITYYH record a known issue
  • [135] GUOQRUL7 Merge lines.love
  • [136] MFZW24AN bugfix: disable typing while file navigator is open
  • [137] MD3W5IRA new fork: rip out drawing support
  • [138] L2FWWEQL source: remember cursor position of multiple files
  • [139] ATQO62TF Merge lines.love
  • [140] LXTTOB33 extract a couple of files

Change contents

  • file deletion: drawing.lua (----------)
    [9.2][9.113893:113928](),[9.113928][9.81462:81462]()
    -- primitives for editing drawings
    Drawing = {}
    require 'drawing_tests'
    -- All drawings span 100% of some conceptual 'page width' and divide it up
    -- into 256 parts.
    function Drawing.draw(State, line_index, y)
    local line = State.lines[line_index]
    local line_cache = State.line_cache[line_index]
    line_cache.starty = y
    local pmx,pmy = App.mouse_x(), App.mouse_y()
    if pmx < State.right and pmy > line_cache.starty and pmy < line_cache.starty+Drawing.pixels(line.h, State.width) then
    App.color(Icon_color)
    love.graphics.rectangle('line', State.left,line_cache.starty, State.width,Drawing.pixels(line.h, State.width))
    if icon[State.current_drawing_mode] then
    icon[State.current_drawing_mode](State.right-22, line_cache.starty+4)
    else
    icon[State.previous_drawing_mode](State.right-22, line_cache.starty+4)
    end
    if App.mouse_down(1) and love.keyboard.isDown('h') then
    draw_help_with_mouse_pressed(State, line_index)
    return
    end
    end
    if line.show_help then
    draw_help_without_mouse_pressed(State, line_index)
    return
    end
    local mx = Drawing.coord(pmx-State.left, State.width)
    local my = Drawing.coord(pmy-line_cache.starty, State.width)
    for _,shape in ipairs(line.shapes) do
    assert(shape)
    if geom.on_shape(mx,my, line, shape) then
    App.color(Focus_stroke_color)
    else
    App.color(Stroke_color)
    end
    Drawing.draw_shape(line, shape, line_cache.starty, State.left,State.right)
    end
    local function px(x) return Drawing.pixels(x, State.width)+State.left end
    local function py(y) return Drawing.pixels(y, State.width)+line_cache.starty end
    for i,p in ipairs(line.points) do
    if p.deleted == nil then
    if Drawing.near(p, mx,my, State.width) then
    App.color(Focus_stroke_color)
    love.graphics.circle('line', px(p.x),py(p.y), Same_point_distance)
    else
    App.color(Stroke_color)
    love.graphics.circle('fill', px(p.x),py(p.y), 2)
    end
    if p.name then
    -- TODO: clip
    local x,y = px(p.x)+5, py(p.y)+5
    love.graphics.print(p.name, x,y)
    if State.current_drawing_mode == 'name' and i == line.pending.target_point then
    -- create a faint red box for the name
    App.color(Current_name_background_color)
    local name_text
    -- TODO: avoid computing name width on every repaint
    if p.name == '' then
    name_text = State.em
    else
    name_text = App.newText(love.graphics.getFont(), p.name)
    end
    love.graphics.rectangle('fill', x,y, App.width(name_text), State.line_height)
    end
    end
    end
    end
    App.color(Current_stroke_color)
    Drawing.draw_pending_shape(line, line_cache.starty, State.left,State.right)
    end
    function Drawing.draw_shape(drawing, shape, top, left,right)
    local width = right-left
    local function px(x) return Drawing.pixels(x, width)+left end
    local function py(y) return Drawing.pixels(y, width)+top end
    if shape.mode == 'freehand' then
    local prev = nil
    for _,point in ipairs(shape.points) do
    if prev then
    love.graphics.line(px(prev.x),py(prev.y), px(point.x),py(point.y))
    end
    prev = point
    end
    elseif shape.mode == 'line' or shape.mode == 'manhattan' then
    local p1 = drawing.points[shape.p1]
    local p2 = drawing.points[shape.p2]
    love.graphics.line(px(p1.x),py(p1.y), px(p2.x),py(p2.y))
    elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
    local prev = nil
    for _,point in ipairs(shape.vertices) do
    local curr = drawing.points[point]
    if prev then
    love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))
    end
    prev = curr
    end
    -- close the loop
    local curr = drawing.points[shape.vertices[1]]
    love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))
    elseif shape.mode == 'circle' then
    -- TODO: clip
    local center = drawing.points[shape.center]
    love.graphics.circle('line', px(center.x),py(center.y), Drawing.pixels(shape.radius, width))
    elseif shape.mode == 'arc' then
    local center = drawing.points[shape.center]
    love.graphics.arc('line', 'open', px(center.x),py(center.y), Drawing.pixels(shape.radius, width), shape.start_angle, shape.end_angle, 360)
    elseif shape.mode == 'deleted' then
    -- ignore
    else
    print(shape.mode)
    assert(false)
    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
    -- after mouse_released
    pmx,pmy = px(mx), py(my)
    local shape = drawing.pending
    if shape.mode == nil then
    -- nothing pending
    elseif shape.mode == 'freehand' then
    local shape_copy = deepcopy(shape)
    Drawing.smoothen(shape_copy)
    Drawing.draw_shape(drawing, shape_copy, top, left,right)
    elseif shape.mode == 'line' then
    if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
    return
    end
    local p1 = drawing.points[shape.p1]
    love.graphics.line(px(p1.x),py(p1.y), pmx,pmy)
    elseif shape.mode == 'manhattan' then
    if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
    return
    end
    local p1 = drawing.points[shape.p1]
    if math.abs(mx-p1.x) > math.abs(my-p1.y) then
    love.graphics.line(px(p1.x),py(p1.y), pmx, py(p1.y))
    else
    love.graphics.line(px(p1.x),py(p1.y), px(p1.x),pmy)
    end
    elseif shape.mode == 'polygon' then
    -- don't close the loop on a pending polygon
    local prev = nil
    for _,point in ipairs(shape.vertices) do
    local curr = drawing.points[point]
    if prev then
    love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))
    end
    prev = curr
    end
    love.graphics.line(px(prev.x),py(prev.y), pmx,pmy)
    elseif shape.mode == 'rectangle' then
    local first = drawing.points[shape.vertices[1]]
    if #shape.vertices == 1 then
    love.graphics.line(px(first.x),py(first.y), pmx,pmy)
    return
    end
    local second = drawing.points[shape.vertices[2]]
    local thirdx,thirdy, fourthx,fourthy = Drawing.complete_rectangle(first.x,first.y, second.x,second.y, mx,my)
    love.graphics.line(px(first.x),py(first.y), px(second.x),py(second.y))
    love.graphics.line(px(second.x),py(second.y), px(thirdx),py(thirdy))
    love.graphics.line(px(thirdx),py(thirdy), px(fourthx),py(fourthy))
    love.graphics.line(px(fourthx),py(fourthy), px(first.x),py(first.y))
    elseif shape.mode == 'square' then
    local first = drawing.points[shape.vertices[1]]
    if #shape.vertices == 1 then
    love.graphics.line(px(first.x),py(first.y), pmx,pmy)
    return
    end
    local second = drawing.points[shape.vertices[2]]
    local thirdx,thirdy, fourthx,fourthy = Drawing.complete_square(first.x,first.y, second.x,second.y, mx,my)
    love.graphics.line(px(first.x),py(first.y), px(second.x),py(second.y))
    love.graphics.line(px(second.x),py(second.y), px(thirdx),py(thirdy))
    love.graphics.line(px(thirdx),py(thirdy), px(fourthx),py(fourthy))
    love.graphics.line(px(fourthx),py(fourthy), px(first.x),py(first.y))
    elseif shape.mode == 'circle' then
    local center = drawing.points[shape.center]
    if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
    return
    end
    local cx,cy = px(center.x), py(center.y)
    love.graphics.circle('line', cx,cy, geom.dist(cx,cy, App.mouse_x(),App.mouse_y()))
    elseif shape.mode == 'arc' then
    local center = drawing.points[shape.center]
    if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
    return
    end
    shape.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, shape.end_angle)
    local cx,cy = px(center.x), py(center.y)
    love.graphics.arc('line', 'open', cx,cy, Drawing.pixels(shape.radius, width), shape.start_angle, shape.end_angle, 360)
    elseif shape.mode == 'move' then
    -- nothing pending; changes are immediately committed
    elseif shape.mode == 'name' then
    -- nothing pending; changes are immediately committed
    else
    print(shape.mode)
    assert(false)
    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
    function Drawing.mouse_pressed(State, drawing_index, x,y, mouse_button)
    local drawing = State.lines[drawing_index]
    local line_cache = State.line_cache[drawing_index]
    local cx = Drawing.coord(x-State.left, State.width)
    local cy = Drawing.coord(y-line_cache.starty, State.width)
    if State.current_drawing_mode == 'freehand' then
    drawing.pending = {mode=State.current_drawing_mode, points={{x=cx, y=cy}}}
    elseif State.current_drawing_mode == 'line' or State.current_drawing_mode == 'manhattan' then
    local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)
    drawing.pending = {mode=State.current_drawing_mode, p1=j}
    elseif State.current_drawing_mode == 'polygon' or State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square' then
    local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)
    drawing.pending = {mode=State.current_drawing_mode, vertices={j}}
    elseif State.current_drawing_mode == 'circle' then
    local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)
    drawing.pending = {mode=State.current_drawing_mode, center=j}
    elseif State.current_drawing_mode == 'move' then
    -- all the action is in mouse_released
    elseif State.current_drawing_mode == 'name' then
    -- nothing
    else
    print(State.current_drawing_mode)
    assert(false)
    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]
    assert(drawing.mode == 'drawing')
    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
    function Drawing.mouse_released(State, x,y, mouse_button)
    if State.current_drawing_mode == 'move' then
    State.current_drawing_mode = State.previous_drawing_mode
    State.previous_drawing_mode = nil
    if State.lines.current_drawing then
    State.lines.current_drawing.pending = {}
    State.lines.current_drawing = nil
    end
    elseif State.lines.current_drawing then
    local drawing = State.lines.current_drawing
    local line_cache = State.line_cache[State.lines.current_drawing_index]
    if drawing.pending then
    if drawing.pending.mode == nil then
    -- nothing pending
    elseif drawing.pending.mode == 'freehand' then
    -- the last point added during update is good enough
    Drawing.smoothen(drawing.pending)
    table.insert(drawing.shapes, drawing.pending)
    elseif drawing.pending.mode == 'line' 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
    drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
    table.insert(drawing.shapes, drawing.pending)
    end
    elseif drawing.pending.mode == 'manhattan' then
    local p1 = drawing.points[drawing.pending.p1]
    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
    if math.abs(mx-p1.x) > math.abs(my-p1.y) then
    drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx, p1.y, State.width)
    else
    drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, p1.x, my, State.width)
    end
    local p2 = drawing.points[drawing.pending.p2]
    App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), line_cache.starty+Drawing.pixels(p2.y, State.width))
    table.insert(drawing.shapes, drawing.pending)
    end
    elseif drawing.pending.mode == 'polygon' 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
    table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, mx,my, State.width))
    table.insert(drawing.shapes, drawing.pending)
    end
    elseif drawing.pending.mode == 'rectangle' then
    assert(#drawing.pending.vertices <= 2)
    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
    assert(#drawing.pending.vertices <= 2)
    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
    print(drawing.pending.mode)
    assert(false)
    end
    State.lines.current_drawing.pending = {}
    State.lines.current_drawing = nil
    end
    end
    end
    function Drawing.keychord_pressed(State, chord)
    if chord == 'C-p' and not App.mouse_down(1) then
    State.current_drawing_mode = 'freehand'
    elseif App.mouse_down(1) and chord == 'l' then
    State.current_drawing_mode = 'line'
    local _,drawing = Drawing.current_drawing(State)
    if drawing.pending.mode == 'freehand' then
    drawing.pending.p1 = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)
    elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
    drawing.pending.p1 = drawing.pending.vertices[1]
    elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
    drawing.pending.p1 = drawing.pending.center
    end
    drawing.pending.mode = 'line'
    elseif chord == 'C-l' and not App.mouse_down(1) then
    State.current_drawing_mode = 'line'
    elseif App.mouse_down(1) and chord == 'm' then
    State.current_drawing_mode = 'manhattan'
    local drawing = Drawing.select_drawing_at_mouse(State)
    if drawing.pending.mode == 'freehand' then
    drawing.pending.p1 = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)
    elseif drawing.pending.mode == 'line' then
    -- do nothing
    elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
    drawing.pending.p1 = drawing.pending.vertices[1]
    elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
    drawing.pending.p1 = drawing.pending.center
    end
    drawing.pending.mode = 'manhattan'
    elseif chord == 'C-m' and not App.mouse_down(1) then
    State.current_drawing_mode = 'manhattan'
    elseif chord == 'C-g' and not App.mouse_down(1) then
    State.current_drawing_mode = 'polygon'
    elseif App.mouse_down(1) and chord == 'g' then
    State.current_drawing_mode = 'polygon'
    local _,drawing = Drawing.current_drawing(State)
    if drawing.pending.mode == 'freehand' then
    drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}
    elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
    if drawing.pending.vertices == nil then
    drawing.pending.vertices = {drawing.pending.p1}
    end
    elseif drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
    -- reuse existing vertices
    elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
    drawing.pending.vertices = {drawing.pending.center}
    end
    drawing.pending.mode = 'polygon'
    elseif chord == 'C-r' and not App.mouse_down(1) then
    State.current_drawing_mode = 'rectangle'
    elseif App.mouse_down(1) and chord == 'r' then
    State.current_drawing_mode = 'rectangle'
    local _,drawing = Drawing.current_drawing(State)
    if drawing.pending.mode == 'freehand' then
    drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}
    elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
    if drawing.pending.vertices == nil then
    drawing.pending.vertices = {drawing.pending.p1}
    end
    elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'square' then
    -- reuse existing (1-2) vertices
    elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
    drawing.pending.vertices = {drawing.pending.center}
    end
    drawing.pending.mode = 'rectangle'
    elseif chord == 'C-s' and not App.mouse_down(1) then
    State.current_drawing_mode = 'square'
    elseif App.mouse_down(1) and chord == 's' then
    State.current_drawing_mode = 'square'
    local _,drawing = Drawing.current_drawing(State)
    if drawing.pending.mode == 'freehand' then
    drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}
    elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
    if drawing.pending.vertices == nil then
    drawing.pending.vertices = {drawing.pending.p1}
    end
    elseif drawing.pending.mode == 'polygon' then
    while #drawing.pending.vertices > 2 do
    table.remove(drawing.pending.vertices)
    end
    elseif drawing.pending.mode == 'rectangle' then
    -- reuse existing (1-2) vertices
    elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
    drawing.pending.vertices = {drawing.pending.center}
    end
    drawing.pending.mode = 'square'
    elseif App.mouse_down(1) and chord == 'p' and State.current_drawing_mode == 'polygon' then
    local _,drawing,line_cache = Drawing.current_drawing(State)
    local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
    local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
    table.insert(drawing.pending.vertices, j)
    elseif App.mouse_down(1) and chord == 'p' and (State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square') then
    local _,drawing,line_cache = Drawing.current_drawing(State)
    local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
    local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
    while #drawing.pending.vertices >= 2 do
    table.remove(drawing.pending.vertices)
    end
    table.insert(drawing.pending.vertices, j)
    elseif chord == 'C-o' and not App.mouse_down(1) then
    State.current_drawing_mode = 'circle'
    elseif App.mouse_down(1) and chord == 'a' and State.current_drawing_mode == 'circle' then
    local _,drawing,line_cache = Drawing.current_drawing(State)
    drawing.pending.mode = 'arc'
    local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
    local center = drawing.points[drawing.pending.center]
    drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))
    drawing.pending.start_angle = geom.angle(center.x,center.y, mx,my)
    elseif App.mouse_down(1) and chord == 'o' then
    State.current_drawing_mode = 'circle'
    local _,drawing = Drawing.current_drawing(State)
    if drawing.pending.mode == 'freehand' then
    drawing.pending.center = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)
    elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
    drawing.pending.center = drawing.pending.p1
    elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
    drawing.pending.center = drawing.pending.vertices[1]
    end
    drawing.pending.mode = 'circle'
    elseif chord == 'C-u' and not App.mouse_down(1) then
    local drawing_index,drawing,line_cache,i,p = Drawing.select_point_at_mouse(State)
    if drawing then
    if State.previous_drawing_mode == nil then
    State.previous_drawing_mode = State.current_drawing_mode
    end
    State.current_drawing_mode = 'move'
    drawing.pending = {mode=State.current_drawing_mode, target_point=p, target_point_index=i}
    State.lines.current_drawing_index = drawing_index
    State.lines.current_drawing = drawing
    end
    elseif chord == 'C-n' and not App.mouse_down(1) then
    local drawing_index,drawing,line_cache,point_index,p = Drawing.select_point_at_mouse(State)
    if drawing then
    if State.previous_drawing_mode == nil then
    -- don't clobber
    State.previous_drawing_mode = State.current_drawing_mode
    end
    State.current_drawing_mode = 'name'
    p.name = ''
    drawing.pending = {mode=State.current_drawing_mode, target_point=point_index}
    State.lines.current_drawing_index = drawing_index
    State.lines.current_drawing = drawing
    end
    elseif chord == 'C-d' and not App.mouse_down(1) then
    local _,drawing,_,i,p = Drawing.select_point_at_mouse(State)
    if drawing then
    for _,shape in ipairs(drawing.shapes) do
    if Drawing.contains_point(shape, i) then
    if shape.mode == 'polygon' then
    local idx = table.find(shape.vertices, i)
    assert(idx)
    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
    assert(shape)
    if geom.on_shape(mx,my, drawing, shape) then
    return drawing,line_cache,i,shape
    end
    end
    end
    end
    end
    end
    function Drawing.select_point_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,point in ipairs(drawing.points) do
    assert(point)
    if Drawing.near(point, mx,my, State.width) then
    return drawing_index,drawing,line_cache,i,point
    end
    end
    end
    end
    end
    end
    function Drawing.select_drawing_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
    return drawing
    end
    end
    end
    end
    function Drawing.contains_point(shape, p)
    if shape.mode == 'freehand' then
    -- not supported
    elseif shape.mode == 'line' or shape.mode == 'manhattan' then
    return shape.p1 == p or shape.p2 == p
    elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
    return table.find(shape.vertices, p)
    elseif shape.mode == 'circle' then
    return shape.center == p
    elseif shape.mode == 'arc' then
    return shape.center == p
    -- ugh, how to support angles
    elseif shape.mode == 'deleted' then
    -- already done
    else
    print(shape.mode)
    assert(false)
    end
    end
    function Drawing.smoothen(shape)
    assert(shape.mode == 'freehand')
    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
  • file deletion: source_text_tests.lua (----------)source_text_tests.lua (----------)
    [9.2][9.83676:83721](),[9.2][9.83676:83721](),[9.83721][9.3498:3498]()
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line_takes_margins_into_account/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_on_wrapping_line_takes_margins_into_account/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_on_wrapping_line_takes_margins_into_account/selection is empty to avoid perturbing future edits')
    App.screen.check(y, 'mno', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:3')
    edit.run_after_text_input(Editor_state, 'a')
    edit.run_after_keychord(Editor_state, 'up')
    -- cursor wraps
    check_eq(Editor_state.cursor1.line, 1, 'F - test_search_wrap_upwards/1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'F - test_search_wrap_upwards/1/cursor:pos')
    end
    edit.run_after_text_input(Editor_state, 'a')
    edit.run_after_keychord(Editor_state, 'return')
    -- cursor wraps
    check_eq(Editor_state.cursor1.line, 1, 'F - test_search_wrap/1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_search_wrap/1/cursor:pos')
    end
    function test_search_wrap_upwards()
    io.write('\ntest_search_wrap_upwards')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc abd'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- search upwards for a string
    edit.run_after_keychord(Editor_state, 'C-f')
    edit.run_after_text_input(Editor_state, 'a')
    -- search for previous occurrence
    edit.run_after_keychord(Editor_state, 'up')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_search_upwards/2/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_search_upwards/2/cursor:pos')
    end
    function test_search_wrap()
    io.write('\ntest_search_wrap')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- search for a string
    edit.run_after_keychord(Editor_state, 'C-f')
    edit.run_after_text_input(Editor_state, 'de')
    edit.run_after_keychord(Editor_state, 'down')
    edit.run_after_keychord(Editor_state, 'return')
    check_eq(Editor_state.cursor1.line, 4, 'F - test_search/2/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_search/2/cursor:pos')
    end
    function test_search_upwards()
    io.write('\ntest_search_upwards')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc abd'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- search for a string
    edit.run_after_keychord(Editor_state, 'C-f')
    edit.run_after_text_input(Editor_state, 'd')
    edit.run_after_keychord(Editor_state, 'return')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_search/1/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_search/1/cursor:pos')
    -- reset cursor
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    -- search for second occurrence
    edit.run_after_keychord(Editor_state, 'C-f')
    edit.run_after_text_input(Editor_state, 'x')
    check_eq(Editor_state.lines[1].data, 'xbc', 'F - test_undo_restores_selection/baseline')
    check_nil(Editor_state.selection1.line, 'F - test_undo_restores_selection/baseline:selection')
    -- undo
    edit.run_after_keychord(Editor_state, 'C-z')
    edit.run_after_keychord(Editor_state, 'C-z')
    -- selection is restored
    check_eq(Editor_state.selection1.line, 1, 'F - test_undo_restores_selection/line')
    check_eq(Editor_state.selection1.pos, 2, 'F - test_undo_restores_selection/pos')
    end
    edit.run_after_text_input(Editor_state, 'g')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_undo_insert_text/baseline/cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'F - test_undo_insert_text/baseline/cursor:pos')
    edit.run_after_text_input(Editor_state, 's')
    edit.run_after_text_input(Editor_state, 't')
    edit.run_after_text_input(Editor_state, 'u')
    check_eq(Editor_state.cursor1.pos, 28, 'F - test_position_cursor_on_recently_edited_wrapping_line/cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'abc def ghi ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl mno pqr ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'stu', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:3')
    -- try to move the cursor earlier in the third screen line by clicking the mouse
    edit.run_after_text_input(Editor_state, 'j')
    edit.run_after_text_input(Editor_state, 'k')
    edit.run_after_text_input(Editor_state, 'l')
    check_eq(Editor_state.screen_top1.line, 2, 'F - test_typing_on_bottom_line_scrolls_down/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_typing_on_bottom_line_scrolls_down/cursor:line')
    check_eq(Editor_state.cursor1.pos, 7, 'F - test_typing_on_bottom_line_scrolls_down/cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'def', 'F - test_typing_on_bottom_line_scrolls_down/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij', 'F - test_typing_on_bottom_line_scrolls_down/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'F - test_typing_on_bottom_line_scrolls_down/screen:3')
    end
    function test_left_arrow_scrolls_up_in_wrapped_line()
    io.write('\ntest_left_arrow_scrolls_up_in_wrapped_line')
    -- display lines starting from second screen line of a line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.screen_top1 = {line=3, pos=5}
    Editor_state.screen_bottom1 = {}
    -- cursor is at top of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'F - test_left_arrow_scrolls_up_in_wrapped_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'F - test_left_arrow_scrolls_up_in_wrapped_line/baseline/screen:2')
    -- after hitting the left arrow the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'left')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'F - test_left_arrow_scrolls_up_in_wrapped_line/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_left_arrow_scrolls_up_in_wrapped_line/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'F - test_left_arrow_scrolls_up_in_wrapped_line/screen:3')
    check_eq(Editor_state.screen_top1.line, 3, 'F - test_left_arrow_scrolls_up_in_wrapped_line/screen_top')
    check_eq(Editor_state.screen_top1.pos, 1, 'F - test_left_arrow_scrolls_up_in_wrapped_line/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_left_arrow_scrolls_up_in_wrapped_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 4, 'F - test_left_arrow_scrolls_up_in_wrapped_line/cursor:pos')
    end
    function test_right_arrow_scrolls_down_in_wrapped_line()
    io.write('\ntest_right_arrow_scrolls_down_in_wrapped_line')
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- cursor is at bottom right of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_right_arrow_scrolls_down_in_wrapped_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_right_arrow_scrolls_down_in_wrapped_line/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'F - test_right_arrow_scrolls_down_in_wrapped_line/baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the right arrow the screen scrolls down by one line
    edit.run_after_keychord(Editor_state, 'right')
    check_eq(Editor_state.screen_top1.line, 2, 'F - test_right_arrow_scrolls_down_in_wrapped_line/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_right_arrow_scrolls_down_in_wrapped_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 6, 'F - test_right_arrow_scrolls_down_in_wrapped_line/cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'def', 'F - test_right_arrow_scrolls_down_in_wrapped_line/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'F - test_right_arrow_scrolls_down_in_wrapped_line/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_right_arrow_scrolls_down_in_wrapped_line/screen:3')
    end
    function test_home_scrolls_up_in_wrapped_line()
    io.write('\ntest_home_scrolls_up_in_wrapped_line')
    -- display lines starting from second screen line of a line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.screen_top1 = {line=3, pos=5}
    Editor_state.screen_bottom1 = {}
    -- cursor is at top of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'F - test_home_scrolls_up_in_wrapped_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'F - test_home_scrolls_up_in_wrapped_line/baseline/screen:2')
    -- after hitting home the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'home')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'F - test_home_scrolls_up_in_wrapped_line/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_home_scrolls_up_in_wrapped_line/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'F - test_home_scrolls_up_in_wrapped_line/screen:3')
    check_eq(Editor_state.screen_top1.line, 3, 'F - test_home_scrolls_up_in_wrapped_line/screen_top')
    check_eq(Editor_state.screen_top1.pos, 1, 'F - test_home_scrolls_up_in_wrapped_line/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_home_scrolls_up_in_wrapped_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_home_scrolls_up_in_wrapped_line/cursor:pos')
    end
    function test_end_scrolls_down_in_wrapped_line()
    io.write('\ntest_end_scrolls_down_in_wrapped_line')
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- cursor is at bottom right of screen
    Editor_state.cursor1 = {line=3, pos=5}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_end_scrolls_down_in_wrapped_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_end_scrolls_down_in_wrapped_line/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'F - test_end_scrolls_down_in_wrapped_line/baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting end the screen scrolls down by one line
    edit.run_after_keychord(Editor_state, 'end')
    check_eq(Editor_state.screen_top1.line, 2, 'F - test_end_scrolls_down_in_wrapped_line/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_end_scrolls_down_in_wrapped_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 8, 'F - test_end_scrolls_down_in_wrapped_line/cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'def', 'F - test_end_scrolls_down_in_wrapped_line/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'F - test_end_scrolls_down_in_wrapped_line/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_end_scrolls_down_in_wrapped_line/screen:3')
    end
    function test_position_cursor_on_recently_edited_wrapping_line()
    -- draw a line wrapping over 2 screen lines
    io.write('\ntest_position_cursor_on_recently_edited_wrapping_line')
    App.screen.init{width=100, height=200}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr ', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=25}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc def ghi ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl mno pqr ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'xyz', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:3')
    -- add to the line until it's wrapping over 3 screen lines
    edit.run_after_text_input(Editor_state, 'a')
    check_eq(Editor_state.screen_top1.line, 2, 'F - test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:pos')
    local y = Editor_state.top
    App.screen.check(y, 'a', 'F - test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:1')
    end
    function test_typing_on_bottom_line_scrolls_down()
    io.write('\ntest_typing_on_bottom_line_scrolls_down')
    -- display a few lines with cursor on bottom line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=4}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_typing_on_bottom_line_scrolls_down/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_typing_on_bottom_line_scrolls_down/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_typing_on_bottom_line_scrolls_down/baseline/screen:3')
    -- after typing something the line wraps and the screen scrolls down
    end
    function test_up_arrow_moves_cursor()
    io.write('\ntest_up_arrow_moves_cursor')
    -- display the first 3 lines with the cursor on the bottom line
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_up_arrow_moves_cursor/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_moves_cursor/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_moves_cursor/baseline/screen:3')
    -- after hitting the up arrow the cursor moves up by 1 line
    edit.run_after_keychord(Editor_state, 'up')
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_up_arrow_moves_cursor/screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_up_arrow_moves_cursor/cursor')
    -- the screen is unchanged
    y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_up_arrow_moves_cursor/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_moves_cursor/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_moves_cursor/screen:3')
    end
    function test_up_arrow_scrolls_up_by_one_line()
    io.write('\ntest_up_arrow_scrolls_up_by_one_line')
    -- display the lines 2/3/4 with the cursor on line 2
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    edit.run_after_keychord(Editor_state, 'up')
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_by_one_line/screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_up_arrow_scrolls_up_by_one_line/cursor')
    y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_by_one_line/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_by_one_line/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_by_one_line/screen:3')
    end
    function test_up_arrow_scrolls_up_by_one_screen_line()
    io.write('\ntest_up_arrow_scrolls_up_by_one_screen_line')
    -- display lines starting from second screen line of a line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=6}
    Editor_state.screen_top1 = {line=3, pos=5}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:2')
    -- after hitting the up arrow the screen scrolls up to first screen line
    edit.run_after_keychord(Editor_state, 'up')
    y = Editor_state.top
    App.screen.check(y, 'ghi ', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:3')
    check_eq(Editor_state.screen_top1.line, 3, 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top')
    check_eq(Editor_state.screen_top1.pos, 1, 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:pos')
    end
    function test_up_arrow_scrolls_up_to_final_screen_line()
    io.write('\ntest_up_arrow_scrolls_up_to_final_screen_line')
    -- display lines starting just after a long line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to final screen line of previous line
    edit.run_after_keychord(Editor_state, 'up')
    y = Editor_state.top
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:3')
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top')
    check_eq(Editor_state.screen_top1.pos, 5, 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:pos')
    end
    function test_up_arrow_scrolls_up_to_empty_line()
    io.write('\ntest_up_arrow_scrolls_up_to_empty_line')
    -- display a screenful of text with an empty line just above it outside the screen
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'', 'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    edit.run_after_keychord(Editor_state, 'up')
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_to_empty_line/screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_up_arrow_scrolls_up_to_empty_line/cursor')
    y = Editor_state.top
    -- empty first line
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_to_empty_line/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_empty_line/screen:3')
    end
    function test_pageup()
    io.write('\ntest_pageup')
    App.screen.init{width=120, height=45}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    Editor_state.screen_bottom1 = {}
    -- initially the last two lines are displayed
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'F - test_pageup/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_pageup/baseline/screen:2')
    -- after pageup the cursor goes to first line
    edit.run_after_keychord(Editor_state, 'pageup')
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_pageup/screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_pageup/cursor')
    y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_pageup/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_pageup/screen:2')
    end
    function test_pageup_scrolls_up_by_screen_line()
    io.write('\ntest_pageup_scrolls_up_by_screen_line')
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'ghi', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    edit.run_after_keychord(Editor_state, 'pageup')
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_pageup_scrolls_up_by_screen_line/screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_pageup_scrolls_up_by_screen_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_pageup_scrolls_up_by_screen_line/cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'abc ', 'F - test_pageup_scrolls_up_by_screen_line/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_pageup_scrolls_up_by_screen_line/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_pageup_scrolls_up_by_screen_line/screen:3')
    end
    function test_pageup_scrolls_up_from_middle_screen_line()
    io.write('\ntest_pageup_scrolls_up_from_middle_screen_line')
    -- display a few lines starting from the middle of a line (Editor_state.cursor1.pos > 1)
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi jkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=5}
    Editor_state.screen_top1 = {line=2, pos=5}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'mno', 'F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    edit.run_after_keychord(Editor_state, 'pageup')
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/screen_top')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'abc ', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi ', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:3')
    end
    function test_enter_on_bottom_line_scrolls_down()
    io.write('\ntest_enter_on_bottom_line_scrolls_down')
    -- display a few lines with cursor on bottom line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:3')
    -- after hitting the enter key the screen scrolls down
    edit.run_after_keychord(Editor_state, 'return')
    check_eq(Editor_state.screen_top1.line, 2, 'F - test_enter_on_bottom_line_scrolls_down/screen_top')
    check_eq(Editor_state.cursor1.line, 4, 'F - test_enter_on_bottom_line_scrolls_down/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_enter_on_bottom_line_scrolls_down/cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'def', 'F - test_enter_on_bottom_line_scrolls_down/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'g', 'F - test_enter_on_bottom_line_scrolls_down/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'hi', 'F - test_enter_on_bottom_line_scrolls_down/screen:3')
    end
    function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    io.write('\ntest_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom')
    -- display just the bottom line on screen
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=4, pos=2}
    Editor_state.screen_top1 = {line=4, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'jkl', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/baseline/screen:1')
    -- after hitting the enter key the screen does not scroll down
    edit.run_after_keychord(Editor_state, 'return')
    check_eq(Editor_state.screen_top1.line, 4, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen_top')
    check_eq(Editor_state.cursor1.line, 5, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'j', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'kl', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:2')
    end
    function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    io.write('\ntest_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom')
    -- display just an empty bottom line on screen
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', ''}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=2, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- after hitting the inserting_text key the screen does not scroll down
    App.screen.check(y, 'kl', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghij', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:1')
    y = y + Editor_state.line_height
    check_eq(Editor_state.screen_top1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos')
    y = Editor_state.top
    check_eq(Editor_state.screen_top1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:pos')
    -- after hitting down arrow the screen doesn't scroll down further, and certainly doesn't scroll up
    edit.run_after_keychord(Editor_state, 'down')
    App.screen.check(y, 'ghij', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:3')
    -- after hitting pagedown the screen scrolls down to start of a long line
    edit.run_after_keychord(Editor_state, 'pagedown')
    App.screen.check(y, 'def', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'abc', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:1')
    y = y + Editor_state.line_height
    function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()
    io.write('\ntest_pagedown_followed_by_down_arrow_does_not_scroll_screen_up')
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghijkl', 'mno'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    edit.run_after_text_input(Editor_state, 'g')
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_edit_wrapping_text/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'de', 'F - test_edit_wrapping_text/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'fg', 'F - test_edit_wrapping_text/screen:3')
    end
    function test_insert_newline()
    io.write('\ntest_insert_newline')
    -- display a few lines
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_insert_newline/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_insert_newline/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_insert_newline/baseline/screen:3')
    -- hitting the enter key splits the line
    edit.run_after_keychord(Editor_state, 'return')
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_insert_newline/screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_insert_newline/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_insert_newline/cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'a', 'F - test_insert_newline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'bc', 'F - test_insert_newline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_insert_newline/screen:3')
    end
    function test_insert_newline_at_start_of_line()
    io.write('\ntest_insert_newline_at_start_of_line')
    -- display a line
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- hitting the enter key splits the line
    edit.run_after_keychord(Editor_state, 'return')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_insert_newline_at_start_of_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_insert_newline_at_start_of_line/cursor:pos')
    check_eq(Editor_state.lines[1].data, '', 'F - test_insert_newline_at_start_of_line/data:1')
    check_eq(Editor_state.lines[2].data, 'abc', 'F - test_insert_newline_at_start_of_line/data:2')
    end
    function test_insert_from_clipboard()
    io.write('\ntest_insert_from_clipboard')
    -- display a few lines
    App.screen.init{width=Editor_state.left+30, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_insert_from_clipboard/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_insert_from_clipboard/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_insert_from_clipboard/baseline/screen:3')
    -- paste some text including a newline, check that new line is created
    App.clipboard = 'xy\nz'
    edit.run_after_keychord(Editor_state, 'C-v')
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_insert_from_clipboard/screen_top')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_insert_from_clipboard/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_insert_from_clipboard/cursor:pos')
    y = Editor_state.top
    App.screen.check(y, 'axy', 'F - test_insert_from_clipboard/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'zbc', 'F - test_insert_from_clipboard/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_insert_from_clipboard/screen:3')
    edit.keychord_press(Editor_state, 'd', 'd')
    edit.text_input(Editor_state, 'D')
    edit.key_release(Editor_state, 'd')
    App.fake_key_release('lshift')
    -- selected text is deleted and replaced with the key
    check_nil(Editor_state.selection1.line, 'F - test_edit_with_shift_key_deletes_selection')
    check_eq(Editor_state.lines[1].data, 'Dbc', 'F - test_edit_with_shift_key_deletes_selection/data')
    end
    function test_copy_does_not_reset_selection()
    io.write('\ntest_copy_does_not_reset_selection')
    -- display a line of text with a selection
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- copy selection
    edit.run_after_keychord(Editor_state, 'C-c')
    check_eq(App.clipboard, 'a', 'F - test_copy_does_not_reset_selection/clipboard')
    -- selection is reset since shift key is not pressed
    check(Editor_state.selection1.line, 'F - test_copy_does_not_reset_selection')
    end
    function test_cut()
    io.write('\ntest_cut')
    -- display a line of text with some part selected
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- press a key
    edit.run_after_keychord(Editor_state, 'C-x')
    check_eq(App.clipboard, 'a', 'F - test_cut/clipboard')
    -- selected text is deleted
    check_eq(Editor_state.lines[1].data, 'bc', 'F - test_cut/data')
    end
    function test_paste_replaces_selection()
    io.write('\ntest_paste_replaces_selection')
    -- display a line of text with a selection
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.selection1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- set clipboard
    App.clipboard = 'xyz'
    -- paste selection
    edit.run_after_keychord(Editor_state, 'C-v')
    -- selection is reset since shift key is not pressed
    -- selection includes the newline, so it's also deleted
    check_eq(Editor_state.lines[1].data, 'xyzdef', 'F - test_paste_replaces_selection')
    end
    function test_deleting_selection_may_scroll()
    io.write('\ntest_deleting_selection_may_scroll')
    -- display lines 2/3/4
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=3, pos=2}
    Editor_state.screen_top1 = {line=2, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'def', 'F - test_deleting_selection_may_scroll/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_deleting_selection_may_scroll/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'jkl', 'F - test_deleting_selection_may_scroll/baseline/screen:3')
    -- set up a selection starting above the currently displayed page
    Editor_state.selection1 = {line=1, pos=2}
    -- delete selection
    edit.run_after_keychord(Editor_state, 'backspace')
    -- page scrolls up
    check_eq(Editor_state.screen_top1.line, 1, 'F - test_deleting_selection_may_scroll')
    check_eq(Editor_state.lines[1].data, 'ahi', 'F - test_deleting_selection_may_scroll/data')
    end
    edit.run_after_text_input(Editor_state, 'x')
    -- selected text is deleted and replaced with the key
    check_eq(Editor_state.lines[1].data, 'xbc', 'F - test_edit_deletes_selection')
    end
    function test_edit_with_shift_key_deletes_selection()
    io.write('\ntest_edit_with_shift_key_deletes_selection')
    -- display a line of text with some part selected
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- mimic precise keypresses for a capital letter
    App.fake_key_press('lshift')
    edit.key_release(Editor_state, 'lshift')
    -- selection persists even after shift is release
    check_eq(Editor_state.selection1.line, 1, 'F - test_select_text/selection:line')
    check_eq(Editor_state.selection1.pos, 1, 'F - test_select_text/selection:pos')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_select_text/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_select_text/cursor:pos')
    end
    function test_cursor_movement_without_shift_resets_selection()
    io.write('\ntest_cursor_movement_without_shift_resets_selection')
    -- display a line of text with some part selected
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.selection1 = {line=1, pos=2}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    -- press an arrow key without shift
    edit.run_after_keychord(Editor_state, 'right')
    -- no change to data, selection is reset
    check_nil(Editor_state.selection1.line, 'F - test_cursor_movement_without_shift_resets_selection')
    check_eq(Editor_state.lines[1].data, 'abc', 'F - test_cursor_movement_without_shift_resets_selection/data')
    end
    function test_draw_text_wrapping_within_word()
    -- arrange a screen line that needs to be split within a word
    io.write('\ntest_draw_text_wrapping_within_word')
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abcd e fghijk', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abcd ', 'F - test_draw_text_wrapping_within_word/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'e fgh', 'F - test_draw_text_wrapping_within_word/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ijk', 'F - test_draw_text_wrapping_within_word/screen:3')
    end
    function test_draw_wrapping_text_containing_non_ascii()
    -- draw a long line containing non-ASCII
    io.write('\ntest_draw_wrapping_text_containing_non_ascii')
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'madam I’m adam', 'xyz'} -- notice the non-ASCII apostrophe
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'mad', 'F - test_draw_wrapping_text_containing_non_ascii/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'am I', 'F - test_draw_wrapping_text_containing_non_ascii/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, '’m a', 'F - test_draw_wrapping_text_containing_non_ascii/screen:3')
    end
    function test_click_on_wrapping_line()
    io.write('\ntest_click_on_wrapping_line')
    -- display a wrapping line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'F - test_click_on_wrapping_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, "I'm ad", 'F - test_click_on_wrapping_line/baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 12, 'F - test_click_on_wrapping_line/cursor:pos')
    end
    function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()
    io.write('\ntest_click_on_wrapping_line_rendered_from_partway_at_top_of_screen')
    -- display a wrapping line from its second screen line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=8}
    Editor_state.screen_top1 = {line=1, pos=7}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, "I'm ad", 'F - test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen/baseline/screen:2')
    y = y + Editor_state.line_height
    -- click past end of second screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen/cursor:line')
    check_eq(Editor_state.cursor1.pos, 12, 'F - test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen/cursor:pos')
    end
    function test_click_past_end_of_wrapping_line()
    io.write('\ntest_click_past_end_of_wrapping_line')
    -- display a wrapping line
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{"madam I'm adam"}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'F - test_click_past_end_of_wrapping_line/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, "I'm ad", 'F - test_click_past_end_of_wrapping_line/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'am', 'F - test_click_past_end_of_wrapping_line/baseline/screen:3')
    y = y + Editor_state.line_height
    -- click past the end of it
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of line
    check_eq(Editor_state.cursor1.pos, 15, 'F - test_click_past_end_of_wrapping_line/cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_wrapping_line_containing_non_ascii()
    io.write('\ntest_click_past_end_of_wrapping_line_containing_non_ascii')
    -- display a wrapping line containing non-ASCII
    App.screen.init{width=75, height=80}
    Editor_state = edit.initialize_test_state()
    -- 12345678901234
    Editor_state.lines = load_array{'madam I’m adam'} -- notice the non-ASCII apostrophe
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'madam ', 'F - test_click_past_end_of_wrapping_line_containing_non_ascii/baseline/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'I’m ad', 'F - test_click_past_end_of_wrapping_line_containing_non_ascii/baseline/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'am', 'F - test_click_past_end_of_wrapping_line_containing_non_ascii/baseline/screen:3')
    y = y + Editor_state.line_height
    -- click past the end of it
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of line
    check_eq(Editor_state.cursor1.pos, 15, 'F - test_click_past_end_of_wrapping_line_containing_non_ascii/cursor') -- one more than the number of UTF-8 code-points
    end
    function test_click_past_end_of_word_wrapping_line()
    io.write('\ntest_click_past_end_of_word_wrapping_line')
    -- display a long line wrapping at a word boundary on a screen of more realistic length
    App.screen.init{width=160, height=80}
    Editor_state = edit.initialize_test_state()
    -- 0 1 2
    -- 123456789012345678901
    Editor_state.lines = load_array{'the quick brown fox jumped over the lazy dog'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'the quick brown fox ', 'F - test_click_past_end_of_word_wrapping_line/baseline/screen:1')
    y = y + Editor_state.line_height
    -- click past the end of the screen line
    edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
    -- cursor moves to end of screen line
    check_eq(Editor_state.cursor1.pos, 20, 'F - test_click_past_end_of_word_wrapping_line/cursor')
    function test_click_on_wrapping_line_takes_margins_into_account()
    io.write('\ntest_click_on_wrapping_line_takes_margins_into_account')
    -- display two lines with cursor on one of them
    App.screen.init{width=100, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.left = 50 -- occupy only right side of screen
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=20}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_on_wrapping_line/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_on_wrapping_line/selection is empty to avoid perturbing future edits')
    end
    function test_click_on_wrapping_line()
    io.write('\ntest_click_on_wrapping_line')
    -- display two lines with cursor on one of them
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=20}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_empty_line/cursor')
    end
    function test_draw_text()
    io.write('\ntest_draw_text')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_draw_text/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def', 'F - test_draw_text/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_draw_text/screen:3')
    end
    function test_draw_wrapping_text()
    io.write('\ntest_draw_wrapping_text')
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'defgh', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc', 'F - test_draw_wrapping_text/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'de', 'F - test_draw_wrapping_text/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'fgh', 'F - test_draw_wrapping_text/screen:3')
    end
    function test_draw_word_wrapping_text()
    io.write('\ntest_draw_word_wrapping_text')
    App.screen.init{width=60, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def ghi', 'jkl'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.draw(Editor_state)
    local y = Editor_state.top
    App.screen.check(y, 'abc ', 'F - test_draw_word_wrapping_text/screen:1')
    y = y + Editor_state.line_height
    App.screen.check(y, 'def ', 'F - test_draw_word_wrapping_text/screen:2')
    y = y + Editor_state.line_height
    App.screen.check(y, 'ghi', 'F - test_draw_word_wrapping_text/screen:3')
    end
    function test_click_on_empty_line()
    io.write('\ntest_click_on_empty_line')
    -- display two lines with the first one empty
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- click on the empty line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_takes_margins_into_account/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_takes_margins_into_account/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_takes_margins_into_account/selection is empty to avoid perturbing future edits')
    end
    function test_click_takes_margins_into_account()
    io.write('\ntest_click_takes_margins_into_account')
    -- display two lines with cursor on one of them
    App.screen.init{width=100, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.left = 50 -- occupy only right side of screen
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    edit.run_after_text_input(Editor_state, 'a')
    local y = Editor_state.top
    App.screen.check(y, 'a', 'F - test_insert_first_character/screen:1')
    end
    function test_press_ctrl()
    io.write('\ntest_press_ctrl')
    -- press ctrl while the cursor is on text
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{''}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    edit.run_after_keychord(Editor_state, 'C-m')
    end
    function test_move_left()
    io.write('\ntest_move_left')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'a'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=2}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'left')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_move_left')
    end
    function test_move_right()
    io.write('\ntest_move_right')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'a'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'right')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_move_right')
    end
    function test_move_left_to_previous_line()
    io.write('\ntest_move_left_to_previous_line')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'left')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_move_left_to_previous_line/line')
    check_eq(Editor_state.cursor1.pos, 4, 'F - test_move_left_to_previous_line/pos') -- past end of line
    end
    function test_move_right_to_next_line()
    io.write('\ntest_move_right_to_next_line')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- past end of line
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'right')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_move_right_to_next_line/line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_move_right_to_next_line/pos')
    end
    function test_move_to_start_of_word()
    io.write('\ntest_move_to_start_of_word')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_move_to_start_of_word')
    end
    function test_move_to_start_of_previous_word()
    io.write('\ntest_move_to_start_of_previous_word')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_move_to_start_of_previous_word')
    end
    function test_skip_to_previous_word()
    io.write('\ntest_skip_to_previous_word')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=5} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_skip_to_previous_word')
    end
    function test_skip_past_tab_to_previous_word()
    io.write('\ntest_skip_past_tab_to_previous_word')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def\tghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=10} -- within third word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left')
    check_eq(Editor_state.cursor1.pos, 9, 'F - test_skip_past_tab_to_previous_word')
    end
    function test_skip_multiple_spaces_to_previous_word()
    io.write('\ntest_skip_multiple_spaces_to_previous_word')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=6} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_skip_multiple_spaces_to_previous_word')
    end
    function test_move_to_start_of_word_on_previous_line()
    io.write('\ntest_move_to_start_of_word_on_previous_line')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=2, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-left')
    check_eq(Editor_state.cursor1.line, 1, 'F - test_move_to_start_of_word_on_previous_line/line')
    check_eq(Editor_state.cursor1.pos, 5, 'F - test_move_to_start_of_word_on_previous_line/pos')
    end
    function test_move_past_end_of_word()
    io.write('\ntest_move_past_end_of_word')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right')
    check_eq(Editor_state.cursor1.pos, 4, 'F - test_move_past_end_of_word')
    end
    function test_skip_to_next_word()
    io.write('\ntest_skip_to_next_word')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right')
    check_eq(Editor_state.cursor1.pos, 8, 'F - test_skip_to_next_word')
    end
    function test_skip_past_tab_to_next_word()
    io.write('\ntest_skip_past_tab_to_next_word')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc\tdef'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1} -- at the space between words
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right')
    check_eq(Editor_state.cursor1.pos, 4, 'F - test_skip_past_tab_to_next_word')
    end
    function test_skip_multiple_spaces_to_next_word()
    io.write('\ntest_skip_multiple_spaces_to_next_word')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=4} -- at the start of second word
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right')
    check_eq(Editor_state.cursor1.pos, 9, 'F - test_skip_multiple_spaces_to_next_word')
    end
    function test_move_past_end_of_word_on_next_line()
    io.write('\ntest_move_past_end_of_word_on_next_line')
    App.screen.init{width=120, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc def', 'ghi'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=8}
    edit.draw(Editor_state)
    edit.run_after_keychord(Editor_state, 'M-right')
    check_eq(Editor_state.cursor1.line, 2, 'F - test_move_past_end_of_word_on_next_line/line')
    check_eq(Editor_state.cursor1.pos, 4, 'F - test_move_past_end_of_word_on_next_line/pos')
    end
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_to_left_of_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_click_to_left_of_line/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_to_left_of_line/selection is empty to avoid perturbing future edits')
    end
    function test_click_to_left_of_line()
    io.write('\ntest_click_to_left_of_line')
    -- display a line with the cursor in the middle
    App.screen.init{width=50, height=80}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=3}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    -- click to the left of the line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1)
    -- cursor moves to start of line
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_moves_cursor/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_moves_cursor/cursor:pos')
    -- selection is empty to avoid perturbing future edits
    check_nil(Editor_state.selection1.line, 'F - test_click_moves_cursor/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_click_moves_cursor/selection:pos')
    end
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
    Text.redraw_all(Editor_state)
    function test_click_moves_cursor()
    io.write('\ntest_click_moves_cursor')
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
  • file deletion: source_text.lua (----------)source_text.lua (----------)
    [9.2][9.147062:147101](),[9.2][9.147062:147101](),[9.147101][9.83723:83723]()
    -- Don't handle any keys here that would trigger text_input above.
    function Text.keychord_press(State, chord)
    --? print('chord', chord, State.selection1.line, State.selection1.pos)
    function Text.text_input(State, t)
    if App.mouse_down(1) then return end
    if App.ctrl_down() or App.alt_down() or App.cmd_down() then return end
    local before = snapshot(State, State.cursor1.line)
    --? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
    Text.insert_at_cursor(State, t)
    if State.cursor_y > App.screen.height - State.line_height then
    Text.populate_screen_line_starting_pos(State, State.cursor1.line)
    Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
    end
    record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
    end
    function Text.insert_at_cursor(State, t)
    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
    end
  • file deletion: source_edit.lua (----------)source_edit.lua (----------)
    [9.2][9.165725:165764](),[9.2][9.165725:165764](),[9.165764][9.152440:152440]()
    edit.mouse_release(State, x,y, mouse_button)
    App.screen.contents = {}
    edit.mouse_release(State, x,y, mouse_button)
    edit.mouse_press(State, x,y, mouse_button)
    App.screen.contents = {}
    App.screen.contents = {}
    edit.keychord_press(State, chord)
    edit.key_release(State, chord)
    edit.mouse_press(State, x,y, mouse_button)
    App.fake_mouse_release(x,y, mouse_button)
    App.screen.contents = {}
    -- not all keys are text_input
    function edit.run_after_keychord(State, chord)
    function edit.run_after_text_input(State, t)
    edit.keychord_press(State, t)
    edit.text_input(State, t)
    edit.key_release(State, t)
    App.screen.contents = {}
    -- all text_input events are also keypresses
    -- TODO: handle chords of multiple keys
    function edit.key_release(State, key, scancode)
    end
    function edit.update_font_settings(State, font_height)
    State.font_height = font_height
    Text.keychord_press(State, chord)
    end
    end
    function edit.eradicate_locations_after_the_fold(State)
    -- eradicate side B from any locations we track
    if State.cursor1.posB then
    State.cursor1.posB = nil
    State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data)
    State.cursor1.pos = Text.pos_at_start_of_screen_line(State, State.cursor1)
    end
    if State.screen_top1.posB then
    State.screen_top1.posB = nil
    State.screen_top1.pos = utf8.len(State.lines[State.screen_top1.line].data)
    State.screen_top1.pos = Text.pos_at_start_of_screen_line(State, State.screen_top1)
    end
    end
    Drawing.keychord_press(State, chord)
    record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})
    schedule_save(State)
    end
    elseif chord == 'escape' and not App.mouse_down(1) then
    for _,line in ipairs(State.lines) do
    if line.mode == 'drawing' then
    line.show_help = false
    end
    end
    elseif State.current_drawing_mode == 'name' then
    if chord == 'return' then
    State.current_drawing_mode = State.previous_drawing_mode
    State.previous_drawing_mode = nil
    else
    local before = snapshot(State, State.lines.current_drawing_index)
    local drawing = State.lines.current_drawing
    local p = drawing.points[drawing.pending.target_point]
    if chord == 'escape' then
    p.name = nil
    record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
    elseif chord == 'backspace' then
    local len = utf8.len(p.name)
    local byte_offset = Text.offset(p.name, len-1)
    if len == 1 then byte_offset = 0 end
    p.name = string.sub(p.name, 1, byte_offset)
    record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})
    end
    end
    schedule_save(State)
    function edit.keychord_press(State, chord, key)
    if State.selection1.line and
    not State.lines.current_drawing and
    -- printable character created using shift key => delete selection
    -- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)
    (not App.shift_down() or utf8.len(key) == 1) and
    Text.text_input(State, t)
    end
    function edit.text_input(State, t)
    if State.search_term then
    State.search_term = State.search_term..t
    State.search_text = nil
    Text.search_next(State)
    Drawing.mouse_release(State, x,y, mouse_button)
    schedule_save(State)
    if Drawing.before then
    record_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})
    Drawing.before = nil
    end
    function edit.mouse_release(State, x,y, mouse_button)
    if State.search_term then return end
    --? print('release')
    if State.lines.current_drawing then
    -- i.e. mouse_release should never look at shift state
    Drawing.mouse_press(State, line_index, x,y, mouse_button)
    break
    end
    State.old_cursor1 = State.cursor1
    State.old_selection1 = State.selection1
    State.mousepress_shift = App.shift_down()
    function edit.mouse_press(State, x,y, mouse_button)
    if State.search_term then return end
  • file deletion: source.lua (----------)source.lua (----------)
    [9.2][9.177652:177686](),[9.2][9.177652:177686](),[9.177686][9.165766:165766]()
    return log_browser.keychord_press(Log_browser_state, chordkey, scancode)
    end
    end
    -- use this sparingly
    function to_text(s)
    if Text_cache[s] == nil then
    Text_cache[s] = App.newText(love.graphics.getFont(), s)
    end
    return Text_cache[s]
    end
    return edit.key_release(Editor_state, key, scancode)
    else
    function source.key_release(key, scancode)
    Cursor_time = 0 -- ensure cursor is visible immediately after it moves
    if Focus == 'edit' then
    return log_browser.keychord_press(Log_browser_state, chord, key)
    end
    end
    return edit.keychord_press(Editor_state, chord, key)
    else
    keychord_press_on_file_navigator(chord, key)
    return
    end
    if chord == 'C-l' then
    --? print('C-l')
    Show_log_browser_side = not Show_log_browser_side
    if Show_log_browser_side then
    function source.keychord_press(chord, key)
    Cursor_time = 0 -- ensure cursor is visible immediately after it moves
    --? print('source keychord')
    if Show_file_navigator then
    return log_browser.text_input(Log_browser_state, t)
    end
    end
    return edit.text_input(Editor_state, t)
    else
    text_input_on_file_navigator(t)
    return
    end
    function source.text_input(t)
    Cursor_time = 0 -- ensure cursor is visible immediately after it moves
    return log_browser.mouse_release(Log_browser_state, x,y, mouse_button)
    end
    end
    return edit.mouse_release(Editor_state, x,y, mouse_button)
    else
    function source.mouse_release(x,y, mouse_button)
    Cursor_time = 0 -- ensure cursor is visible immediately after it moves
    if Focus == 'edit' then
    -- a copy of source.file_drop when given a filename
    log_browser.mouse_press(Log_browser_state, x,y, mouse_button)
    for _,line_cache in ipairs(Editor_state.line_cache) do line_cache.starty = nil end -- just in case we scroll
    end
    end
    edit.mouse_press(Editor_state, x,y, mouse_button)
    elseif Show_log_browser_side and Log_browser_state.left <= x and x < Log_browser_state.right then
    --? print('click on log_browser side')
    if Focus ~= 'log_browser' then
    Focus = 'log_browser'
    edit.mouse_press(Editor_state, x,y, mouse_button)
    return
    end
    function source.mouse_press(x,y, mouse_button)
    Cursor_time = 0 -- ensure cursor is visible immediately after it moves
    --? print('mouse click', x, y)
    --? print(Editor_state.left, Editor_state.right)
    --? print(Log_browser_state.left, Log_browser_state.right)
    function source.switch_to_file(filename)
    -- first make sure to save edits on any existing file
    if Editor_state.next_save then
    save_to_disk(Editor_state)
    end
    function source.file_drop(file)
    -- first make sure to save edits on any existing file
    if Editor_state.next_save then
    save_to_disk(Editor_state)
    end
    -- clear the slate for the new file
    Editor_state.filename = file:getFilename()
    file:open('r')
    Editor_state.lines = load_from_file(file)
    file:close()
    Text.redraw_all(Editor_state)
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.cursor1 = {line=1, pos=1}
  • file deletion: run.lua (----------)run.lua (----------)
    [9.2][9.184046:184077](),[9.2][9.184046:184077](),[9.184077][9.178044:178044]()
    return edit.key_release(Editor_state, key, scancode)
    end
    -- use this sparingly
    function to_text(s)
    if Text_cache[s] == nil then
    Text_cache[s] = App.newText(love.graphics.getFont(), s)
    end
    return Text_cache[s]
    end
    function run.key_release(key, scancode)
    Cursor_time = 0 -- ensure cursor is visible immediately after it moves
    return edit.keychord_press(Editor_state, chord, key)
    end
    function run.keychord_press(chord, key)
    Cursor_time = 0 -- ensure cursor is visible immediately after it moves
    return edit.text_input(Editor_state, t)
    end
    function run.text_input(t)
    Cursor_time = 0 -- ensure cursor is visible immediately after it moves
    return edit.mouse_release(Editor_state, x,y, mouse_button)
    end
    function run.mouse_release(x,y, mouse_button)
    Cursor_time = 0 -- ensure cursor is visible immediately after it moves
    function run.file_drop(file)
    -- first make sure to save edits on any existing file
    if Editor_state.next_save then
    save_to_disk(Editor_state)
    end
    -- clear the slate for the new file
    App.initialize_globals()
    Editor_state.filename = file:getFilename()
    file:open('r')
    Editor_state.lines = load_from_file(file)
    file:close()
    Text.redraw_all(Editor_state)
    edit.fixup_cursor(Editor_state)
    love.window.setTitle('lines.love - '..Editor_state.filename)
    end
    function run.draw()
    edit.draw(Editor_state)
    end
    function run.update(dt)
    Cursor_time = Cursor_time + dt
    edit.update(Editor_state, dt)
    end
    function run.quit()
    edit.quit(Editor_state)
    end
    function run.settings()
  • file deletion: log_browser.lua (----------)log_browser.lua (----------)
    [9.2][9.203223:203262](),[9.2][9.203223:203262](),[9.203262][9.191782:191782]()
    function log_browser.key_release(State, key, scancode)
    end
    function log_browser.keychord_press(State, chord, key)
    -- move
    if chord == 'up' then
    while State.screen_top1.line > 1 do
    State.screen_top1.line = State.screen_top1.line-1
    if should_show(State.lines[State.screen_top1.line]) then
    break
    end
    end
    elseif chord == 'down' then
    while State.screen_top1.line < #State.lines do
    State.screen_top1.line = State.screen_top1.line+1
    if should_show(State.lines[State.screen_top1.line]) then
    break
    end
    end
    elseif chord == 'pageup' then
    local y = 0
    while State.screen_top1.line > 1 and y < App.screen.height - 100 do
    State.screen_top1.line = State.screen_top1.line - 1
    if should_show(State.lines[State.screen_top1.line]) then
    y = y + log_browser.height(State, State.screen_top1.line)
    end
    end
    elseif chord == 'pagedown' then
    local y = 0
    while State.screen_top1.line < #State.lines and y < App.screen.height - 100 do
    if should_show(State.lines[State.screen_top1.line]) then
    y = y + log_browser.height(State, State.screen_top1.line)
    end
    State.screen_top1.line = State.screen_top1.line + 1
    end
    end
    end
    function log_browser.height(State, line_index)
    local line = State.lines[line_index]
    if line.data == nil then
    -- section header
    return State.line_height
    elseif type(line.data) == 'string' then
    return State.line_height
    else
    if line.height == nil then
    --? print('nil line height! rendering off screen to calculate')
    line.height = log_render[line.data.name](line.data, State.left, App.screen.height, State.right-State.left)
    end
    return line.height
    end
    end
    function log_browser.text_input(State, t)
    end
    function log_browser.mouse_release(State, x,y, mouse_button)
    end
    function log_browser.mouse_press(State, x,y, mouse_button)
    local line_index = log_browser.line_index(State, x,y)
    if line_index == nil then
    -- below lower margin
    return
    end
    -- leave some space to click without focusing
    local line = State.lines[line_index]
    local xleft = log_browser.left_margin(State, line)
    local xright = log_browser.right_margin(State, line)
    if x < xleft or x > xright then
    return
    end
    -- if it's a section begin/end and the section is collapsed, expand it
    -- TODO: how to collapse?
    if line.section_begin or line.section_end then
    -- HACK: get section reference from next/previous line
    local new_section
    if line.section_begin then
    if line_index < #State.lines then
    local next_section_stack = State.lines[line_index+1].section_stack
    if next_section_stack then
    new_section = next_section_stack[#next_section_stack]
    end
    end
    elseif line.section_end then
    if line_index > 1 then
    local previous_section_stack = State.lines[line_index-1].section_stack
    if previous_section_stack then
    new_section = previous_section_stack[#previous_section_stack]
    end
    end
    end
    if new_section and new_section.expanded == nil then
    new_section.expanded = true
    return
    end
    end
    -- open appropriate file in source side
    if line.filename ~= Editor_state.filename then
    source.switch_to_file(line.filename)
    end
    -- set cursor
    Editor_state.cursor1 = {line=line.line_number, pos=1, posB=nil}
    -- 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'
    -- expand B side
    Editor_state.expanded = true
    end
    function log_browser.line_index(State, mx,my)
    -- duplicate some logic from log_browser.draw
    local y = State.top
    for line_index = State.screen_top1.line,#State.lines do
    local line = State.lines[line_index]
    if should_show(line) then
    y = y + log_browser.height(State, line_index)
    if my < y then
    return line_index
    end
    if y > App.screen.height then break end
    end
    end
    end
  • file deletion: commands.lua (----------)commands.lua (----------)
    [9.2][9.207726:207762](),[9.2][9.207726:207762](),[9.207762][9.204370:204370]()
    function text_input_on_file_navigator(t)
    File_navigation.filter = File_navigation.filter..t
    File_navigation.candidates = source.file_navigator_candidates()
    end
    function keychord_press_on_file_navigator(chord, key)
    log(2, 'file navigator: '..chord)
  • resurrect zombie in text_tests.lua at line 1935
    [9.6032][9.1650:1741](),[9.6032][9.1650:1741]()
    check_eq(Editor_state.lines[1].data, 'xbc', 'F - test_undo_restores_selection/baseline')
  • edit in text_tests.lua at line 1935
    [9.381]
    [9.1650]
    edit.run_after_text_input(Editor_state, 'x')
  • edit in text_tests.lua at line 1937
    [9.381][7.744:791](),[9.381][7.744:791]()
    edit.run_after_text_input(Editor_state, 'x')
  • resolve order conflict in text_tests.lua at line 1937
    [9.1741]
    [9.53832]
  • replacement in source_text_tests.lua at line 68
    [9.5299][9.5299:5345]()
    edit.run_after_textinput(Editor_state, 'a')
    [9.5299]
    [9.5345]
    edit.run_after_text_input(Editor_state, 'a')
  • replacement in source_text_tests.lua at line 270
    [9.13302][9.13302:13462]()
    function test_click_with_mouse()
    io.write('\ntest_click_with_mouse')
    -- display two lines with cursor on one of them
    App.screen.init{width=50, height=80}
    [9.13302]
    [9.13462]
    function test_click_moves_cursor()
    io.write('\ntest_click_moves_cursor')
    App.screen.init{width=50, height=60}
  • replacement in source_text_tests.lua at line 274
    [9.13508][9.13508:13556]()
    Editor_state.lines = load_array{'abc', 'def'}
    [9.13508]
    [9.13556]
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
  • replacement in source_text_tests.lua at line 276
    [9.13588][9.13588:13629]()
    Editor_state.cursor1 = {line=2, pos=1}
    [9.13588]
    [9.13629]
    Editor_state.cursor1 = {line=1, pos=1}
  • replacement in source_text_tests.lua at line 279
    [9.13709][9.13709:13950](),[9.13950][9.6:129]()
    -- click on the other line
    edit.draw(Editor_state)
    edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    -- cursor moves
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse/cursor:line')
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse/selection is empty to avoid perturbing future edits')
    [9.13709]
    [9.13950]
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_moves_cursor/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_moves_cursor/cursor:pos')
    -- selection is empty to avoid perturbing future edits
    check_nil(Editor_state.selection1.line, 'F - test_click_moves_cursor/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_click_moves_cursor/selection:pos')
  • replacement in source_text_tests.lua at line 289
    [9.13955][9.13955:14058]()
    function test_click_with_mouse_to_left_of_line()
    io.write('\ntest_click_with_mouse_to_left_of_line')
    [9.13955]
    [9.14058]
    function test_click_to_left_of_line()
    io.write('\ntest_click_to_left_of_line')
  • replacement in source_text_tests.lua at line 303
    [9.14569][9.14569:14763](),[9.14763][9.130:269]()
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_to_left_of_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_click_with_mouse_to_left_of_line/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_to_left_of_line/selection is empty to avoid perturbing future edits')
    [9.14569]
    [9.14763]
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_to_left_of_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_click_to_left_of_line/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_to_left_of_line/selection is empty to avoid perturbing future edits')
  • replacement in source_text_tests.lua at line 308
    [9.14768][9.14768:14893]()
    function test_click_with_mouse_takes_margins_into_account()
    io.write('\ntest_click_with_mouse_takes_margins_into_account')
    [9.14768]
    [9.14893]
    function test_click_takes_margins_into_account()
    io.write('\ntest_click_takes_margins_into_account')
  • replacement in source_text_tests.lua at line 323
    [9.15451][9.15451:15667](),[9.15667][9.270:420]()
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_takes_margins_into_account/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_with_mouse_takes_margins_into_account/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_takes_margins_into_account/selection is empty to avoid perturbing future edits')
    [9.15451]
    [9.15667]
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_takes_margins_into_account/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_takes_margins_into_account/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_takes_margins_into_account/selection is empty to avoid perturbing future edits')
  • replacement in source_text_tests.lua at line 328
    [9.15672][9.15672:15771]()
    function test_click_with_mouse_on_empty_line()
    io.write('\ntest_click_with_mouse_on_empty_line')
    [9.15672]
    [9.15771]
    function test_click_on_empty_line()
    io.write('\ntest_click_on_empty_line')
  • replacement in source_text_tests.lua at line 342
    [9.16261][9.16261:16352]()
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_on_empty_line/cursor')
    [9.16261]
    [9.16352]
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_empty_line/cursor')
  • replacement in source_text_tests.lua at line 399
    [9.18457][9.18457:18562]()
    function test_click_with_mouse_on_wrapping_line()
    io.write('\ntest_click_with_mouse_on_wrapping_line')
    [9.18457]
    [9.18562]
    function test_click_on_wrapping_line()
    io.write('\ntest_click_on_wrapping_line')
  • replacement in source_text_tests.lua at line 413
    [9.19075][9.19075:19271](),[9.19271][9.421:561]()
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_on_wrapping_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_with_mouse_on_wrapping_line/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_on_wrapping_line/selection is empty to avoid perturbing future edits')
    [9.19075]
    [9.19271]
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_on_wrapping_line/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_on_wrapping_line/selection is empty to avoid perturbing future edits')
  • replacement in source_text_tests.lua at line 418
    [9.19276][9.19276:19435]()
    function test_click_with_mouse_on_wrapping_line_takes_margins_into_account()
    io.write('\ntest_click_with_mouse_on_wrapping_line_takes_margins_into_account')
    [9.19276]
    [9.19435]
    function test_click_on_wrapping_line_takes_margins_into_account()
    io.write('\ntest_click_on_wrapping_line_takes_margins_into_account')
  • replacement in source_text_tests.lua at line 433
    [9.20011][9.20011:20261](),[9.20261][9.562:729]()
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/selection is empty to avoid perturbing future edits')
    [9.20011]
    [9.20261]
    check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line_takes_margins_into_account/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_on_wrapping_line_takes_margins_into_account/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_click_on_wrapping_line_takes_margins_into_account/selection is empty to avoid perturbing future edits')
  • replacement in source_text_tests.lua at line 609
    [9.1269][9.1269:1366]()
    edit.key_released(Editor_state, 'lshift')
    -- selection persists even after shift is released
    [9.1269]
    [9.1366]
    edit.key_release(Editor_state, 'lshift')
    -- selection persists even after shift is release
  • replacement in source_text_tests.lua at line 649
    [9.3062][9.3062:3108]()
    edit.run_after_textinput(Editor_state, 'x')
    [9.3062]
    [9.3108]
    edit.run_after_text_input(Editor_state, 'x')
  • replacement in source_text_tests.lua at line 668
    [9.3846][9.3846:3969]()
    edit.keychord_pressed(Editor_state, 'd', 'd')
    edit.textinput(Editor_state, 'D')
    edit.key_released(Editor_state, 'd')
    [9.3846]
    [9.3969]
    edit.keychord_press(Editor_state, 'd', 'd')
    edit.text_input(Editor_state, 'D')
    edit.key_release(Editor_state, 'd')
  • replacement in source_text_tests.lua at line 772
    [9.28312][9.28312:28358]()
    edit.run_after_textinput(Editor_state, 'g')
    [9.28312]
    [9.28358]
    edit.run_after_text_input(Editor_state, 'g')
  • edit in source_text_tests.lua at line 858
    [9.32456][9.32456:32839](),[9.32839][9.7717:7748](),[9.7748][9.32839:32934](),[9.32839][9.32839:32934](),[9.32934][9.7749:7837](),[9.7837][9.33020:33196](),[9.33020][9.33020:33196](),[9.33196][9.7838:8022]()
    end
    function test_move_cursor_using_mouse()
    io.write('\ntest_move_cursor_using_mouse')
    App.screen.init{width=50, height=60}
    Editor_state = edit.initialize_test_state()
    Editor_state.lines = load_array{'abc', 'def', 'xyz'}
    Text.redraw_all(Editor_state)
    Editor_state.cursor1 = {line=1, pos=1}
    Editor_state.screen_top1 = {line=1, pos=1}
    Editor_state.screen_bottom1 = {}
    Editor_state.selection1 = {}
    edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
    edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
    check_eq(Editor_state.cursor1.line, 1, 'F - test_move_cursor_using_mouse/cursor:line')
    check_eq(Editor_state.cursor1.pos, 2, 'F - test_move_cursor_using_mouse/cursor:pos')
    check_nil(Editor_state.selection1.line, 'F - test_move_cursor_using_mouse/selection:line')
    check_nil(Editor_state.selection1.pos, 'F - test_move_cursor_using_mouse/selection:pos')
  • replacement in source_text_tests.lua at line 1175
    [9.43698][9.43698:43853]()
    function test_page_down_followed_by_down_arrow_does_not_scroll_screen_up()
    io.write('\ntest_page_down_followed_by_down_arrow_does_not_scroll_screen_up')
    [9.43698]
    [9.43853]
    function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()
    io.write('\ntest_pagedown_followed_by_down_arrow_does_not_scroll_screen_up')
  • replacement in source_text_tests.lua at line 1186
    [9.44229][9.44229:44347]()
    App.screen.check(y, 'abc', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:1')
    [9.44229]
    [9.44347]
    App.screen.check(y, 'abc', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:1')
  • replacement in source_text_tests.lua at line 1188
    [9.44382][9.44382:44500]()
    App.screen.check(y, 'def', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:2')
    [9.44382]
    [9.44500]
    App.screen.check(y, 'def', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:2')
  • replacement in source_text_tests.lua at line 1190
    [9.44535][9.44535:44654]()
    App.screen.check(y, 'ghij', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:3')
    [9.44535]
    [9.44654]
    App.screen.check(y, 'ghij', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:3')
  • replacement in source_text_tests.lua at line 1193
    [9.44782][9.44782:45185]()
    check_eq(Editor_state.screen_top1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:pos')
    [9.44782]
    [9.45185]
    check_eq(Editor_state.screen_top1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line')
    check_eq(Editor_state.cursor1.pos, 1, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:pos')
  • replacement in source_text_tests.lua at line 1198
    [9.45335][9.45335:45708]()
    check_eq(Editor_state.screen_top1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos')
    [9.45335]
    [9.45708]
    check_eq(Editor_state.screen_top1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen_top')
    check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line')
    check_eq(Editor_state.cursor1.pos, 5, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos')
  • replacement in source_text_tests.lua at line 1202
    [9.45731][9.45731:45841]()
    App.screen.check(y, 'ghij', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:1')
    [9.45731]
    [9.45841]
    App.screen.check(y, 'ghij', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:1')
  • replacement in source_text_tests.lua at line 1204
    [9.45876][9.45876:45984]()
    App.screen.check(y, 'kl', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:2')
    [9.45876]
    [9.45984]
    App.screen.check(y, 'kl', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:2')
  • replacement in source_text_tests.lua at line 1206
    [9.46019][9.46019:46128]()
    App.screen.check(y, 'mno', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:3')
    [9.46019]
    [9.46128]
    App.screen.check(y, 'mno', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:3')
  • replacement in source_text_tests.lua at line 1506
    [9.62258][9.62258:62304]()
    edit.run_after_textinput(Editor_state, 'a')
    [9.62258]
    [9.62304]
    edit.run_after_text_input(Editor_state, 'a')
  • replacement in source_text_tests.lua at line 1532
    [9.63817][9.63817:63955]()
    edit.run_after_textinput(Editor_state, 'j')
    edit.run_after_textinput(Editor_state, 'k')
    edit.run_after_textinput(Editor_state, 'l')
    [9.63817]
    [9.63955]
    edit.run_after_text_input(Editor_state, 'j')
    edit.run_after_text_input(Editor_state, 'k')
    edit.run_after_text_input(Editor_state, 'l')
  • replacement in source_text_tests.lua at line 1686
    [9.72557][9.72557:72695]()
    edit.run_after_textinput(Editor_state, 's')
    edit.run_after_textinput(Editor_state, 't')
    edit.run_after_textinput(Editor_state, 'u')
    [9.72557]
    [9.72695]
    edit.run_after_text_input(Editor_state, 's')
    edit.run_after_text_input(Editor_state, 't')
    edit.run_after_text_input(Editor_state, 'u')
  • replacement in source_text_tests.lua at line 1886
    [9.77761][9.77761:77807]()
    edit.run_after_textinput(Editor_state, 'g')
    [9.77761]
    [9.77807]
    edit.run_after_text_input(Editor_state, 'g')
  • replacement in source_text_tests.lua at line 1962
    [9.20227][9.20227:20273]()
    edit.run_after_textinput(Editor_state, 'x')
    [9.20227]
    [9.20273]
    edit.run_after_text_input(Editor_state, 'x')
  • replacement in source_text_tests.lua at line 1985
    [9.80804][9.80804:80850]()
    edit.run_after_textinput(Editor_state, 'd')
    [9.80804]
    [9.80850]
    edit.run_after_text_input(Editor_state, 'd')
  • replacement in source_text_tests.lua at line 1994
    [9.81231][9.81231:81278]()
    edit.run_after_textinput(Editor_state, 'de')
    [9.81231]
    [9.81278]
    edit.run_after_text_input(Editor_state, 'de')
  • replacement in source_text_tests.lua at line 2013
    [9.81976][9.81976:82022]()
    edit.run_after_textinput(Editor_state, 'a')
    [9.81976]
    [9.82022]
    edit.run_after_text_input(Editor_state, 'a')
  • replacement in source_text_tests.lua at line 2032
    [9.82710][9.82710:82756]()
    edit.run_after_textinput(Editor_state, 'a')
    [9.82710]
    [9.82756]
    edit.run_after_text_input(Editor_state, 'a')
  • replacement in source_text_tests.lua at line 2051
    [9.83452][9.83452:83498]()
    edit.run_after_textinput(Editor_state, 'a')
    [9.83452]
    [9.83498]
    edit.run_after_text_input(Editor_state, 'a')
  • replacement in source_text.lua at line 356
    [9.97167][9.97167:97201]()
    function Text.textinput(State, t)
    [9.97167]
    [9.97201]
    function Text.text_input(State, t)
  • replacement in source_text.lua at line 384
    [9.98728][9.98728:98844]()
    -- Don't handle any keys here that would trigger love.textinput above.
    function Text.keychord_pressed(State, chord)
    [9.98728]
    [9.21139]
    -- Don't handle any keys here that would trigger text_input above.
    function Text.keychord_press(State, chord)
  • replacement in source_edit.lua at line 211
    [9.157121][9.157121:157175]()
    function edit.mouse_pressed(State, x,y, mouse_button)
    [9.157121]
    [9.157175]
    function edit.mouse_press(State, x,y, mouse_button)
  • replacement in source_edit.lua at line 230
    [9.24027][9.24027:24094]()
    -- i.e. mouse_released should never look at shift state
    [9.24027]
    [9.24094]
    -- i.e. mouse_release should never look at shift state
  • replacement in source_edit.lua at line 246
    [9.20742][9.20742:20810]()
    Drawing.mouse_pressed(State, line_index, x,y, mouse_button)
    [9.20742]
    [9.20810]
    Drawing.mouse_press(State, line_index, x,y, mouse_button)
  • replacement in source_edit.lua at line 253
    [9.157761][9.157761:157816]()
    function edit.mouse_released(State, x,y, mouse_button)
    [9.157761]
    [9.20835]
    function edit.mouse_release(State, x,y, mouse_button)
  • replacement in source_edit.lua at line 257
    [9.20935][9.20935:20988]()
    Drawing.mouse_released(State, x,y, mouse_button)
    [9.20935]
    [9.20988]
    Drawing.mouse_release(State, x,y, mouse_button)
  • replacement in source_edit.lua at line 290
    [9.157821][9.157821:157855]()
    function edit.textinput(State, t)
    [9.157821]
    [9.157960]
    function edit.text_input(State, t)
  • replacement in source_edit.lua at line 305
    [4.219][4.219:250]()
    Text.textinput(State, t)
    [4.219]
    [4.250]
    Text.text_input(State, t)
  • replacement in source_edit.lua at line 311
    [9.158159][9.158159:158209]()
    function edit.keychord_pressed(State, chord, key)
    [9.158159]
    [9.25356]
    function edit.keychord_press(State, chord, key)
  • replacement in source_edit.lua at line 464
    [9.22039][9.22039:22084]()
    Drawing.keychord_pressed(State, chord)
    [9.22039]
    [9.22084]
    Drawing.keychord_press(State, chord)
  • replacement in source_edit.lua at line 496
    [9.163298][9.163298:163338]()
    Text.keychord_pressed(State, chord)
    [9.163298]
    [9.163338]
    Text.keychord_press(State, chord)
  • replacement in source_edit.lua at line 514
    [9.163912][9.163912:163961]()
    function edit.key_released(State, key, scancode)
    [9.163912]
    [9.163961]
    function edit.key_release(State, key, scancode)
  • replacement in source_edit.lua at line 542
    [9.164615][9.164615:164659]()
    -- all textinput events are also keypresses
    [9.164615]
    [9.164659]
    -- all text_input events are also keypresses
  • replacement in source_edit.lua at line 544
    [9.164699][9.164699:164834]()
    function edit.run_after_textinput(State, t)
    edit.keychord_pressed(State, t)
    edit.textinput(State, t)
    edit.key_released(State, t)
    [9.164699]
    [9.164834]
    function edit.run_after_text_input(State, t)
    edit.keychord_press(State, t)
    edit.text_input(State, t)
    edit.key_release(State, t)
  • replacement in source_edit.lua at line 553
    [9.164885][9.164885:164915]()
    -- not all keys are textinput
    [9.164885]
    [9.164915]
    -- not all keys are text_input
  • replacement in source_edit.lua at line 555
    [9.164962][9.164962:165034]()
    edit.keychord_pressed(State, chord)
    edit.key_released(State, chord)
    [9.164962]
    [9.165034]
    edit.keychord_press(State, chord)
    edit.key_release(State, chord)
  • replacement in source_edit.lua at line 564
    [9.165189][9.165189:165236]()
    edit.mouse_pressed(State, x,y, mouse_button)
    [9.165189]
    [9.165236]
    edit.mouse_press(State, x,y, mouse_button)
  • replacement in source_edit.lua at line 566
    [9.165280][9.165280:165328]()
    edit.mouse_released(State, x,y, mouse_button)
    [9.165280]
    [9.165328]
    edit.mouse_release(State, x,y, mouse_button)
  • replacement in source_edit.lua at line 574
    [9.165483][9.165483:165530]()
    edit.mouse_pressed(State, x,y, mouse_button)
    [9.165483]
    [9.165530]
    edit.mouse_press(State, x,y, mouse_button)
  • replacement in source_edit.lua at line 582
    [9.165689][9.165689:165737]()
    edit.mouse_released(State, x,y, mouse_button)
    [9.165689]
    [9.165737]
    edit.mouse_release(State, x,y, mouse_button)
  • replacement in source.lua at line 200
    [9.171997][9.171997:172031]()
    function source.filedropped(file)
    [9.171997]
    [9.172031]
    function source.file_drop(file)
  • replacement in source.lua at line 216
    [9.172439][9.172439:172493]()
    -- a copy of source.filedropped when given a filename
    [9.172439]
    [9.172493]
    -- a copy of source.file_drop when given a filename
  • replacement in source.lua at line 288
    [9.174633][9.174633:174682]()
    function source.mouse_pressed(x,y, mouse_button)
    [9.174633]
    [9.174682]
    function source.mouse_press(x,y, mouse_button)
  • replacement in source.lua at line 295
    [9.2826][9.2826:2882]()
    edit.mouse_pressed(Editor_state, x,y, mouse_button)
    [9.2826]
    [9.2882]
    edit.mouse_press(Editor_state, x,y, mouse_button)
  • replacement in source.lua at line 304
    [9.175054][9.175054:175110]()
    edit.mouse_pressed(Editor_state, x,y, mouse_button)
    [9.175054]
    [9.175110]
    edit.mouse_press(Editor_state, x,y, mouse_button)
  • replacement in source.lua at line 311
    [9.175324][9.175324:175392]()
    log_browser.mouse_pressed(Log_browser_state, x,y, mouse_button)
    [9.175324]
    [9.175392]
    log_browser.mouse_press(Log_browser_state, x,y, mouse_button)
  • replacement in source.lua at line 316
    [9.175517][9.175517:175567]()
    function source.mouse_released(x,y, mouse_button)
    [9.175517]
    [9.175567]
    function source.mouse_release(x,y, mouse_button)
  • replacement in source.lua at line 319
    [9.175667][9.175667:175731]()
    return edit.mouse_released(Editor_state, x,y, mouse_button)
    [9.175667]
    [9.175731]
    return edit.mouse_release(Editor_state, x,y, mouse_button)
  • replacement in source.lua at line 321
    [9.175738][9.175738:175814]()
    return log_browser.mouse_released(Log_browser_state, x,y, mouse_button)
    [9.175738]
    [9.175814]
    return log_browser.mouse_release(Log_browser_state, x,y, mouse_button)
  • replacement in source.lua at line 325
    [9.175825][9.175825:175854]()
    function source.textinput(t)
    [9.175825]
    [9.175854]
    function source.text_input(t)
  • replacement in source.lua at line 328
    [9.661][9.661:696]()
    textinput_on_file_navigator(t)
    [9.661]
    [9.696]
    text_input_on_file_navigator(t)
  • replacement in source.lua at line 332
    [9.175954][9.175954:175997]()
    return edit.textinput(Editor_state, t)
    [9.175954]
    [9.175997]
    return edit.text_input(Editor_state, t)
  • replacement in source.lua at line 334
    [9.176004][9.176004:176059]()
    return log_browser.textinput(Log_browser_state, t)
    [9.176004]
    [9.176059]
    return log_browser.text_input(Log_browser_state, t)
  • replacement in source.lua at line 338
    [9.176070][9.176070:176115]()
    function source.keychord_pressed(chord, key)
    [9.176070]
    [9.176115]
    function source.keychord_press(chord, key)
  • replacement in source.lua at line 342
    [9.176250][9.176250:176301]()
    keychord_pressed_on_file_navigator(chord, key)
    [9.176250]
    [9.176301]
    keychord_press_on_file_navigator(chord, key)
  • replacement in source.lua at line 383
    [9.177101][9.177101:177160]()
    return edit.keychord_pressed(Editor_state, chord, key)
    [9.177101]
    [9.177160]
    return edit.keychord_press(Editor_state, chord, key)
  • replacement in source.lua at line 385
    [9.177167][9.177167:177238]()
    return log_browser.keychord_pressed(Log_browser_state, chord, key)
    [9.177167]
    [9.177238]
    return log_browser.keychord_press(Log_browser_state, chord, key)
  • replacement in source.lua at line 389
    [9.177249][9.177249:177293]()
    function source.key_released(key, scancode)
    [9.177249]
    [9.177293]
    function source.key_release(key, scancode)
  • replacement in source.lua at line 392
    [9.177393][9.177393:177451]()
    return edit.key_released(Editor_state, key, scancode)
    [9.177393]
    [9.177451]
    return edit.key_release(Editor_state, key, scancode)
  • replacement in source.lua at line 394
    [9.177458][9.177458:177537]()
    return log_browser.keychord_pressed(Log_browser_state, chordkey, scancode)
    [9.177458]
    [9.177537]
    return log_browser.keychord_press(Log_browser_state, chordkey, scancode)
  • replacement in run.lua at line 104
    [9.181676][9.181676:181707]()
    function run.filedropped(file)
    [9.181676]
    [9.181707]
    function run.file_drop(file)
  • replacement in run.lua at line 157
    [9.183012][9.183012:183059]()
    function run.mouse_released(x,y, mouse_button)
    [9.183012]
    [9.183059]
    function run.mouse_release(x,y, mouse_button)
  • replacement in run.lua at line 159
    [9.183133][9.183133:183195]()
    return edit.mouse_released(Editor_state, x,y, mouse_button)
    [9.183133]
    [9.183195]
    return edit.mouse_release(Editor_state, x,y, mouse_button)
  • replacement in run.lua at line 162
    [9.183200][9.183200:183226]()
    function run.textinput(t)
    [9.183200]
    [9.183226]
    function run.text_input(t)
  • replacement in run.lua at line 164
    [9.183300][9.183300:183341]()
    return edit.textinput(Editor_state, t)
    [9.183300]
    [9.183341]
    return edit.text_input(Editor_state, t)
  • replacement in run.lua at line 167
    [9.183346][9.183346:183388]()
    function run.keychord_pressed(chord, key)
    [9.183346]
    [9.183388]
    function run.keychord_press(chord, key)
  • replacement in run.lua at line 169
    [9.183462][9.183462:183519]()
    return edit.keychord_pressed(Editor_state, chord, key)
    [9.183462]
    [9.183519]
    return edit.keychord_press(Editor_state, chord, key)
  • replacement in run.lua at line 172
    [9.183524][9.183524:183565]()
    function run.key_released(key, scancode)
    [9.183524]
    [9.183565]
    function run.key_release(key, scancode)
  • replacement in run.lua at line 174
    [9.183639][9.183639:183695]()
    return edit.key_released(Editor_state, key, scancode)
    [9.183639]
    [9.183695]
    return edit.key_release(Editor_state, key, scancode)
  • resurrect zombie in main.lua at line 109
    [9.187732][7.4429:4479](),[9.187732][7.4429:4479](),[9.187824][7.4480:4536](),[9.187824][7.4480:4536]()
    if run.file_drop then run.file_drop(file) end
    if source.file_drop then source.file_drop(file) end
  • edit in main.lua at line 109
    [9.187701]
    [7.4429]
    if Current_app == 'run' then
  • edit in main.lua at line 111
    [7.4479]
    [7.4480]
    elseif Current_app == 'source' then
  • edit in main.lua at line 113
    [9.1352][9.187158:187341]()
    if Current_app == 'run' then
    if run.filedropped then run.filedropped(file) end
    elseif Current_app == 'source' then
    if source.filedropped then source.filedropped(file) end
  • replacement in main.lua at line 157
    [9.8][9.188134:188176]()
    function App.keychord_pressed(chord, key)
    [9.8]
    [9.188176]
    function App.keychord_press(chord, key)
  • edit in main.lua at line 161
    [9.188308][9.467:467](),[9.8][7.4537:4577](),[9.8][7.4537:4577]()
    function App.keychord_press(chord, key)
  • resolve order conflict in main.lua at line 161
    [9.188308]
    [9.188847]
  • resurrect zombie in main.lua at line 219
    [9.190433][7.4885:4951](),[9.190433][7.4885:4951]()
    if source.key_release then source.key_release(chord, key) end
  • edit in main.lua at line 220
    [9.190680][8.454:523](),[9.190680][8.454:523](),[9.190789][8.524:599](),[9.190789][8.524:599]()
    if run.mouse_pressed then run.mouse_press(x,y, mouse_button) end
    if source.mouse_pressed then source.mouse_press(x,y, mouse_button) end
  • replacement in main.lua at line 228
    [9.190137][9.190137:190208]()
    if run.mouse_pressed then run.mouse_pressed(x,y, mouse_button) end
    [9.190137]
    [9.190208]
    if run.mouse_pressed then run.mouse_press(x,y, mouse_button) end
  • edit in main.lua at line 230
    [9.190246][9.190246:190323](),[9.190323][9.62:62](),[9.191010][7.4952:5023](),[9.191010][7.4952:5023](),[9.191121][7.5024:5101](),[9.191121][7.5024:5101]()
    if source.mouse_pressed then source.mouse_pressed(x,y, mouse_button) end
    if run.mouse_release then run.mouse_release(x,y, mouse_button) end
    if source.mouse_release then source.mouse_release(x,y, mouse_button) end
  • resolve order conflict in main.lua at line 230
    [9.190246]
  • edit in main.lua at line 230
    [0.8247]
    [9.191200]
    if source.mouse_pressed then source.mouse_press(x,y, mouse_button) end
  • replacement in main.lua at line 238
    [9.190461][9.190461:190534]()
    if run.mouse_released then run.mouse_released(x,y, mouse_button) end
    [9.190461]
    [9.190534]
    if run.mouse_release then run.mouse_release(x,y, mouse_button) end
  • replacement in main.lua at line 240
    [9.190572][9.190572:190651]()
    if source.mouse_released then source.mouse_released(x,y, mouse_button) end
    [9.190572]
    [9.190651]
    if source.mouse_release then source.mouse_release(x,y, mouse_button) end
  • replacement in log_browser.lua at line 197
    [9.198624][9.198624:198685]()
    function log_browser.mouse_pressed(State, x,y, mouse_button)
    [9.198624]
    [9.198685]
    function log_browser.mouse_press(State, x,y, mouse_button)
  • replacement in log_browser.lua at line 265
    [9.200825][9.200825:200887]()
    function log_browser.mouse_released(State, x,y, mouse_button)
    [9.200825]
    [9.200887]
    function log_browser.mouse_release(State, x,y, mouse_button)
  • replacement in log_browser.lua at line 268
    [9.200892][9.200892:200933]()
    function log_browser.textinput(State, t)
    [9.200892]
    [9.200933]
    function log_browser.text_input(State, t)
  • replacement in log_browser.lua at line 271
    [9.200938][9.200938:200995]()
    function log_browser.keychord_pressed(State, chord, key)
    [9.200938]
    [9.200995]
    function log_browser.keychord_press(State, chord, key)
  • replacement in log_browser.lua at line 322
    [9.202624][9.202624:202679]()
    function log_browser.keyreleased(State, key, scancode)
    [9.202624]
    [9.202679]
    function log_browser.key_release(State, key, scancode)
  • edit in edit.lua at line 150
    [9.7631][7.5383:5449](),[9.7631][7.5383:5449](),[9.2295][8.722:788](),[9.2295][8.722:788]()
    -- i.e. mouse_release should never look at shift state
    Drawing.mouse_press(State, line_index, x,y, mouse_button)
  • resolve order conflict in edit.lua at line 150
    [9.10815]
    [9.8345]
  • edit in edit.lua at line 170
    [9.5377][7.5505:5557](),[9.5377][7.5505:5557]()
    Drawing.mouse_release(State, x,y, mouse_button)
  • resolve order conflict in edit.lua at line 170
    [9.11328]
    [9.9594]
  • edit in edit.lua at line 189
    [3.133][3.133:162](),[9.234][7.5594:5626](),[9.234][7.5594:5626]()
    Text.textinput(State, t)
    Text.text_input(State, t)
  • resolve order conflict in edit.lua at line 189
    [3.133]
  • edit in edit.lua at line 189
    [0.8765]
    [9.10314]
    Text.text_input(State, t)
  • edit in edit.lua at line 308
    [9.2865][7.5676:5719](),[9.2865][7.5676:5719]()
    Drawing.keychord_press(State, chord)
  • resolve order conflict in edit.lua at line 308
    [9.11602]
    [9.15871]
  • replacement in drawing_tests.lua at line 168
    [9.9890][9.9890:9962]()
    -- no change to text either because we didn't run the textinput event
    [9.9890]
    [9.9962]
    -- no change to text either because we didn't run the text_input event
  • replacement in drawing_tests.lua at line 188
    [9.11183][3.164:210]()
    edit.run_after_textinput(Editor_state, 'o')
    [9.11183]
    [9.11228]
    edit.run_after_text_input(Editor_state, 'o')
  • replacement in drawing_tests.lua at line 217
    [9.13005][3.211:270]()
    edit.run_after_textinput(Editor_state, 'a') -- arc mode
    [9.13005]
    [9.13063]
    edit.run_after_text_input(Editor_state, 'a') -- arc mode
  • replacement in drawing_tests.lua at line 248
    [9.14912][3.271:334]()
    edit.run_after_textinput(Editor_state, 'g') -- polygon mode
    [9.14912]
    [9.14974]
    edit.run_after_text_input(Editor_state, 'g') -- polygon mode
  • replacement in drawing_tests.lua at line 251
    [9.15072][3.335:395]()
    edit.run_after_textinput(Editor_state, 'p') -- add point
    [9.15072]
    [9.15131]
    edit.run_after_text_input(Editor_state, 'p') -- add point
  • replacement in drawing_tests.lua at line 287
    [9.17155][3.396:461]()
    edit.run_after_textinput(Editor_state, 'r') -- rectangle mode
    [9.17155]
    [9.17219]
    edit.run_after_text_input(Editor_state, 'r') -- rectangle mode
  • replacement in drawing_tests.lua at line 290
    [9.17328][3.462:508]()
    edit.run_after_textinput(Editor_state, 'p')
    [9.17328]
    [9.17373]
    edit.run_after_text_input(Editor_state, 'p')
  • replacement in drawing_tests.lua at line 293
    [9.17491][3.509:555]()
    edit.run_after_textinput(Editor_state, 'p')
    [9.17491]
    [9.17536]
    edit.run_after_text_input(Editor_state, 'p')
  • replacement in drawing_tests.lua at line 332
    [9.19937][3.556:621]()
    edit.run_after_textinput(Editor_state, 'r') -- rectangle mode
    [9.19937]
    [9.20001]
    edit.run_after_text_input(Editor_state, 'r') -- rectangle mode
  • replacement in drawing_tests.lua at line 335
    [9.20110][3.622:668]()
    edit.run_after_textinput(Editor_state, 'p')
    [9.20110]
    [9.20155]
    edit.run_after_text_input(Editor_state, 'p')
  • replacement in drawing_tests.lua at line 338
    [9.20273][3.669:715]()
    edit.run_after_textinput(Editor_state, 'p')
    [9.20273]
    [9.20318]
    edit.run_after_text_input(Editor_state, 'p')
  • replacement in drawing_tests.lua at line 369
    [9.22232][3.716:778]()
    edit.run_after_textinput(Editor_state, 's') -- square mode
    [9.22232]
    [9.22293]
    edit.run_after_text_input(Editor_state, 's') -- square mode
  • replacement in drawing_tests.lua at line 372
    [9.22402][3.779:825]()
    edit.run_after_textinput(Editor_state, 'p')
    [9.22402]
    [9.22447]
    edit.run_after_text_input(Editor_state, 'p')
  • replacement in drawing_tests.lua at line 375
    [9.22565][3.826:872]()
    edit.run_after_textinput(Editor_state, 'p')
    [9.22565]
    [9.22610]
    edit.run_after_text_input(Editor_state, 'p')
  • replacement in drawing_tests.lua at line 424
    [9.25277][9.25277:25323]()
    edit.run_after_textinput(Editor_state, 'A')
    [9.25277]
    [9.25323]
    edit.run_after_text_input(Editor_state, 'A')
  • edit in drawing_tests.lua at line 433
    [9.25706][3.874:874](),[9.110665][7.6188:6261](),[9.110665][7.6188:6261](),[9.2495][7.6262:6309](),[9.2495][7.6262:6309](),[9.2910][7.6310:6370](),[9.2910][7.6310:6370](),[9.3256][7.6371:6435](),[9.3256][7.6371:6435](),[9.3337][7.6436:6497](),[9.3337][7.6436:6497](),[9.3672][7.6498:6564](),[9.3672][7.6498:6564](),[9.3753][7.6565:6612](),[9.3753][7.6565:6612](),[9.3834][7.6613:6660](),[9.3834][7.6613:6660](),[9.4182][7.6661:6727](),[9.4182][7.6661:6727](),[9.4263][7.6728:6775](),[9.4263][7.6728:6775](),[9.4344][7.6776:6823](),[9.4344][7.6776:6823](),[9.4564][7.6824:6887](),[9.4564][7.6824:6887](),[9.4645][7.6888:6935](),[9.4645][7.6888:6935](),[9.4726][7.6936:6983](),[9.4726][7.6936:6983](),[9.118248][7.6984:7031](),[9.118248][7.6984:7031]()
    -- no change to text either because we didn't run the text_input event
    edit.run_after_text_input(Editor_state, 'o')
    edit.run_after_text_input(Editor_state, 'a') -- arc mode
    edit.run_after_text_input(Editor_state, 'g') -- polygon mode
    edit.run_after_text_input(Editor_state, 'p') -- add point
    edit.run_after_text_input(Editor_state, 'r') -- rectangle mode
    edit.run_after_text_input(Editor_state, 'p')
    edit.run_after_text_input(Editor_state, 'p')
    edit.run_after_text_input(Editor_state, 'r') -- rectangle mode
    edit.run_after_text_input(Editor_state, 'p')
    edit.run_after_text_input(Editor_state, 'p')
    edit.run_after_text_input(Editor_state, 's') -- square mode
    edit.run_after_text_input(Editor_state, 'p')
    edit.run_after_text_input(Editor_state, 'p')
    edit.run_after_text_input(Editor_state, 'A')
  • resolve order conflict in drawing_tests.lua at line 433
    [9.25706]
    [9.971]
  • replacement in drawing_tests.lua at line 597
    [9.34297][3.875:938]()
    edit.run_after_textinput(Editor_state, 'g') -- polygon mode
    [9.34297]
    [9.34359]
    edit.run_after_text_input(Editor_state, 'g') -- polygon mode
  • replacement in drawing_tests.lua at line 600
    [9.34457][3.939:999]()
    edit.run_after_textinput(Editor_state, 'p') -- add point
    [9.34457]
    [9.34516]
    edit.run_after_text_input(Editor_state, 'p') -- add point
  • replacement in drawing_tests.lua at line 603
    [9.34613][3.1000:1060]()
    edit.run_after_textinput(Editor_state, 'p') -- add point
    [9.34613]
    [9.34672]
    edit.run_after_text_input(Editor_state, 'p') -- add point
  • replacement in drawing_tests.lua at line 629
    [9.36079][3.1061:1124]()
    edit.run_after_textinput(Editor_state, 'g') -- polygon mode
    [9.36079]
    [9.36141]
    edit.run_after_text_input(Editor_state, 'g') -- polygon mode
  • replacement in drawing_tests.lua at line 632
    [9.36239][3.1125:1185]()
    edit.run_after_textinput(Editor_state, 'p') -- add point
    [9.36239]
    [9.36298]
    edit.run_after_text_input(Editor_state, 'p') -- add point
  • replacement in drawing_tests.lua at line 674
    [9.38662][9.38662:38708]()
    edit.run_after_textinput(Editor_state, 'A')
    [9.38662]
    [9.38708]
    edit.run_after_text_input(Editor_state, 'A')
  • edit in drawing_tests.lua at line 687
    [9.39401][3.1187:1187](),[9.6915][7.7032:7096](),[9.6915][7.7032:7096](),[9.6996][7.7097:7158](),[9.6996][7.7097:7158](),[9.7077][7.7159:7220](),[9.7077][7.7159:7220](),[9.7378][7.7221:7285](),[9.7378][7.7221:7285](),[9.7459][7.7286:7347](),[9.7459][7.7286:7347](),[9.14484][7.7348:7395](),[9.14484][7.7348:7395]()
    edit.run_after_text_input(Editor_state, 'g') -- polygon mode
    edit.run_after_text_input(Editor_state, 'p') -- add point
    edit.run_after_text_input(Editor_state, 'p') -- add point
    edit.run_after_text_input(Editor_state, 'g') -- polygon mode
    edit.run_after_text_input(Editor_state, 'p') -- add point
    edit.run_after_text_input(Editor_state, 'A')
  • resolve order conflict in drawing_tests.lua at line 687
    [9.39401]
    [9.1119]
  • file un-deletion: drawing.lua (----------)drawing.lua (----------)
    [9.1577][9.98:98](),[9.2][9.1542:1577](),[9.2][9.1542:1577]()
  • resurrect zombie in drawing.lua at line 1
    [9.95][7.7397:7422](),[9.95][7.7397:7422](),[9.800][8.883:953](),[9.800][8.883:953](),[9.1210][7.7423:7465](),[9.1210][7.7423:7465](),[9.1875][7.7466:7523](),[9.1875][7.7466:7523](),[9.19077][7.7524:7570](),[9.19077][7.7524:7570]()
    -- after mouse_release
    function Drawing.mouse_press(State, drawing_index, x,y, mouse_button)
    -- all the action is in mouse_release
    function Drawing.mouse_release(State, x,y, mouse_button)
    function Drawing.keychord_press(State, chord)
  • edit in drawing.lua at line 1
    [9.98]
    [7.7397]
    -- primitives for editing drawings
    Drawing = {}
    require 'drawing_tests'
    -- All drawings span 100% of some conceptual 'page width' and divide it up
    -- into 256 parts.
    function Drawing.draw(State, line_index, y)
    local line = State.lines[line_index]
    local line_cache = State.line_cache[line_index]
    line_cache.starty = y
    local pmx,pmy = App.mouse_x(), App.mouse_y()
    if pmx < State.right and pmy > line_cache.starty and pmy < line_cache.starty+Drawing.pixels(line.h, State.width) then
    App.color(Icon_color)
    love.graphics.rectangle('line', State.left,line_cache.starty, State.width,Drawing.pixels(line.h, State.width))
    if icon[State.current_drawing_mode] then
    icon[State.current_drawing_mode](State.right-22, line_cache.starty+4)
    else
    icon[State.previous_drawing_mode](State.right-22, line_cache.starty+4)
    end
    if App.mouse_down(1) and love.keyboard.isDown('h') then
    draw_help_with_mouse_pressed(State, line_index)
    return
    end
    end
    if line.show_help then
    draw_help_without_mouse_pressed(State, line_index)
    return
    end
    local mx = Drawing.coord(pmx-State.left, State.width)
    local my = Drawing.coord(pmy-line_cache.starty, State.width)
    for _,shape in ipairs(line.shapes) do
    assert(shape)
    if geom.on_shape(mx,my, line, shape) then
    App.color(Focus_stroke_color)
    else
    App.color(Stroke_color)
    end
    Drawing.draw_shape(line, shape, line_cache.starty, State.left,State.right)
    end
    local function px(x) return Drawing.pixels(x, State.width)+State.left end
    local function py(y) return Drawing.pixels(y, State.width)+line_cache.starty end
    for i,p in ipairs(line.points) do
    if p.deleted == nil then
    if Drawing.near(p, mx,my, State.width) then
    App.color(Focus_stroke_color)
    love.graphics.circle('line', px(p.x),py(p.y), Same_point_distance)
    else
    App.color(Stroke_color)
    love.graphics.circle('fill', px(p.x),py(p.y), 2)
    end
    if p.name then
    -- TODO: clip
    local x,y = px(p.x)+5, py(p.y)+5
    love.graphics.print(p.name, x,y)
    if State.current_drawing_mode == 'name' and i == line.pending.target_point then
    -- create a faint red box for the name
    App.color(Current_name_background_color)
    local name_text
    -- TODO: avoid computing name width on every repaint
    if p.name == '' then
    name_text = State.em
    else
    name_text = App.newText(love.graphics.getFont(), p.name)
    end
    love.graphics.rectangle('fill', x,y, App.width(name_text), State.line_height)
    end
    end
    end
    end
    App.color(Current_stroke_color)
    Drawing.draw_pending_shape(line, line_cache.starty, State.left,State.right)
    end
    function Drawing.draw_shape(drawing, shape, top, left,right)
    local width = right-left
    local function px(x) return Drawing.pixels(x, width)+left end
    local function py(y) return Drawing.pixels(y, width)+top end
    if shape.mode == 'freehand' then
    local prev = nil
    for _,point in ipairs(shape.points) do
    if prev then
    love.graphics.line(px(prev.x),py(prev.y), px(point.x),py(point.y))
    end
    prev = point
    end
    elseif shape.mode == 'line' or shape.mode == 'manhattan' then
    local p1 = drawing.points[shape.p1]
    local p2 = drawing.points[shape.p2]
    love.graphics.line(px(p1.x),py(p1.y), px(p2.x),py(p2.y))
    elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
    local prev = nil
    for _,point in ipairs(shape.vertices) do
    local curr = drawing.points[point]
    if prev then
    love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))
    end
    prev = curr
    end
    -- close the loop
    local curr = drawing.points[shape.vertices[1]]
    love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))
    elseif shape.mode == 'circle' then
    -- TODO: clip
    local center = drawing.points[shape.center]
    love.graphics.circle('line', px(center.x),py(center.y), Drawing.pixels(shape.radius, width))
    elseif shape.mode == 'arc' then
    local center = drawing.points[shape.center]
    love.graphics.arc('line', 'open', px(center.x),py(center.y), Drawing.pixels(shape.radius, width), shape.start_angle, shape.end_angle, 360)
    elseif shape.mode == 'deleted' then
    -- ignore
    else
    print(shape.mode)
    assert(false)
    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
  • edit in drawing.lua at line 131
    [7.7422]
    [8.883]
    pmx,pmy = px(mx), py(my)
    local shape = drawing.pending
    if shape.mode == nil then
    -- nothing pending
    elseif shape.mode == 'freehand' then
    local shape_copy = deepcopy(shape)
    Drawing.smoothen(shape_copy)
    Drawing.draw_shape(drawing, shape_copy, top, left,right)
    elseif shape.mode == 'line' then
    if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
    return
    end
    local p1 = drawing.points[shape.p1]
    love.graphics.line(px(p1.x),py(p1.y), pmx,pmy)
    elseif shape.mode == 'manhattan' then
    if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
    return
    end
    local p1 = drawing.points[shape.p1]
    if math.abs(mx-p1.x) > math.abs(my-p1.y) then
    love.graphics.line(px(p1.x),py(p1.y), pmx, py(p1.y))
    else
    love.graphics.line(px(p1.x),py(p1.y), px(p1.x),pmy)
    end
    elseif shape.mode == 'polygon' then
    -- don't close the loop on a pending polygon
    local prev = nil
    for _,point in ipairs(shape.vertices) do
    local curr = drawing.points[point]
    if prev then
    love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))
    end
    prev = curr
    end
    love.graphics.line(px(prev.x),py(prev.y), pmx,pmy)
    elseif shape.mode == 'rectangle' then
    local first = drawing.points[shape.vertices[1]]
    if #shape.vertices == 1 then
    love.graphics.line(px(first.x),py(first.y), pmx,pmy)
    return
    end
    local second = drawing.points[shape.vertices[2]]
    local thirdx,thirdy, fourthx,fourthy = Drawing.complete_rectangle(first.x,first.y, second.x,second.y, mx,my)
    love.graphics.line(px(first.x),py(first.y), px(second.x),py(second.y))
    love.graphics.line(px(second.x),py(second.y), px(thirdx),py(thirdy))
    love.graphics.line(px(thirdx),py(thirdy), px(fourthx),py(fourthy))
    love.graphics.line(px(fourthx),py(fourthy), px(first.x),py(first.y))
    elseif shape.mode == 'square' then
    local first = drawing.points[shape.vertices[1]]
    if #shape.vertices == 1 then
    love.graphics.line(px(first.x),py(first.y), pmx,pmy)
    return
    end
    local second = drawing.points[shape.vertices[2]]
    local thirdx,thirdy, fourthx,fourthy = Drawing.complete_square(first.x,first.y, second.x,second.y, mx,my)
    love.graphics.line(px(first.x),py(first.y), px(second.x),py(second.y))
    love.graphics.line(px(second.x),py(second.y), px(thirdx),py(thirdy))
    love.graphics.line(px(thirdx),py(thirdy), px(fourthx),py(fourthy))
    love.graphics.line(px(fourthx),py(fourthy), px(first.x),py(first.y))
    elseif shape.mode == 'circle' then
    local center = drawing.points[shape.center]
    if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
    return
    end
    local cx,cy = px(center.x), py(center.y)
    love.graphics.circle('line', cx,cy, geom.dist(cx,cy, App.mouse_x(),App.mouse_y()))
    elseif shape.mode == 'arc' then
    local center = drawing.points[shape.center]
    if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h then
    return
    end
    shape.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, shape.end_angle)
    local cx,cy = px(center.x), py(center.y)
    love.graphics.arc('line', 'open', cx,cy, Drawing.pixels(shape.radius, width), shape.start_angle, shape.end_angle, 360)
    elseif shape.mode == 'move' then
    -- nothing pending; changes are immediately committed
    elseif shape.mode == 'name' then
    -- nothing pending; changes are immediately committed
    else
    print(shape.mode)
    assert(false)
    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
  • edit in drawing.lua at line 222
    [8.953]
    [7.7423]
    local drawing = State.lines[drawing_index]
    local line_cache = State.line_cache[drawing_index]
    local cx = Drawing.coord(x-State.left, State.width)
    local cy = Drawing.coord(y-line_cache.starty, State.width)
    if State.current_drawing_mode == 'freehand' then
    drawing.pending = {mode=State.current_drawing_mode, points={{x=cx, y=cy}}}
    elseif State.current_drawing_mode == 'line' or State.current_drawing_mode == 'manhattan' then
    local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)
    drawing.pending = {mode=State.current_drawing_mode, p1=j}
    elseif State.current_drawing_mode == 'polygon' or State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square' then
    local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)
    drawing.pending = {mode=State.current_drawing_mode, vertices={j}}
    elseif State.current_drawing_mode == 'circle' then
    local j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)
    drawing.pending = {mode=State.current_drawing_mode, center=j}
    elseif State.current_drawing_mode == 'move' then
  • edit in drawing.lua at line 239
    [7.7465]
    [7.7466]
    elseif State.current_drawing_mode == 'name' then
    -- nothing
    else
    print(State.current_drawing_mode)
    assert(false)
    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]
    assert(drawing.mode == 'drawing')
    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
  • edit in drawing.lua at line 296
    [7.7523]
    [7.7524]
    if State.current_drawing_mode == 'move' then
    State.current_drawing_mode = State.previous_drawing_mode
    State.previous_drawing_mode = nil
    if State.lines.current_drawing then
    State.lines.current_drawing.pending = {}
    State.lines.current_drawing = nil
    end
    elseif State.lines.current_drawing then
    local drawing = State.lines.current_drawing
    local line_cache = State.line_cache[State.lines.current_drawing_index]
    if drawing.pending then
    if drawing.pending.mode == nil then
    -- nothing pending
    elseif drawing.pending.mode == 'freehand' then
    -- the last point added during update is good enough
    Drawing.smoothen(drawing.pending)
    table.insert(drawing.shapes, drawing.pending)
    elseif drawing.pending.mode == 'line' 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
    drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
    table.insert(drawing.shapes, drawing.pending)
    end
    elseif drawing.pending.mode == 'manhattan' then
    local p1 = drawing.points[drawing.pending.p1]
    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
    if math.abs(mx-p1.x) > math.abs(my-p1.y) then
    drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx, p1.y, State.width)
    else
    drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, p1.x, my, State.width)
    end
    local p2 = drawing.points[drawing.pending.p2]
    App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), line_cache.starty+Drawing.pixels(p2.y, State.width))
    table.insert(drawing.shapes, drawing.pending)
    end
    elseif drawing.pending.mode == 'polygon' 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
    table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, mx,my, State.width))
    table.insert(drawing.shapes, drawing.pending)
    end
    elseif drawing.pending.mode == 'rectangle' then
    assert(#drawing.pending.vertices <= 2)
    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
    assert(#drawing.pending.vertices <= 2)
    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
    print(drawing.pending.mode)
    assert(false)
    end
    State.lines.current_drawing.pending = {}
    State.lines.current_drawing = nil
    end
    end
    end
  • edit in drawing.lua at line 393
    [7.7570]
    if chord == 'C-p' and not App.mouse_down(1) then
    State.current_drawing_mode = 'freehand'
    elseif App.mouse_down(1) and chord == 'l' then
    State.current_drawing_mode = 'line'
    local _,drawing = Drawing.current_drawing(State)
    if drawing.pending.mode == 'freehand' then
    drawing.pending.p1 = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)
    elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
    drawing.pending.p1 = drawing.pending.vertices[1]
    elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
    drawing.pending.p1 = drawing.pending.center
    end
    drawing.pending.mode = 'line'
    elseif chord == 'C-l' and not App.mouse_down(1) then
    State.current_drawing_mode = 'line'
    elseif App.mouse_down(1) and chord == 'm' then
    State.current_drawing_mode = 'manhattan'
    local drawing = Drawing.select_drawing_at_mouse(State)
    if drawing.pending.mode == 'freehand' then
    drawing.pending.p1 = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)
    elseif drawing.pending.mode == 'line' then
    -- do nothing
    elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
    drawing.pending.p1 = drawing.pending.vertices[1]
    elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
    drawing.pending.p1 = drawing.pending.center
    end
    drawing.pending.mode = 'manhattan'
    elseif chord == 'C-m' and not App.mouse_down(1) then
    State.current_drawing_mode = 'manhattan'
    elseif chord == 'C-g' and not App.mouse_down(1) then
    State.current_drawing_mode = 'polygon'
    elseif App.mouse_down(1) and chord == 'g' then
    State.current_drawing_mode = 'polygon'
    local _,drawing = Drawing.current_drawing(State)
    if drawing.pending.mode == 'freehand' then
    drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}
    elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
    if drawing.pending.vertices == nil then
    drawing.pending.vertices = {drawing.pending.p1}
    end
    elseif drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
    -- reuse existing vertices
    elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
    drawing.pending.vertices = {drawing.pending.center}
    end
    drawing.pending.mode = 'polygon'
    elseif chord == 'C-r' and not App.mouse_down(1) then
    State.current_drawing_mode = 'rectangle'
    elseif App.mouse_down(1) and chord == 'r' then
    State.current_drawing_mode = 'rectangle'
    local _,drawing = Drawing.current_drawing(State)
    if drawing.pending.mode == 'freehand' then
    drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}
    elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
    if drawing.pending.vertices == nil then
    drawing.pending.vertices = {drawing.pending.p1}
    end
    elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'square' then
    -- reuse existing (1-2) vertices
    elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
    drawing.pending.vertices = {drawing.pending.center}
    end
    drawing.pending.mode = 'rectangle'
    elseif chord == 'C-s' and not App.mouse_down(1) then
    State.current_drawing_mode = 'square'
    elseif App.mouse_down(1) and chord == 's' then
    State.current_drawing_mode = 'square'
    local _,drawing = Drawing.current_drawing(State)
    if drawing.pending.mode == 'freehand' then
    drawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}
    elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
    if drawing.pending.vertices == nil then
    drawing.pending.vertices = {drawing.pending.p1}
    end
    elseif drawing.pending.mode == 'polygon' then
    while #drawing.pending.vertices > 2 do
    table.remove(drawing.pending.vertices)
    end
    elseif drawing.pending.mode == 'rectangle' then
    -- reuse existing (1-2) vertices
    elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' then
    drawing.pending.vertices = {drawing.pending.center}
    end
    drawing.pending.mode = 'square'
    elseif App.mouse_down(1) and chord == 'p' and State.current_drawing_mode == 'polygon' then
    local _,drawing,line_cache = Drawing.current_drawing(State)
    local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
    local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
    table.insert(drawing.pending.vertices, j)
    elseif App.mouse_down(1) and chord == 'p' and (State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square') then
    local _,drawing,line_cache = Drawing.current_drawing(State)
    local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
    local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
    while #drawing.pending.vertices >= 2 do
    table.remove(drawing.pending.vertices)
    end
    table.insert(drawing.pending.vertices, j)
    elseif chord == 'C-o' and not App.mouse_down(1) then
    State.current_drawing_mode = 'circle'
    elseif App.mouse_down(1) and chord == 'a' and State.current_drawing_mode == 'circle' then
    local _,drawing,line_cache = Drawing.current_drawing(State)
    drawing.pending.mode = 'arc'
    local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)
    local center = drawing.points[drawing.pending.center]
    drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))
    drawing.pending.start_angle = geom.angle(center.x,center.y, mx,my)
    elseif App.mouse_down(1) and chord == 'o' then
    State.current_drawing_mode = 'circle'
    local _,drawing = Drawing.current_drawing(State)
    if drawing.pending.mode == 'freehand' then
    drawing.pending.center = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)
    elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' then
    drawing.pending.center = drawing.pending.p1
    elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then
    drawing.pending.center = drawing.pending.vertices[1]
    end
    drawing.pending.mode = 'circle'
    elseif chord == 'C-u' and not App.mouse_down(1) then
    local drawing_index,drawing,line_cache,i,p = Drawing.select_point_at_mouse(State)
    if drawing then
    if State.previous_drawing_mode == nil then
    State.previous_drawing_mode = State.current_drawing_mode
    end
    State.current_drawing_mode = 'move'
    drawing.pending = {mode=State.current_drawing_mode, target_point=p, target_point_index=i}
    State.lines.current_drawing_index = drawing_index
    State.lines.current_drawing = drawing
    end
    elseif chord == 'C-n' and not App.mouse_down(1) then
    local drawing_index,drawing,line_cache,point_index,p = Drawing.select_point_at_mouse(State)
    if drawing then
    if State.previous_drawing_mode == nil then
    -- don't clobber
    State.previous_drawing_mode = State.current_drawing_mode
    end
    State.current_drawing_mode = 'name'
    p.name = ''
    drawing.pending = {mode=State.current_drawing_mode, target_point=point_index}
    State.lines.current_drawing_index = drawing_index
    State.lines.current_drawing = drawing
    end
    elseif chord == 'C-d' and not App.mouse_down(1) then
    local _,drawing,_,i,p = Drawing.select_point_at_mouse(State)
    if drawing then
    for _,shape in ipairs(drawing.shapes) do
    if Drawing.contains_point(shape, i) then
    if shape.mode == 'polygon' then
    local idx = table.find(shape.vertices, i)
    assert(idx)
    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
    assert(shape)
    if geom.on_shape(mx,my, drawing, shape) then
    return drawing,line_cache,i,shape
    end
    end
    end
    end
    end
    end
    function Drawing.select_point_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,point in ipairs(drawing.points) do
    assert(point)
    if Drawing.near(point, mx,my, State.width) then
    return drawing_index,drawing,line_cache,i,point
    end
    end
    end
    end
    end
    end
    function Drawing.select_drawing_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
    return drawing
    end
    end
    end
    end
    function Drawing.contains_point(shape, p)
    if shape.mode == 'freehand' then
    -- not supported
    elseif shape.mode == 'line' or shape.mode == 'manhattan' then
    return shape.p1 == p or shape.p2 == p
    elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
    return table.find(shape.vertices, p)
    elseif shape.mode == 'circle' then
    return shape.center == p
    elseif shape.mode == 'arc' then
    return shape.center == p
    -- ugh, how to support angles
    elseif shape.mode == 'deleted' then
    -- already done
    else
    print(shape.mode)
    assert(false)
    end
    end
    function Drawing.smoothen(shape)
    assert(shape.mode == 'freehand')
    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
  • replacement in commands.lua at line 154
    [9.3524][9.206573:206629](),[9.206573][9.206573:206629]()
    function keychord_pressed_on_file_navigator(chord, key)
    [9.3524]
    [9.3441]
    function keychord_press_on_file_navigator(chord, key)
  • replacement in commands.lua at line 292
    [9.7743][9.7743:7783]()
    function textinput_on_file_navigator(t)
    [9.7743]
    [9.7783]
    function text_input_on_file_navigator(t)