app running again

[?]
Nov 27, 2022, 10:06 PM
LRDM35CEK3OHXOTB7TEFJRL7P6PQWO5ZG3F2BVA7DIDFHBPJQ7KAC

Dependencies

  • [2] YJ6ASFBG yet another fork trying to integrate my live framework with everything else
  • [3] VO2ZVTWK Merge lines.love
  • [4] UH4YWHW5 button framework is at the app level
  • [5] TXDMRA5J bugfix: alt-tab shouldn't emit keypress events
  • [6] PP2IIHL6 stop putting button state in a global
  • [7] VP5KC4XZ Merge lines.love
  • [8] MD3W5IRA new fork: rip out drawing support
  • [9] KMSL74GA support selections in the source editor
  • [10] H3KWPK3G regression: dropping files on the window
  • [11] ZNLTRNNK highlight another global
  • [12] 6LJZN727 handle chords
  • [13] XX7G2FFJ intermingle freehand line drawings with text
  • [14] D2GCFTTT clean up repl functionality
  • [15] HAZLW5K7 Merge text.love
  • [16] 3QNOKBFM beginnings of a test harness
  • [17] 2CTN2IEF Merge lines.love
  • [18] 66X36NZN a little more prose describing manual_tests
  • [19] RSZD5A7G forgot to add json.lua
  • [20] K2X6G75Z start writing some tests for drawings
  • [21] YIQYNVD2 rip out the line-width slider
  • [22] 4YDBYBA4 clean up memory leak experiments
  • [23] BULPIBEG beginnings of a module for the text editor
  • [24] V7LATJC7 bugfix: resize
  • [25] AVTNUQYR basic test-enabled framework
  • [26] OTIBCAUJ love2d scaffold
  • [27] PTDO2SOT add state arg to schedule_save
  • [28] BZRRUIFQ correct location of the line width slider
  • [29] 5F54FYKI Merge lines.love
  • [30] CNCYMM6A make test initializations a little more obvious
  • [31] OL7ZCZWD Merge text.love
  • [32] VHUNJHXB Merge lines.love
  • [33] TVCPXAAU rename
  • [34] LXTTOB33 extract a couple of files
  • [35] 32V6ZHQB Merge lines.love
  • [36] KKMFQDR4 editing source code from within the app
  • [37] ATQO62TF Merge lines.love
  • [38] TLOAPLBJ add a license
  • [39] VXORMHME delete experimental REPL
  • [40] UHB4GARJ left/right margin -> left/right coordinates
  • [41] MUJTM6RE bring back a level of wrapping
  • [42] T4FRZSYL delete an ancient, unused file
  • [43] RPGTBMMM Merge lines.love
  • [44] BPWFKBXT new test: dragging and dropping a file on lines.love
  • [45] 2WGHUWE6 self-documenting 0 Test_right_margin
  • [46] 73OCE2MC after much struggle, a brute-force undo
  • [47] JOPVPUSA editing source code from within the app
  • [48] D4B52CQ2 Merge lines.love
  • [49] LF7BWEG4 group all editor globals
  • [50] RXNR3U5E Merge text.love
  • [51] 2L5MEZV3 experiment: new edit namespace
  • [52] ETXNVRPT Merge lines.love
  • [53] JCXL74WV bring back everything from commit a68647ae22
  • [54] YT5P6TO6 bugfix: save previous file when dropping a new one on
  • [55] BJ5X5O4A let's prevent the text cursor from ever getting on a drawing
  • [56] LNUHQOGH start passing in Editor_state explicitly
  • [57] GUOQRUL7 Merge lines.love
  • [58] 3PSFWAIL Merge lines.love
  • [59] VBU5YHLR Merge lines.love
  • [60] FS2ITYYH record a known issue
  • [61] VHQCNMAR several more modules
  • [62] BLWAYPKV extract a module
  • [63] SDRXK4X5 move
  • [64] 36Z442IV back to commit 8123959e52f without code editing
  • [65] JCSLDGAH beginnings of support for multiple shapes
  • [66] R5QXEHUI somebody stop me
  • [67] SCOXD4EO Merge lines.love
  • [68] OGUV4HSA remove some memory leaks from rendered fragments
  • [69] CE4LZV4T drop last couple of manual tests
  • [*] KVHUFUFV reorg

