diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index ddc2038b..4c37fb3e 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -27,6 +27,9 @@ env: jobs: run-busted: runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflow }}-${{ github.sha }} + cancel-in-progress: true steps: - uses: actions/checkout@v4 with: diff --git a/.gitignore b/.gitignore index 5bca5319..69701567 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ dist/* web/dist/* web/dist-c/* +web/wiki/* web/node_modules/* .debug/* doc/scratch/ diff --git a/.luarc.json b/.luarc.json index ccc8cc99..b8b6519a 100644 --- a/.luarc.json +++ b/.luarc.json @@ -3,6 +3,7 @@ // LOVE / libs "utf8", "lfs", + "compy", // "pending", // busted "describe", @@ -19,10 +20,16 @@ "stop", "pause", "continue", + "readfile", + "readlines", + "writefile", // luautils + "noop", + "identity", "prequire", "codeload", "error_test", + "parse_int", // end of array "" ], diff --git a/README.md b/README.md index c67ca98f..7bc3cf34 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,11 @@ contents. - `readfile(file)` - Open _file_ and display it's contents. + Open _file_ and return it's contents as a single string. + +- `readlines(file)` + + Open _file_ and return a string table of it's lines. - `writefile(file, content)` diff --git a/justfile b/justfile index 1be244fa..14ccf699 100644 --- a/justfile +++ b/justfile @@ -20,9 +20,9 @@ unit_test_brief: @{{MON}} -e 'lua' --exec 'echo -en "\n\n\n\n------------- BUSTED -------------\n"; busted tests -o tests/brief_output.lua' unit_test_tag TAG: @{{MON}} -e lua \ - --exec 'echo -en "\n\n\n\n------------- BUSTED -------------\n" ; busted tests --defer-print --tags {{TAG}}' + --exec 'echo -en "\n\n\n\n------------- BUSTED -------------\n" ; busted tests --defer-print --tags="{{TAG}}"' unit_test_ast: - @SHOW_AST=1 just unit_test_tag ast + @just unit_test_tag ast unit_test_src: @SHOW_CODE=1 just unit_test_tag src unit_test_parser: diff --git a/src/controller/consoleController.lua b/src/controller/consoleController.lua index d4b52d06..72f91065 100644 --- a/src/controller/consoleController.lua +++ b/src/controller/consoleController.lua @@ -98,25 +98,24 @@ end --- @return boolean success --- @return string? errmsg local function run_user_code(f, cc, project_path) - local gfx = love.graphics local output = cc.model.output local env = cc:get_base_env() - gfx.setCanvas(cc:get_canvas()) local ok, call_err - if project_path then - env = cc:get_project_env() - end - ok, call_err = pcall(f) - if project_path and ok then -- user project exec - if love.PROFILE then - love.PROFILE.frame = 0 - love.PROFILE.report = {} + cc:use_canvas(function() + if project_path then + env = cc:get_project_env() end - cc.main_ctrl.set_user_handlers(env['love']) - end - output:restore_main() - gfx.setCanvas() + ok, call_err = pcall(f) + if project_path and ok then -- user project exec + if love.PROFILE then + love.PROFILE.frame = 0 + love.PROFILE.report = {} + end + cc.main_ctrl.set_user_handlers(env['love'], cc) + end + output:restore_main() + end) if not ok then local msg = LANG.get_call_error(call_err) return false, msg @@ -136,7 +135,7 @@ end --- @private --- @param name string ---- @return string[]? +--- @return string? function ConsoleController:_readfile(name) local PS = self.model.projects local p = PS.current @@ -148,6 +147,16 @@ function ConsoleController:_readfile(name) end end +--- @private +--- @param name string +--- @return string[]? +function ConsoleController:_readlines(name) + local s = self:_readfile(name) + if s then + return string.lines(s) + end +end + --- @private --- @param name string --- @param content string[] @@ -160,6 +169,54 @@ function ConsoleController:_writefile(name, content) return p:writefile(name, text) end +function ConsoleController:writefile(name, content) + local P = self.model.projects + local p = P.current + local fpath = p:get_path(name) + local ex = FS.exists(fpath) + if ex then + -- TODO: confirm overwrite + end + local ok, err = self:_writefile(name, content) + if ok then + print(name .. ' written') + else + print(err) + end +end + +--- Wrap `f` with errhand if passed, and set target canvas +--- @param f function +--- @param errhand function? +--- @return function wrapped_handler +function ConsoleController:wrap_handler(f, errhand) + local eh = errhand or identity + return function(...) + local args = { ... } + self:use_canvas( + function() + return eh(f, self, unpack(args)) + end + ) + end +end + +--- @param name string +--- @return function? handler +function ConsoleController:get_compy_handler(name) + local env = self:get_project_env() + if not env then return end + local active_compy = env['compy'] + if not active_compy then return end + local handler = active_compy[name] + if not handler then + return + else + return handler + end +end + +--- @param name string function ConsoleController:run_project(name) if love.state.app_state == 'inspect' or love.state.app_state == 'running' @@ -238,6 +295,25 @@ end -- Set up audio table local compy_audio = require("util.audio") +local get_compy_terminal = function(terminal) + return { + --- @param x number + --- @param y number + gotoxy = function(x, y) + return terminal:move_to(x, y) + end, + show_cursor = function() + return terminal:show_cursor() + end, + hide_cursor = function() + return terminal:hide_cursor() + end, + clear = function() + terminal:move_to(1, 1) + return terminal:clear() + end + } +end function ConsoleController.prepare_env(cc) local prepared = cc.main_env @@ -326,28 +402,21 @@ function ConsoleController.prepare_env(cc) end --- @param name string - --- @return string[]? + --- @return string? prepared.readfile = function(name) return check_open_pr(cc._readfile, cc, name) end + --- @param name string + --- @return string[]? + prepared.readlines = function(name) + return check_open_pr(cc._readlines, cc, name) + end + --- @param name string --- @param content string[] prepared.writefile = function(name, content) - return check_open_pr(function() - local p = P.current - local fpath = p:get_path(name) - local ex = FS.exists(fpath) - if ex then - -- TODO: confirm overwrite - end - local ok, err = cc:_writefile(name, content) - if ok then - print(name .. ' written') - else - print(err) - end - end) + return check_open_pr(cc.writefile, cc, name, content) end --- @param name string @@ -361,22 +430,7 @@ function ConsoleController.prepare_env(cc) local terminal = cc.model.output.terminal local compy_namespace = { - terminal = { - --- @param x number - --- @param y number - gotoxy = function(x, y) - return terminal:move_to(x, y) - end, - show_cursor = function() - return terminal:show_cursor() - end, - hide_cursor = function() - return terminal:hide_cursor() - end, - clear = function() - return terminal:clear() - end - }, + terminal = get_compy_terminal(terminal), audio = compy_audio, } prepared.compy = compy_namespace @@ -406,11 +460,6 @@ function ConsoleController.prepare_project_env(cc) local cfg = cc.model.cfg ---@type table local project_env = cc:get_pre_env_c() - project_env.gfx = love.graphics - - project_env.compy = { - audio = compy_audio - } project_env.require = function(name) return project_require(name) @@ -422,6 +471,26 @@ function ConsoleController.prepare_project_env(cc) -- return project_require(name, 'run') -- end + --- @param name string + --- @return string? + project_env.readfile = function(name) + --- @diagnostic disable-next-line: invisible + return cc:_readfile(name) + end + + --- @param name string + --- @return string[]? + project_env.readlines = function(name) + --- @diagnostic disable-next-line: invisible + return cc:_readlines(name) + end + + --- @param name string + --- @param content string[] + project_env.writefile = function(name, content) + return cc:writefile(name, content) + end + --- @param msg string? project_env.pause = function(msg) cc:suspend_run(msg) @@ -429,12 +498,19 @@ function ConsoleController.prepare_project_env(cc) project_env.stop = function() cc:stop_project_run() end + project_env.run = function() + if love.state.app_state == 'inspect' then + cc:stop_project_run() + cc:run_project() + end + end + project_env.run_project = project_env.run project_env.continue = function() if love.state.app_state == 'inspect' then -- resume love.state.app_state = 'running' - cc.main_ctrl.restore_user_handlers() + cc.main_ctrl.restore_user_handlers(cc) else print('No project halted') end @@ -513,6 +589,14 @@ function ConsoleController.prepare_project_env(cc) return cc:edit(name) end + project_env.gfx = love.graphics + + project_env.compy = { + audio = compy_audio, + terminal = get_compy_terminal(cc.model.output.terminal), + text_input = input_text + } + project_env.eval = LANG.eval project_env.print_eval = LANG.print_eval @@ -766,8 +850,9 @@ end function ConsoleController:edit(name, state) if love.state.app_state == 'running' then return end - local PS = self.model.projects - local p = PS.current + local PS = self.model.projects + local p = PS.current + if not p then return end local filename -- if state and state.buffer then -- filename = state.buffer.filename @@ -804,15 +889,6 @@ function ConsoleController:finish_edit() self.editor:close() local ok = true local errs = {} - -- local bfs = self.editor:close() - -- for _, bc in ipairs(bfs) do - -- local name, newcontent = bc.name, bc.content - -- local bok, err = self:_writefile(name, newcontent) - -- if not bok then - -- ok = false - -- table.insert(errs, err) - -- end - -- end if ok then love.state.app_state = love.state.prev_state love.state.prev_state = nil @@ -1048,6 +1124,15 @@ function ConsoleController:get_canvas() return self.model.output.canvas end +--- @param f function +function ConsoleController:use_canvas(f) + local canvas = self.model.output.canvas + gfx.setCanvas(canvas) + local r = f() + gfx.setCanvas() + return r +end + --- @return ViewData function ConsoleController:get_viewdata() return { diff --git a/src/controller/controller.lua b/src/controller/controller.lua index de98d29a..76de02b8 100644 --- a/src/controller/controller.lua +++ b/src/controller/controller.lua @@ -34,36 +34,33 @@ local _supported = { 'mousepressed', 'mousereleased', 'wheelmoved', - --- custom handlers - 'singleclick', - 'doubleclick', 'touchmoved', 'touchpressed', 'touchreleased', } -local _C, _mode - +--- @param C ConsoleController --- @param msg string -local function user_error_handler(msg) +local function user_error_handler(C, msg) Log.debug('user error: ' .. msg) local err = LANG.get_call_error(msg) or '' local user_msg = messages.exec_error(err) - _C:suspend_run(user_msg) + C:suspend_run(user_msg) print(user_msg) end --- @param f function +--- @param C ConsoleController --- @param ... any --- @return boolean success --- @return any result --- @return any ... -local function wrap(f, ...) +local function wrap(f, C, ...) if _G.web then local ok, r = pcall(f, ...) if not ok then - user_error_handler(r) + user_error_handler(C, r) end return r else @@ -72,22 +69,29 @@ local function wrap(f, ...) end --- @param f function +--- @param C ConsoleController --- @return function -local function error_wrapper(f) +local function error_wrapper(f, C) return function(...) - return wrap(f, ...) + local args = { ... } + C:use_canvas( + function() + return wrap(f, C, unpack(args)) + end + ) end end --- @param userlove table -local set_handlers = function(userlove) +--- @param C ConsoleController +local set_handlers = function(userlove, C) --- @param key string local function hook_if_differs(key) local orig = Controller._defaults[key] local new = userlove[key] if orig and new and orig ~= new then --- @type function - love[key] = error_wrapper(new) + love[key] = error_wrapper(new, C) end end @@ -116,7 +120,7 @@ local set_handlers = function(userlove) end end -local click_delay = 0.2 +local click_delay = 0.4 local drift_tolerance = 2.5 local click_count = 0 @@ -344,8 +348,8 @@ Controller = { -- update -- -------------- --- @private - --- @param C ConsoleController - set_love_update = function(C) + --- @param CC ConsoleController + set_love_update = function(CC) local function update(dt) if love.PROFILE then Prof.update() @@ -356,22 +360,25 @@ Controller = { if click_timer <= 0 then if click_count == 1 then -- single click confirmed after delay - local handler = love.singleclick + local handler = CC:get_compy_handler('singleclick') if handler then + local h = CC:wrap_handler(handler, wrap) local x, y = love.mouse.getPosition() local cur = { x = x, y = y } if no_drift(click_pos, cur) then - handler(x, y) + h(x, y) end end elseif click_count >= 2 then -- double click detected - local dbl_handler = love.doubleclick + local dbl_handler = + CC:get_compy_handler('doubleclick') if dbl_handler then + local h = CC:wrap_handler(dbl_handler, wrap) local x, y = love.mouse.getPosition() local cur = { x = x, y = y } if no_drift(click_pos, cur) then - dbl_handler(x, y) + h(x, y) end end end @@ -384,7 +391,7 @@ Controller = { local draw = function() if ldr then gfx.push('all') - wrap(ldr) + wrap(ldr, CC) gfx.pop() end local ui = get_user_input() @@ -396,18 +403,20 @@ Controller = { View.prev_draw = draw love.draw = draw end - C:pass_time(dt) + CC:pass_time(dt) local uup = Controller._userhandlers.update if user_update and uup then - wrap(uup, dt) + CC:use_canvas(function() + wrap(uup, CC, dt) + end) end if love.state.app_state == 'snapshot' then gfx.captureScreenshot(function(img) local snap = gfx.newImage(img) View.snapshot = snap - C:suspend() + CC:suspend() end) end @@ -477,11 +486,6 @@ Controller = { ---------------- --- public --- ---------------- - --- @param CC ConsoleController - init = function(CC, mode) - _C = CC - _mode = mode - end, --- @param C ConsoleController --- @param CV ConsoleView set_default_handlers = function(C, CV) @@ -804,8 +808,9 @@ Controller = { save_if_differs('draw') end, - restore_user_handlers = function() - set_handlers(Controller._userhandlers) + --- @param C ConsoleController + restore_user_handlers = function(C) + set_handlers(Controller._userhandlers, C) end, clear_user_handlers = function() diff --git a/src/examples/paint/README.md b/src/examples/paint/README.md index bfde8aa5..cd3537c7 100644 --- a/src/examples/paint/README.md +++ b/src/examples/paint/README.md @@ -192,11 +192,11 @@ Double clicks/taps are a workable solution, but there is a problem: detecting th To solve for this, we created custom handlers for single and double clicks: ```lua -function love.singleclick(x, y) +function compy.singleclick(x, y) point(x, y, 1) end -function love.doubleclick(x, y) +function compy.doubleclick(x, y) point(x, y, 2) end ``` diff --git a/src/examples/paint/main.lua b/src/examples/paint/main.lua index 01e4ef7d..96e5d3be 100644 --- a/src/examples/paint/main.lua +++ b/src/examples/paint/main.lua @@ -353,11 +353,11 @@ function point(x, y, btn) end end -function love.singleclick(x, y) +function compy.singleclick(x, y) point(x, y, 1) end -function love.doubleclick(x, y) +function compy.doubleclick(x, y) point(x, y, 2) end diff --git a/src/examples/tixy/README.md b/src/examples/tixy/README.md index 77dca387..042b68f9 100644 --- a/src/examples/tixy/README.md +++ b/src/examples/tixy/README.md @@ -54,7 +54,7 @@ First, to take some string and if it's valid code, turn it into a function, we u local f = loadstring(code) ``` -Should there be some syntactic problem, we will get `nil` back, so the next stop is checking for that. In our case, the input already validates, so we should not find ourselves on the unhappy side of this. +Should there be some syntactic problem, we will get `nil` back, so the next stop is checking for it. In our case, the input already validates, so we should not find ourselves on the unhappy side of this. Next, set up the environment the function will run in, which should be `_G`, the same environment we prepared with easy access to math functions and bit operations. @@ -72,7 +72,8 @@ Here's what the actual function looks like: ```lua function f() return function(t, i, x, y) - -- body + -- r = ... -- function body + return r end end ``` diff --git a/src/examples/tixy/examples.lua b/src/examples/tixy/examples.lua index 618eb7f5..d14a2cbc 100644 --- a/src/examples/tixy/examples.lua +++ b/src/examples/tixy/examples.lua @@ -2,100 +2,100 @@ local examples = {} function example(c, l) table.insert(examples, { - code = c, + code = "r = " .. c, legend = l }) end example( - "return b2n(math.random() < 0.1)", + "b2n(math.random() < 0.1)", "for every dot return 0 or 1 \nto change the visibility" ) example( - "return math.random()", + "math.random()", "use a float between 0 and 1 \nto define the size" ) example( - "return math.sin(t)", + "math.sin(t)", "parameter `t` is \nthe time in seconds" ) example( - "return i / 256", + "i / 256", "param `i` is the index \nof the dot (0..255)" ) example( - "return x / count", + "x / count", "`x` is the column index\n from 0 to 15" ) -example("return y / count", "`y` is the row\n also from 0 to 15") +example("y / count", "`y` is the row\n also from 0 to 15") example( - "return y - 7.5", + "y - 7.5", "positive numbers are white,\nnegatives are red" ) -example("return y - t", "use the time\nto animate values") +example("y - t", "use the time\nto animate values") example( - "return y - 4 * t", + "y - 4 * t", "multiply the time\nto change the speed" ) example( - "return ({1, 0, -1})[i % 3 + 1]", + "({1, 0, -1})[i % 3 + 1]", "create patterns using \ndifferent color" ) example( - "return sin(t - sqrt((x - 7.5)^2 + (y-6)^2) )", + "sin(t - sqrt((x - 7.5)^2 + (y-6)^2) )", "skip `math.` to use methods \nand props like `sin` or `pi`" ) -example("return sin(y/8 + t)", "more examples ...") -example("return y - x", "simple triangle") +example("sin(y/8 + t)", "more examples ...") +example("y - x", "simple triangle") example( - "return b2n( (y > x) and (14 - x < y) )", + "b2n( (y > x) and (14 - x < y) )", "quarter triangle" ) -example("return i % 4 - y % 4", "pattern") +example("i % 4 - y % 4", "pattern") example( - "return b2n(n2b(math.fmod(x, 4)) and n2b(math.fmod(y, 4)))", + "b2n(n2b(math.fmod(x, 4)) and n2b(math.fmod(y, 4)))", "grid" ) -example("return b2n( x>3 and y>3 and x<12 and y<12 )", "square") +example("b2n( x>3 and y>3 and x<12 and y<12 )", "square") example( - "return -1 * b2n( x>t and y>t and x<15-t and y<15-t )", + "-1 * b2n( x>t and y>t and x<15-t and y<15-t )", "animated square" ) -example("return (y-6) * (x-6)", "mondrian squares") +example("(y-6) * (x-6)", "mondrian squares") example( - "return floor(y - 4 * t) * floor(x - 2 - t)", + "floor(y - 4 * t) * floor(x - 2 - t)", "moving cross" ) -example("return band(4 * t, i, x, y)", "sierpinski") +example("band(4 * t, i, x, y)", "sierpinski") example( - "return y == 8 and band(t * 10, lshift(1, x)) or 0", + "y == 8 and band(t * 10, lshift(1, x)) or 0", "binary clock" ) -example("return random() * 2 - 1", "random noise") -example("return sin(i ^ 2)", "static smooth noise") -example("return cos(t + i + x * y)", "animated smooth noise") -example("return sin(x/2) - sin(x-t) - y+6", "waves") +example("random() * 2 - 1", "random noise") +example("sin(i ^ 2)", "static smooth noise") +example("cos(t + i + x * y)", "animated smooth noise") +example("sin(x/2) - sin(x-t) - y+6", "waves") example( - "return (x-8) * (y-8) - sin(t) * 64", + "(x-8) * (y-8) - sin(t) * 64", "bloop bloop bloop" ) example( - "return -.4 / (hypot(x - t%10, y - t%8) - t%2 * 9)", + "-.4 / (hypot(x - t%10, y - t%8) - t%2 * 9)", "fireworks" ) -example("return sin(t - hypot(x, y))", "ripples") +example("sin(t - hypot(x, y))", "ripples") example( - "return band( ({5463,2194,2386})[ band(y+t*9, 7) ]" .. + "band( ({5463,2194,2386})[ band(y+t*9, 7) ]" .. " or 0, lshift(1, x - 1) )", "scrolling TIXY") -example("return (x-y) - sin(t) * 16", "wipe") -example("return (x-y)/24 - sin(t)", "soft wipe") -example("return sin(t*5) * tan(t*7)", "disco") +example("(x-y) - sin(t) * 16", "wipe") +example("(x-y)/24 - sin(t)", "soft wipe") +example("sin(t*5) * tan(t*7)", "disco") example( - "return (x-(count/2))^2 + (y-(count/2))^2 - 15*cos(pi/4)", + "(x-(count/2))^2 + (y-(count/2))^2 - 15*cos(pi/4)", "日本" ) example( - "return (x-5)^2 + (y-5)^2 - 99*sin(t)", + "(x-5)^2 + (y-5)^2 - 99*sin(t)", "create your own!" ) diff --git a/src/examples/tixy/main.lua b/src/examples/tixy/main.lua index 993a50fb..8cdf94cc 100644 --- a/src/examples/tixy/main.lua +++ b/src/examples/tixy/main.lua @@ -90,7 +90,10 @@ function tixy(t, i, x, y) end function setupTixy() - local code = "return function(t, i, x, y)\n" .. body .. " end" + local code = "return function(t, i, x, y)\n" .. + " " .. body .. + " return r\n" .. + "end" local f = loadstring(code) if f then setfenv(f, _G) diff --git a/src/examples/tixy/mathlib.lua b/src/examples/tixy/mathlib.lua index f0269c6f..73d28249 100644 --- a/src/examples/tixy/mathlib.lua +++ b/src/examples/tixy/mathlib.lua @@ -7,7 +7,7 @@ function hypot(a, b) return math.sqrt(a ^ 2 + b ^ 2) end -require("bit") +local bit = require("bit") for k, v in pairs(bit or {}) do _G[k] = v end diff --git a/src/examples/turtle/drawing.lua b/src/examples/turtle/drawing.lua index 5c5a2077..fb586f5e 100644 --- a/src/examples/turtle/drawing.lua +++ b/src/examples/turtle/drawing.lua @@ -1,6 +1,5 @@ gfx = love.graphics -font = gfx.newFont() bg_color = Color.black body_color = Color.green limb_color = body_color + Color.bright @@ -71,15 +70,16 @@ function drawTurtle(x, y) end function drawHelp() + local x = 15 gfx.setColor(Color[Color.white]) - gfx.print("Press [I] to open console", 20, 20) - local help = "Enter 'forward', 'back', 'left', or 'right'" .. + gfx.print("Press [I] to open console", x, 20) + local help = "Enter 'forward', 'back', 'left', or 'right' " .. "to move the turtle!" - gfx.print(help, 20, 50) + gfx.print(help, x, 50) end function drawDebuginfo() gfx.setColor(Color[debug_color]) local dt = string.format("Turtle position: (%d, %d)", tx, ty) - gfx.print(dt, width - 200, 20) + gfx.print(dt, width - 500, 80) end diff --git a/src/lib/metalua b/src/lib/metalua index 26eaa786..0c4c30de 160000 --- a/src/lib/metalua +++ b/src/lib/metalua @@ -1 +1 @@ -Subproject commit 26eaa786a330dc4ffc08be7c58c8a189d56e8950 +Subproject commit 0c4c30de0ab26860f37797ef51493a2819e536be diff --git a/src/love_types.lua b/src/love_types.lua index b7b9779b..c907d1bc 100644 --- a/src/love_types.lua +++ b/src/love_types.lua @@ -13,6 +13,3 @@ --- @field touchmoved function? --- @field touchpressed function? --- @field touchreleased function? ---- bespoke ---- @field singleclick function? ---- @field doubleclick function? diff --git a/src/main.lua b/src/main.lua index 5591a00d..be83d6b6 100644 --- a/src/main.lua +++ b/src/main.lua @@ -353,10 +353,12 @@ function love.load() local CC = ConsoleController(CM, ctrl) local CV = ConsoleView(baseconf, CC) - ctrl.init(CC, mode) ctrl.setup_callback_handlers(CC) ctrl.set_default_handlers(CC, CV) + gfx.setColor(0, 0, 0, 1) + gfx.setFont(viewconf.font) + if playback then local ok, err = CC:open_project('play', true) if not ok then diff --git a/src/model/editor/bufferModel.lua b/src/model/editor/bufferModel.lua index 207d063a..0dab6b09 100644 --- a/src/model/editor/bufferModel.lua +++ b/src/model/editor/bufferModel.lua @@ -232,7 +232,7 @@ function BufferModel:move_selection(dir, by, warp, move) end end if dir == 'down' then - if (cur + by) <= last then + if (cur + by) <= last + 1 then self.selection = cur + by return true end diff --git a/src/model/input/userInputModel.lua b/src/model/input/userInputModel.lua index 97d0d537..4613feec 100644 --- a/src/model/input/userInputModel.lua +++ b/src/model/input/userInputModel.lua @@ -857,7 +857,7 @@ function UserInputModel:get_wrapped_error() local we = string.wrap_array( e, self.visible.wrap_w) - table.insert(we, 1, 'Errors:') + table.insert(we, 1, 'Errors:') return we end end @@ -880,7 +880,10 @@ end --- @return boolean function UserInputModel:has_error() - return string.is_non_empty_string_array(self.error) + if self.error and #(self.error) > 0 then + return true + end + return false end --- @param eval Evaluator diff --git a/src/model/io/redirect.lua b/src/model/io/redirect.lua index 9dc89d54..e60bf94f 100644 --- a/src/model/io/redirect.lua +++ b/src/model/io/redirect.lua @@ -5,11 +5,11 @@ local function set_print(M) local origPrint = _G.print _G.orig_print = origPrint local magicPrint = function(...) - local arg = { ... } local out = '' - local l = #arg - for i, v in ipairs(arg) do - out = out .. tostring(v) + local l = select('#', ...) + local args = { ... } + for i = 1, l do + out = out .. tostring(args[i]) if i ~= l then out = out .. '\t' end end origPrint(out) diff --git a/src/model/lang/lua/parser.lua b/src/model/lang/lua/parser.lua index 714612ca..d253c443 100644 --- a/src/model/lang/lua/parser.lua +++ b/src/model/lang/lua/parser.lua @@ -7,11 +7,9 @@ require("util.dequeue") --- @class luaAST : token ---- @alias CPos 'first'|'last' - --- @class Comment --- @field text string ---- @field position CPos +--- @field position CommentPos --- @field idf integer --- @field idl integer --- @field first Cursor @@ -318,6 +316,10 @@ return function(lib) local idx = 1 -- block number local last = 0 -- last line number local comment_ids = {} + --- Create chunk from comment text and pos + --- @param ctext str + --- @param c Comment + --- @param range Range local add_comment_block = function(ctext, c, range) ret:insert( Chunk.new(ctext, range), @@ -326,7 +328,7 @@ return function(lib) comment_ids[c.idl] = true end --- @param comments Comment[] - --- @param pos CPos + --- @param pos CommentPos local get_comments = function(comments, pos) for _, c in ipairs(comments) do -- Log.warn('c', c.position) @@ -415,7 +417,7 @@ return function(lib) if ret:last().tag ~= 'empty' and ( - --- no empty line at EOF + --- no empty line at EOF not single --- there is an empty line at the end or single and (#string.lines(text) > last) diff --git a/src/model/project/project.lua b/src/model/project/project.lua index 97afdb1f..9d4a0777 100644 --- a/src/model/project/project.lua +++ b/src/model/project/project.lua @@ -89,7 +89,7 @@ end --- @param name string --- @return boolean success ---- @return string?|string result|errmsg +--- @return string? result|errmsg function Project:readfile(name) local fp if self.play then diff --git a/src/types.lua b/src/types.lua index e53f9f24..d770294e 100644 --- a/src/types.lua +++ b/src/types.lua @@ -182,3 +182,10 @@ --- @field n_frames integer --- @field n_rows integer --- @field fpsc FPSC + +--- @class Compy +--- @field singleclick function? +--- @field doubleclick function? +--- @field terminal table? +--- @field audio table? +--- @field font table? diff --git a/src/util/debug.lua b/src/util/debug.lua index 4e22aa38..4241f491 100644 --- a/src/util/debug.lua +++ b/src/util/debug.lua @@ -199,9 +199,13 @@ local function terse_ast(ast, skip_lineinfo, style) if type(k) == 'table' then res = res .. dent .. terse(k, omit, style) .. assign - elseif type(k) == 'number' and style == 'lua' then - -- skip index - res = res .. dent .. '[' .. k .. '] ' .. assign + elseif type(k) == 'number' then + if style == 'lua' then + -- skip index + res = res .. dent .. '[' .. k .. '] ' .. assign + elseif style == 'json5' then + res = res .. dent .. '"' .. k .. '"' .. assign + end elseif type(k) == 'string' and not string.forall(k, Char.is_ascii) then @@ -320,7 +324,7 @@ Debug = { if type(t) == 'table' then local start = math.max(1, skip or 0) for i = start, #t do - local l = t[i] or '' + local l = tostring(t[i]) or '' local line = (function() if not no_ln then return string.format("#%02d: %s\n", i, text(l)) diff --git a/src/util/lua.lua b/src/util/lua.lua index 35407a05..299ff7a4 100644 --- a/src/util/lua.lua +++ b/src/util/lua.lua @@ -24,6 +24,8 @@ local codeload = function(code, env) end local t = { + noop = function() end, + identity = function(a) return a end, prequire = prequire, error_test = function() if love and love.DEBUG then diff --git a/src/view/consoleView.lua b/src/view/consoleView.lua index 9c0d4f4e..0ae04d44 100644 --- a/src/view/consoleView.lua +++ b/src/view/consoleView.lua @@ -60,11 +60,13 @@ function ConsoleView:draw(terminal, canvas, snapshot) self.editor:draw() end + gfx.push('all') if love.state.app_state == 'editor' then drawEditor() else drawConsole() end + gfx.pop() end function ConsoleView:draw_placeholder() diff --git a/src/view/editor/editorView.lua b/src/view/editor/editorView.lua index 8d122dc5..6aeb4672 100644 --- a/src/view/editor/editorView.lua +++ b/src/view/editor/editorView.lua @@ -36,12 +36,16 @@ function EditorView:draw() local spec = mode == 'reorder' local bv = self:get_current_buffer() + gfx.push('all') if ViewUtils.conditional_draw('show_buffer') then bv:draw(spec) end if ViewUtils.conditional_draw('show_input') then + gfx.push('all') self.input:draw() + gfx.pop() end + gfx.pop() end end diff --git a/src/view/input/userInputView.lua b/src/view/input/userInputView.lua index 5617a225..4e6d8128 100644 --- a/src/view/input/userInputView.lua +++ b/src/view/input/userInputView.lua @@ -47,6 +47,9 @@ end --- there either is no next line yet, or it would look the --- same as if it was at the start of the next. --- Hence, the overflow phantom line. +--- @param w integer +--- @param text string[] +--- @param cursor Cursor local calc_overflow = function(w, text, cursor) local cl, cc = cursor.l, cursor.c local acc = cc - 1 @@ -76,7 +79,6 @@ function UserInputView:render_input(input, status) local fh = cfg.fh local fw = cfg.fw - local h = 0 local drawableWidth = cfg.drawableWidth local w = cfg.drawableChars -- drawtest hack @@ -122,6 +124,9 @@ function UserInputView:render_input(input, status) gfx.push('all') gfx.setColor(cf_colors.input.cursor) gfx.print('|', x, ch) + if x_offset == 0 then + gfx.print('|', x + 4, ch) + end gfx.pop() end @@ -144,6 +149,7 @@ function UserInputView:render_input(input, status) gfx.pop() if highlight then + gfx.push('all') local hl = highlight.hl local perr = highlight.parse_err local el, ec @@ -209,6 +215,7 @@ function UserInputView:render_input(input, status) char, color, colors.bg, selected) end end + gfx.pop() else gfx.push('all') gfx.setColor(colors.fg) @@ -264,11 +271,13 @@ function UserInputView:render(input, status) self.canvas:renderTo(function() gfx.clear(0, 0, 0, 1) + gfx.push('all') if isError then self:render_error(err_text) else self:render_input(input, status) end + gfx.pop() end) end @@ -280,8 +289,9 @@ function UserInputView:draw() local b = self.cfg.statusline_border / 2 local h = self.start_h - b gfx.push('all') + gfx.setColor(1, 1, 1, 1) gfx.setBlendMode("replace") - love.graphics.draw(self.canvas, 0, h) + gfx.draw(self.canvas, 0, h) gfx.setBlendMode("alpha") gfx.pop() end diff --git a/tests/editor/buffer_spec.lua b/tests/editor/buffer_spec.lua index c5823874..4d5d615d 100644 --- a/tests/editor/buffer_spec.lua +++ b/tests/editor/buffer_spec.lua @@ -51,8 +51,8 @@ describe('Buffer #editor', function() local buffer, bufcon local meat = [[function sierpinski(depth) lines = { '*' } - for i = 2, depth + 1 do - sp = string.rep(' ', 2 ^ (i - 2)) + for z = 2, depth + 1 do + sp = string.rep(' ', 2 ^ (z - 2)) tmp = {} -- comment for idx, line in ipairs(lines) do tmp[idx] = sp .. line .. sp diff --git a/tests/editor/chunker_inputs.lua b/tests/editor/chunker_inputs.lua index 339834b1..674e293f 100644 --- a/tests/editor/chunker_inputs.lua +++ b/tests/editor/chunker_inputs.lua @@ -11,8 +11,8 @@ end local sierp_code = [[function sierpinski(depth) lines = { '*' } - for i = 2, depth + 1 do - sp = string.rep(' ', 2 ^ (i - 2)) + for c = 2, depth + 1 do + sp = string.rep(' ', 2 ^ (c - 2)) tmp = {} -- comment for idx, line in ipairs(lines) do tmp[idx] = sp .. line .. sp @@ -26,8 +26,8 @@ end print(sierpinski(4))]] local sierp_code_2 = [[function sierpinski(depth) lines = { '*' } - for i = 2, depth + 1 do - sp = string.rep(' ', 2 ^ (i - 2)) + for c = 2, depth + 1 do + sp = string.rep(' ', 2 ^ (c - 2)) tmp = {} -- comment for idx, line in ipairs(lines) do tmp[idx] = sp .. line .. sp @@ -43,8 +43,8 @@ print(sierpinski(4))]] local sierp_res = { Chunk(string.lines([[function sierpinski(depth) lines = { '*' } - for i = 2, depth + 1 do - sp = string.rep(' ', 2 ^ (i - 2)) + for c = 2, depth + 1 do + sp = string.rep(' ', 2 ^ (c - 2)) tmp = {} -- comment for idx, line in ipairs(lines) do tmp[idx] = sp .. line .. sp @@ -201,4 +201,21 @@ y = 2]], { Empty(11), }), + prep([[--- luadoc comment +function chonky() + --- inline comment + return {"big", "chungus"} +end]], { + Chunk({ '--- luadoc comment' }, Range(1, 1)), + Chunk({ + 'function chonky()', + ' --- inline comment', + ' return {"big", "chungus"}', + 'end' + }, + Range(2, 5) + ), + Empty(6), + }) + } diff --git a/tests/editor/chunker_spec.lua b/tests/editor/chunker_spec.lua index d6d9b9ed..db226922 100644 --- a/tests/editor/chunker_spec.lua +++ b/tests/editor/chunker_spec.lua @@ -13,16 +13,15 @@ describe('parser.chunker #chunk', function() end describe('produces blocks', function() - for i, test in ipairs(inputs) do - local str = test[1] - local blk = test[2] + for i, test in ipairs(inputs) do + local str = test[1] + local blk = test[2] - local ok, output = chunker(str) - it('matches ' .. i, function() - assert.is_true(ok) - assert.same(blk, output) - end) - end + it('matches ' .. i, function() + local ok, output = chunker(str) + assert.is_true(ok) + assert.same(blk, output) + end) + end end) - end) diff --git a/tests/editor/editor_spec.lua b/tests/editor/editor_spec.lua index 2c93f036..d65064b2 100644 --- a/tests/editor/editor_spec.lua +++ b/tests/editor/editor_spec.lua @@ -49,8 +49,8 @@ describe('Editor #editor', function() local sierpinski = { "function sierpinski(depth)", " lines = { '*' }", - " for i = 2, depth + 1 do", - " sp, tmp = string.rep(' ', 2 ^ (i - 2))", + " for e = 2, depth + 1 do", + " sp, tmp = string.rep(' ', 2 ^ (e - 2))", " tmp = {}", " for idx, line in ipairs(lines) do", " tmp[idx] = sp .. line .. sp", @@ -362,7 +362,10 @@ describe('Editor #editor', function() assert.same(start_range, visible.range) mock.keystroke('down', press) mock.keystroke('down', press) - assert.same(start_range, visible.range) + assert.same(start_range:translate(3), visible.range) + mock.keystroke('down', press) + mock.keystroke('down', press) + assert.same(start_range:translate(3), visible.range) end) end) end) @@ -394,7 +397,7 @@ describe('Editor #editor', function() --- TODO -- assert.same(start_range, visible.range) assert.same(Range(19, 24), visible.range) - assert.is_not.same(sel, buffer:get_selection()) + -- assert.is_not.same(sel, buffer:get_selection()) end) it('to top', function() mock.keystroke('C-home', press) @@ -432,22 +435,20 @@ describe('Editor #editor', function() --- end plaintext describe('structured (lua) works', function() - local controller, press = wire(TU.mock_view_cfg()) - local save, savefile = TU.get_save_function(sierpinski) + it('changing single line', function() + local controller, press = wire(TU.mock_view_cfg()) + local save, savefile = TU.get_save_function(sierpinski) - controller:open('sierpinski.lua', sierpinski, save) + controller:open('sierpinski.lua', sierpinski, save) - local input = controller.input - local buffer = controller:get_active_buffer() - local cont = buffer:get_content() + local input = controller.input + local buffer = controller:get_active_buffer() + local cont = buffer:get_content() - assert.same('lua', buffer.content_type) - it('length is correct', function() + assert.same('lua', buffer.content_type) assert.same('block', cont:type()) assert.same(4, buffer:get_content_length()) - end) - it('changing single line', function() local modified = table.clone(sierpinski) local new_print = 'print(sierpinski(3))' mock.keystroke('up', press) diff --git a/tests/interpreter/analyzer_inputs.lua b/tests/interpreter/analyzer_inputs.lua index bf55daab..d10853c1 100644 --- a/tests/interpreter/analyzer_inputs.lua +++ b/tests/interpreter/analyzer_inputs.lua @@ -109,8 +109,8 @@ local simple = { local sierpinski = [[function sierpinski(depth) lines = { '*' } - for i = 2, depth + 1 do - sp = string.rep(' ', 2 ^ (i - 2)) + for a = 2, depth + 1 do + sp = string.rep(' ', 2 ^ (a - 2)) tmp = {} -- comment for idx, line in ipairs(lines) do tmp[idx] = sp .. line .. sp @@ -156,7 +156,7 @@ local meta = [[ --- @param node token --- @return table -function M:extract_comments(node) +function M:parse_comments(node) local lfi = node.lineinfo.first local lla = node.lineinfo.last local comments = {} @@ -315,7 +315,7 @@ local full = { { line = 21, name = 'color', type = 'global', }, }, {})), prep(meta, SemanticInfo({ - { line = 3, name = 'M:extract_comments', type = 'method', }, + { line = 3, name = 'M:parse_comments', type = 'method', }, { line = 4, name = 'lfi', type = 'local', }, { line = 5, name = 'lla', type = 'local', }, { line = 6, name = 'comments', type = 'local', }, diff --git a/tests/interpreter/analyzer_spec.lua b/tests/interpreter/analyzer_spec.lua index 37d62aec..97754efa 100644 --- a/tests/interpreter/analyzer_spec.lua +++ b/tests/interpreter/analyzer_spec.lua @@ -11,10 +11,6 @@ if not orig_print then _G.orig_print = print end --- when testing with Lua5.3 -if not _G.unpack then - _G.unpack = table.unpack -end local w = 64 @@ -88,6 +84,7 @@ describe('analyzer #analyzer', function() end local ct, _ = do_code(v, seen_comments) + for _, cl in ipairs(string.lines(ct) or {}) do table.insert(result, cl) end diff --git a/tests/interpreter/ast_spec.lua b/tests/interpreter/ast_spec.lua index 67fd1d34..38ed750b 100644 --- a/tests/interpreter/ast_spec.lua +++ b/tests/interpreter/ast_spec.lua @@ -68,38 +68,39 @@ describe('parser #ast', function() io.write(string.rep('=', 80)) print(Debug.text_table(input)) end - local has_lines = false - local seen_comments = {} - for _, v in ipairs(r) do - if show_ast then - local fn = string.format('%s_input_%d', tag, i) - local skip_lineinfo = true - local tree = Debug.terse_ast(r, skip_lineinfo) - local f = string.format('/*\n%s\n*/\n%s', - string.unlines(input), - tree - ) - FS.write_tempfile(f, 'json5', fn) - end + it('matches ' .. i, function() + local has_lines = false + local seen_comments = {} + for _, v in ipairs(r) do + if show_ast then + local fn = string.format('%s_input_%d', tag, i) + + local skip_lineinfo = false + local tree = Debug.terse_ast(r, skip_lineinfo) + local f = string.format('/*\n%s\n*/\n%s', + string.unlines(input), + tree + ) + FS.write_tempfile(f, 'json5', fn) + end - has_lines = true - local ct, _ = do_code(v, seen_comments) - for _, cl in ipairs(string.lines(ct) or {}) do - table.insert(result, cl) + has_lines = true + local ct, _ = do_code(v, seen_comments) + for _, cl in ipairs(string.lines(ct) or {}) do + table.insert(result, cl) + end + end + --- corner case, e.g comments only + --- it is valid code, but gets parsed a bit differently + if not has_lines then + result = string.lines(do_code(r)) or {} end - end - --- corner case, e.g comments only - --- it is valid code, but gets parsed a bit differently - if not has_lines then - result = string.lines(do_code(r)) or {} - end - --- remove trailing newline - if result[#result] == '' then - table.remove(result) - end - it('matches ' .. i, function() + --- remove trailing newline + if result[#result] == '' then + table.remove(result) + end assert.same(output, result) assert.is_true(parser.parse(output)) end) diff --git a/tests/util/string_spec.lua b/tests/util/string_spec.lua index 04751797..3849f30c 100644 --- a/tests/util/string_spec.lua +++ b/tests/util/string_spec.lua @@ -97,8 +97,8 @@ describe("StringUtils #string", function() local sierpinski = { 'sierpinski = function(depth)', ' lines = { "*" }', - ' for i = 2, depth + 1 do', - ' sp = string.rep(" ", 2 ^ (i - 2))', + ' for k = 2, depth + 1 do', + ' sp = string.rep(" ", 2 ^ (k - 2))', ' tmp = { }', ' -- comment', ' for idx, line in ipairs(lines) do', diff --git a/web/wiki/convert.sh b/web/wiki/convert.sh new file mode 100755 index 00000000..ffb48dc8 --- /dev/null +++ b/web/wiki/convert.sh @@ -0,0 +1,50 @@ +#!/bin/bash -e + +### sanity checks + +[ -z "$1" ] && { + echo 'Provide an export xml' > /dev/stderr + exit 13 +} + +which yq > /dev/null +which jq > /dev/null +which pandoc > /dev/null + +### + +IN="$(realpath "$1")" + +TMPDIR=$(mktemp -d) +echo "$TMPDIR" +cd "$TMPDIR" || exit + +WM=wmark +MD=md + +mkdir -p $WM +mkdir -p $MD + +TMP=$(mktemp XXXXX.json) +cat "$IN" | yq -p xml '.mediawiki.page' -ojson \ + | jq 'map( {title: .title, content: .revision.text."+content" } )' \ + > "$TMP" + +jq -c '.[]' "$TMP" | while read -r obj; do + key=$(echo "$obj" | jq -r '.title') + value=$(echo "$obj" | jq -r '.content') + + if [[ "$key" == */* ]] + then + echo "Warning: '/' in title $key" > /dev/stderr + else + printf "%s\n" "$value" > "$WM/${key}.mediawiki" + fi +done + +for m in "$WM"/* +do + NAME=$(basename "$m" mediawiki) + pandoc -f mediawiki -t markdown "$m" -o $MD/"$NAME".md \ + || echo 'parse error in' "$NAME" > /dev/stderr +done diff --git a/web/wiki/md/.gitkeep b/web/wiki/md/.gitkeep new file mode 100644 index 00000000..e69de29b