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