Change contents

  • file deletion: main_tests.lua (----------)run_tests.lua (----------)run_tests.lua (----------)
    [3.2][3.1125:1163](),[3.1163][3.7:7](),[3.2][3.178005:178042](),[3.178042][3.7:7](),[3.2][3.178068:178105](),[3.178105][3.7:7]()
    function test_resize_window()
    io.write('\ntest_resize_window')
    check_eq(App.screen.height, 300, 'F - test_resize_window/baseline/height')
    check_eq(Editor_state.left, Test_margin_left, 'F - test_resize_window/baseline/left_margin')
    check_eq(Editor_state.right, 300 - Test_margin_right, 'F - test_resize_window/baseline/left_margin')
    App.resize(200, 400)
    -- ugly; resize switches to real, non-test margins
    check_eq(App.screen.width, 200, 'F - test_resize_window/width')
    check_eq(App.screen.height, 400, 'F - test_resize_window/height')
    check_eq(Editor_state.right, 200-Margin_right, 'F - test_resize_window/right_margin')
    check_eq(Editor_state.width, 200-Margin_left-Margin_right, 'F - test_resize_window/drawing_width')
    check_eq(Editor_state.left, Margin_left, 'F - test_resize_window/left_margin')
    -- TODO: how to make assertions about when App.update got past the early exit?
    end
    function test_drop_file()
    io.write('\ntest_drop_file')
    App.filesystem['foo'] = 'abc\ndef\nghi\n'
    local fake_dropped_file = {
    opened = false,
    getFilename = function(self)
    return 'foo'
    end,
    open = function(self)
    self.opened = true
    end,
    lines = function(self)
    assert(self.opened)
    return App.filesystem['foo']:gmatch('[^\n]+')
    end,
    close = function(self)
    self.opened = false
    end,
    }
    App.filedropped(fake_dropped_file)
    end
    function test_drop_file_saves_previous()
    io.write('\ntest_drop_file_saves_previous')
    -- initially editing a file called foo that hasn't been saved to filesystem yet
    -- now drag a new file bar from the filesystem
    App.filesystem['bar'] = 'abc\ndef\nghi\n'
    local fake_dropped_file = {
    opened = false,
    getFilename = function(self)
    return 'bar'
    end,
    open = function(self)
    self.opened = true
    end,
    lines = function(self)
    assert(self.opened)
    return App.filesystem['bar']:gmatch('[^\n]+')
    end,
    close = function(self)
    self.opened = false
    end,
    }
    App.filedropped(fake_dropped_file)
    -- filesystem now contains a file called foo
    check_eq(App.filesystem['foo'], 'abc\ndef\n', 'F - test_drop_file_saves_previous')
    end
    Editor_state.lines = load_array{'abc', 'def'}
    Editor_state.filename = 'foo'
    schedule_save(Editor_state)
    App.screen.init{width=Editor_state.left+300, height=300}
    check_eq(#Editor_state.lines, 3, 'F - test_drop_file/#lines')
    edit.draw(Editor_state)
    check_eq(Editor_state.lines[1].data, 'abc', 'F - test_drop_file/lines:1')
    check_eq(Editor_state.lines[2].data, 'def', 'F - test_drop_file/lines:2')
    check_eq(Editor_state.lines[3].data, 'ghi', 'F - test_drop_file/lines:3')
    App.screen.init{width=Editor_state.left+300, height=300}
    Editor_state = edit.initialize_test_state()
    Editor_state.filename = 'foo'
    check_eq(App.screen.width, 300, 'F - test_resize_window/baseline/width')
    App.screen.init{width=300, height=300}
    Editor_state = edit.initialize_test_state()
  • edit in main.lua at line 6
    [3.1894]
    [3.18]
    require 'live'
  • edit in main.lua at line 11
    [3.21][3.1932:1953](),[3.1953][3.22:23](),[3.184452][3.22:23]()
    require 'main_tests'
  • edit in main.lua at line 35
    [3.2459]
    [3.2459]
    Editor_state = nil -- not used outside editor tests
  • edit in main.lua at line 39
    [3.2502]
    [3.1319]
    live.initialize(arg)
  • edit in main.lua at line 82
    [3.46]
    [3.5607]
    if on.resize then on.resize(w,h) end
  • replacement in main.lua at line 86
    [3.5643][3.5643:5763](),[3.5763][3.188315:188321](),[3.188315][3.188315:188321](),[3.188321][3.5764:6044]()
    -- 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)
    love.window.setTitle('text.love - '..Editor_state.filename)
    [3.5643]
    [3.183]
    if on.filedropped then on.filedropped() end
  • replacement in main.lua at line 90
    [3.1373][3.6045:6071]()
    edit.draw(Editor_state)
    [3.1373]
    [3.46]
    if on.draw then on.draw() end
  • replacement in main.lua at line 100
    [3.6105][3.6105:6137]()
    edit.update(Editor_state, dt)
    [3.6105]
    [3.6137]
    if on.update then on.update(dt) end
  • replacement in main.lua at line 104
    [3.6163][3.6163:6189]()
    edit.quit(Editor_state)
    [3.6163]
    [2.113]
    if on.quit then on.quit() end
  • replacement in main.lua at line 110
    [3.6894][3.6894:6955]()
    return edit.mouse_pressed(Editor_state, x,y, mouse_button)
    [3.6894]
    [3.6955]
    if on.mouse_pressed then on.mouse_pressed(x,y, mouse_button) end
  • replacement in main.lua at line 115
    [3.7080][3.7080:7142]()
    return edit.mouse_released(Editor_state, x,y, mouse_button)
    [3.7080]
    [3.7142]
    if on.mouse_released then on.mouse_released(x,y, mouse_button) end
  • edit in main.lua at line 122
    [3.188672]
    [71.3]
    if on.focus then on.focus(in_focus) end
  • edit in main.lua at line 125
    [71.8]
    [3.188134]
    -- App.keypressed is defined in keychord.lua
  • replacement in main.lua at line 133
    [3.7306][3.7306:7363]()
    return edit.keychord_pressed(Editor_state, chord, key)
    [3.7306]
    [3.223]
    if on.keychord_pressed then on.keychord_pressed(key, scancode, isrepeat) end
  • replacement in main.lua at line 142
    [3.7438][3.7438:7479]()
    return edit.textinput(Editor_state, t)
    [3.7438]
    [3.3116]
    if on.textinput then on.textinput(t) end
  • replacement in main.lua at line 151
    [3.7595][3.7595:7651]()
    return edit.key_released(Editor_state, key, scancode)
    [3.7595]
    [3.190712]
    if on.key_released then on.key_released(key, scancode, isrepeat) end
  • file addition: live.lua (----------)
    [3.2]
    -- A general architecture for free-wheeling, live programs:
    -- on startup:
    -- scan both the app directory and the save directory for files with numeric prefixes
    -- from the largest numeric prefix found, obtain a manifest
    -- load all files (which must start with a numeric prefix) from the manifest)
    --
    -- then start drawing frames on screen and reacting to events
    --
    -- events from keyboard and mouse are handled as the app desires
    --
    -- on incoming messages to a specific file, however, the app must:
    -- save the message's value to a specific, unused numeric prefix
    -- execute the value
    --
    -- if a game encounters an error:
    -- find the previous version of the definition in the 'head' numeric prefix
    -- decrement 'head'
    -- namespace for these functions
    live = {}
    -- state for these functions
    Live = {}
    -- a namespace of frameworky callbacks
    -- these will be modified live
    -- TODO: how to make them discoverable?
    on = {}
    -- ========= on startup, load the version at head
    function live.initialize(arg)
    -- version control
    Live.Head = 0
    Live.Next_version = 1
    Live.History = {} -- array of filename roots corresponding to each numeric prefix
    Live.Manifest = {} -- mapping from roots to numeric prefixes as of version Live.Head
    live.load_files_so_far()
    if on.load then on.load() end
    end
    function live.load_files_so_far()
    print('new edits will go to ' .. love.filesystem.getSaveDirectory())
    local files = {}
    live.append_files_with_numeric_prefix('', files)
    table.sort(files)
    live.check_integrity(files)
    live.append_files_with_numeric_prefix(love.filesystem.getSaveDirectory(), files)
    table.sort(files)
    live.check_integrity(files)
    Live.History = live.load_history(files)
    Live.Next_version = #Live.History + 1
    local head_string = love.filesystem.read('head')
    Live.Head = tonumber(head_string)
    if Live.Head > 0 then
    Live.Manifest = json.decode(love.filesystem.read(live.versioned_manifest(Live.Head)))
    end
    live.load_everything_in_manifest()
    end
    function live.append_files_with_numeric_prefix(dir, files)
    for _,file in ipairs(love.filesystem.getDirectoryItems(dir)) do
    table.insert(files, file)
    end
    end
    function live.check_integrity(files)
    local manifest_found, file_found = false, false
    local expected_index = 1
    for _,file in ipairs(files) do
    for numeric_prefix, root in file:gmatch('(%d+)-(.+)') do
    -- only runs once
    local index = tonumber(numeric_prefix)
    -- skip files without numeric prefixes
    if index ~= nil then
    if index < expected_index then
    print(index, expected_index)
    end
    assert(index >= expected_index)
    if index > expected_index then
    assert(index == expected_index+1)
    assert(manifest_found and file_found)
    expected_index = index
    manifest_found, file_found = false, false
    end
    assert(index == expected_index)
    if root == 'manifest' then
    assert(not manifest_found)
    manifest_found = true
    else
    assert(not file_found)
    file_found = true
    end
    end
    end
    end
    end
    function live.load_history(files)
    local result = {}
    for _,file in ipairs(files) do
    for numeric_prefix, root in file:gmatch('(%d+)-(.+)') do
    -- only runs once
    local index = tonumber(numeric_prefix)
    -- skip
    if index ~= nil then
    if root ~= 'manifest' then
    assert(index == #result+1)
    table.insert(result, root)
    end
    end
    end
    end
    return result
    end
    function live.load_everything_in_manifest()
    for k,v in pairs(Live.Manifest) do
    if k ~= 'parent' then
    local root, index = k, v
    local filename = live.versioned_filename(index, root)
    local buf = love.filesystem.read(filename)
    assert(buf and buf ~= '')
    live.eval(buf)
    end
    end
    end
    function live.versioned_filename(index, root)
    return ('%04d-%s'):format(index, root)
    end
    function live.versioned_manifest(index)
    return ('%04d-manifest'):format(index)
    end
    -- ========= on each frame, check for messages and alter the app as needed
    function live.update(dt)
    if Current_time - Live.Previous_read > 0.1 then
    local buf = live.receive()
    if buf then
    live.run(buf)
    end
    Live.Previous_read = Current_time
    end
    end
    -- look for a message from outside, and return nil if there's nothing
    function live.receive()
    local f = io.open(love.filesystem.getUserDirectory()..'/_love_akkartik_app')
    if f == nil then return nil end
    local result = f:read('*a')
    f:close()
    if result == '' then return nil end -- empty file == no message
    print('<='..color(--[[bold]]1, --[[blue]]4))
    print(result)
    print(reset_terminal())
    -- we can't unlink files, so just clear them
    local clear = io.open(love.filesystem.getUserDirectory()..'/_love_akkartik_app', 'w')
    clear:close()
    return result
    end
    function live.send(msg)
    local f = io.open(love.filesystem.getUserDirectory()..'/_love_akkartik_driver', 'w')
    if f == nil then return end
    f:write(msg)
    f:close()
    print('=>'..color(0, --[[green]]2))
    print(msg)
    print(reset_terminal())
    end
    -- args:
    -- format: 0 for normal, 1 for bold
    -- color: 0-15
    function color(format, color)
    return ('\027[%d;%dm'):format(format, 30+color)
    end
    function reset_terminal()
    return '\027[m'
    end
    -- define or undefine top-level bindings
    function live.run(buf)
    local cmd = buf:match('^%S+')
    assert(cmd)
    print('command is '..cmd)
    if cmd == 'QUIT' then
    love.event.quit(1)
    elseif cmd == 'MANIFEST' then
    live.send(json.encode(Live.Manifest))
    elseif cmd == 'DELETE' then
    local binding = buf:match('^%S+%s+(%S+)')
    Live.Manifest[binding] = nil
    live.eval(binding..' = nil')
    local next_filename = live.versioned_filename(Live.Next_version, binding)
    love.filesystem.write(next_filename, '')
    table.insert(Live.History, binding)
    Live.Manifest.parent = Live.Head
    local manifest_filename = live.versioned_manifest(Live.Next_version)
    love.filesystem.write(manifest_filename, json.encode(Live.Manifest))
    Live.Head = Live.Next_version
    love.filesystem.write('head', tostring(Live.Head))
    Live.Next_version = Live.Next_version + 1
    elseif cmd == 'GET' then
    local binding = buf:match('^%S+%s+(%S+)')
    live.send(live.get_binding(binding))
    -- other commands go here
    else
    local binding = cmd
    local next_filename = live.versioned_filename(Live.Next_version, binding)
    love.filesystem.write(next_filename, buf)
    table.insert(Live.History, binding)
    Live.Manifest[binding] = Live.Next_version
    Live.Manifest.parent = Live.Head
    local manifest_filename = live.versioned_manifest(Live.Next_version)
    love.filesystem.write(manifest_filename, json.encode(Live.Manifest))
    Live.Head = Live.Next_version
    love.filesystem.write('head', tostring(Live.Head))
    Live.Next_version = Live.Next_version + 1
    local status, err = live.eval(buf)
    if not status then
    -- roll back
    Live.Head = Live.Manifest.parent
    local previous_manifest_filename = live.versioned_manifest(Live.Head)
    Live.Manifest = json.decode(love.filesystem.read(previous_manifest_filename))
    -- throw an error
    live.send('ERROR '..tostring(err))
    end
    end
    end
    function live.get_binding(name)
    if Live.Manifest[name] then
    return love.filesystem.read(live.versioned_filename(Live.Manifest[name], name))
    end
    end
    -- Wrapper for Lua's weird evaluation model.
    -- Lua is persnickety about expressions vs statements, so we need to do some
    -- extra work to get the result of an evaluation.
    -- return values:
    -- all well -> true, ...
    -- load failed -> nil, error message
    -- run failed -> false, error message
    function live.eval(buf)
    -- We assume a program is either correct with 'return' prefixed xor not.
    -- Is this correct? Who knows! But the Lua REPL does this as well.
    local f = load('return '..buf, 'REPL')
    if f then
    return pcall(f)
    end
    local f, err = load(buf, 'REPL')
    if f then
    return pcall(f)
    else
    return nil, err
    end
    end
    -- ========= on error, pause the app and wait for messages
    function live.run()
    love.load(love.arg.parseGameArguments(arg), arg)
    love.timer.step()
    local dt = 0
    return function()
    local status, result = xpcall(live.try_run, live.handle_error)
    return result
    end
    end
    -- one iteration of the event loop
    -- return nil to continue the event loop, non-nil to quit
    -- from https://love2d.org/wiki/love.run
    function live.try_run()
    if love.event then
    love.event.pump()
    for name, a,b,c,d,e,f in love.event.poll() do
    if name == 'quit' then
    if not love.quit() then
    return a or 0
    end
    end
    love.handlers[name](a,b,c,d,e,f)
    end
    end
    -- update
    dt = love.timer.step()
    love.update(dt)
    -- draw before update to give it a chance to mutate state
    love.graphics.origin()
    love.graphics.clear(love.graphics.getBackgroundColor())
    love.draw()
    love.graphics.present()
    love.timer.sleep(0.001)
    -- returning nil continues the loop
    end
    -- return nil to continue the event loop, non-nil to quit
    function live.handle_error(err)
    local msg = tostring(err)
    -- draw a pause indicator on screen
    love.graphics.setColor(1,0,0)
    love.graphics.rectangle('fill', 10,10, 3,10)
    love.graphics.rectangle('fill', 16,10, 3,10)
    love.graphics.present()
    -- print stack trace here just in case we ran the app through a terminal
    local stack_trace = debug.traceback('Error: ' .. tostring(msg), --[[stack frame]]2):gsub('\n[^\n]+$', '')
    print(stack_trace)
    print('Look in the driver for options to investigate further.')
    print("(You probably can't close the app window at this point. If you don't have the driver set up, you might need to force-quit.)")
    -- send stack trace to driver and wait for a response
    live.send('ERROR '..stack_trace)
    local buf
    repeat
    buf = live.receive()
    until buf
    if buf == 'QUIT' then
    return true
    end
    live.run(buf)
    end
  • file addition: head (----------)
    [3.2]
    0