diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml
index 3abe56da..ddc2038b 100644
--- a/.github/workflows/package.yml
+++ b/.github/workflows/package.yml
@@ -321,6 +321,7 @@ jobs:
name: ${{ env.IDE_DEBUG_APK_NAME }}
path: ${{ env.ARTIFACT_FOLDER }}
- uses: actions/download-artifact@v4
+ continue-on-error: true
with:
name: ${{ env.IDE_APK_NAME }}
path: ${{ env.ARTIFACT_FOLDER }}
@@ -329,6 +330,7 @@ jobs:
name: ${{ env.PLAYER_DEBUG_APK_NAME }}
path: ${{ env.ARTIFACT_FOLDER }}
- uses: actions/download-artifact@v4
+ continue-on-error: true
with:
name: ${{ env.PLAYER_APK_NAME }}
path: ${{ env.ARTIFACT_FOLDER }}
diff --git a/README.md b/README.md
index f8cdf686..c67ca98f 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ a project must be selected first.
| :---------------------------------------------------------------- | :-------------------------------------------- |
| Clear terminal | Ctrl+L |
| Stop project | Ctrl+Shift+S |
-| Quit project (stop and close) | Ctrl+Shift+Q |
+| Quit project (stop and close) | Ctrl+Q |
| Reset application to initial state | Ctrl+Shift+R |
| Reset project to initial state | Ctrl+Alt+R |
| Exit application | Ctrl+Esc |
@@ -59,6 +59,8 @@ a project must be selected first.
| Move selection | Ctrl+⇧/⇩ |
| Replace selection with input | Enter ⏎ |
| _additionally_ | |
+| Insert input contents before selection | Ctrl+Enter ⏎ |
+| Insert empty block before current (if input is empty) | Shift+Enter ⏎ |
| Delete selected block | Ctrl+Delete |
| Delete selected block (if input is empty) | Ctrl+Y |
| Wipe input | Ctrl+W |
@@ -70,7 +72,8 @@ a project must be selected first.
| Scroll down by one line | Shift+PageDown |
| Move selection to start | Ctrl+Home |
| Move selecion to end | Ctrl+End |
-| Stop editor | Ctrl+Shift+S |
+| Close editor buffer | Ctrl+S |
+| Stop editor (close all buffers) | Ctrl+Shift+S |
| _move mode_ | |
| Switch to moving ("pick up" selection) | Ctrl+M |
| Move selection | ⇧/⇩ |
@@ -82,13 +85,13 @@ a project must be selected first.
| Search definitions | Ctrl+F |
| Exit search | Esc |
| Jump to selected definition | Enter ⏎ |
-| Edit required file / return to previous | Ctrl+O |
+| Edit required file under highlight | Ctrl+O |
## Projects
A _project_ is a folder in the application's storage which
contains at least a `main.lua` file. Projects can be loaded and
-ran. At any time, pressing Ctrl-Shift-Q quits and
+ran. At any time, pressing Ctrl-Q quits and
returns to the console
- `list_projects()`
diff --git a/justfile b/justfile
index e7239e70..1be244fa 100644
--- a/justfile
+++ b/justfile
@@ -15,11 +15,16 @@ WEBDIST-c := "./dist/web-c"
# run unit tests on file change
unit_test:
- @{{MON}} --exec 'echo -en "\n\n\n\n------------- BUSTED -------------\n"; busted tests' -e 'lua'
+ @{{MON}} -e 'lua' --exec 'echo -en "\n\n\n\n------------- BUSTED -------------\n"; busted tests'
+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 --tags {{TAG}}'
+ @{{MON}} -e lua \
+ --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
+unit_test_src:
+ @SHOW_CODE=1 just unit_test_tag src
unit_test_parser:
@PARSER_DEBUG=1 just unit_test_tag parser
unit_test_analyzer:
diff --git a/src/assets/sounds/beep.ogg b/src/assets/sounds/beep.ogg
new file mode 100644
index 00000000..ed844367
Binary files /dev/null and b/src/assets/sounds/beep.ogg differ
diff --git a/src/assets/sounds/blast.ogg b/src/assets/sounds/blast.ogg
new file mode 100644
index 00000000..a52b80fa
Binary files /dev/null and b/src/assets/sounds/blast.ogg differ
diff --git a/src/assets/sounds/boom.ogg b/src/assets/sounds/boom.ogg
new file mode 100644
index 00000000..e83112fa
Binary files /dev/null and b/src/assets/sounds/boom.ogg differ
diff --git a/src/assets/sounds/correct.ogg b/src/assets/sounds/correct.ogg
new file mode 100644
index 00000000..ec823882
Binary files /dev/null and b/src/assets/sounds/correct.ogg differ
diff --git a/src/assets/sounds/gameover.ogg b/src/assets/sounds/gameover.ogg
new file mode 100644
index 00000000..abbc36b8
Binary files /dev/null and b/src/assets/sounds/gameover.ogg differ
diff --git a/src/assets/sounds/hyperjump.ogg b/src/assets/sounds/hyperjump.ogg
new file mode 100644
index 00000000..4ec9c9fd
Binary files /dev/null and b/src/assets/sounds/hyperjump.ogg differ
diff --git a/src/assets/sounds/jump.ogg b/src/assets/sounds/jump.ogg
new file mode 100644
index 00000000..72251fa3
Binary files /dev/null and b/src/assets/sounds/jump.ogg differ
diff --git a/src/assets/sounds/knock.ogg b/src/assets/sounds/knock.ogg
new file mode 100644
index 00000000..cbb58d73
Binary files /dev/null and b/src/assets/sounds/knock.ogg differ
diff --git a/src/assets/sounds/lose.ogg b/src/assets/sounds/lose.ogg
new file mode 100644
index 00000000..a431ecc8
Binary files /dev/null and b/src/assets/sounds/lose.ogg differ
diff --git a/src/assets/sounds/pew.ogg b/src/assets/sounds/pew.ogg
new file mode 100644
index 00000000..c3f8c633
Binary files /dev/null and b/src/assets/sounds/pew.ogg differ
diff --git a/src/assets/sounds/ping.ogg b/src/assets/sounds/ping.ogg
new file mode 100644
index 00000000..1613f7ae
Binary files /dev/null and b/src/assets/sounds/ping.ogg differ
diff --git a/src/assets/sounds/punch.ogg b/src/assets/sounds/punch.ogg
new file mode 100644
index 00000000..31477fed
Binary files /dev/null and b/src/assets/sounds/punch.ogg differ
diff --git a/src/assets/sounds/rawr.ogg b/src/assets/sounds/rawr.ogg
new file mode 100644
index 00000000..acf1f69d
Binary files /dev/null and b/src/assets/sounds/rawr.ogg differ
diff --git a/src/assets/sounds/shot.ogg b/src/assets/sounds/shot.ogg
new file mode 100644
index 00000000..1105676f
Binary files /dev/null and b/src/assets/sounds/shot.ogg differ
diff --git a/src/assets/sounds/sword.ogg b/src/assets/sounds/sword.ogg
new file mode 100644
index 00000000..0405c96f
Binary files /dev/null and b/src/assets/sounds/sword.ogg differ
diff --git a/src/assets/sounds/toggle.ogg b/src/assets/sounds/toggle.ogg
new file mode 100644
index 00000000..381ae109
Binary files /dev/null and b/src/assets/sounds/toggle.ogg differ
diff --git a/src/assets/sounds/win.ogg b/src/assets/sounds/win.ogg
new file mode 100644
index 00000000..33da9b3d
Binary files /dev/null and b/src/assets/sounds/win.ogg differ
diff --git a/src/assets/sounds/wow.ogg b/src/assets/sounds/wow.ogg
new file mode 100644
index 00000000..60fcf55b
Binary files /dev/null and b/src/assets/sounds/wow.ogg differ
diff --git a/src/assets/sounds/wrong.ogg b/src/assets/sounds/wrong.ogg
new file mode 100644
index 00000000..8fbf6bd8
Binary files /dev/null and b/src/assets/sounds/wrong.ogg differ
diff --git a/src/conf.lua b/src/conf.lua
index 0e41f9a9..93d2e4db 100644
--- a/src/conf.lua
+++ b/src/conf.lua
@@ -78,11 +78,11 @@ function love.conf(t)
t.identity = 'compy'
t.window.resizable = false
+ local width = 1024
+ local height = 600
if start.mode ~= 'play' then
local hidpi = os.getenv("HIDPI")
- local width = 1024
- local height = 600
if hidpi == 'true' or hidpi == 'TRUE' then
t.window.width = width * 2
t.window.height = height * 2
@@ -103,6 +103,9 @@ function love.conf(t)
if string.len(gp) > 0 then
title = string.format('%s - %s', 'Compy Player', gp)
end
+ t.window.resizable = true
+ love.fixHeight = height
+ love.fixWidth = width
t.window.title = title
end
love.test_grid_x = 4
diff --git a/src/controller/consoleController.lua b/src/controller/consoleController.lua
index 4a4213a0..d4b52d06 100644
--- a/src/controller/consoleController.lua
+++ b/src/controller/consoleController.lua
@@ -10,6 +10,13 @@ require("util.key")
require("util.table")
local TerminalTest = require("util.test_terminal")
+local messages = {
+ file_does_not_exist = function(name)
+ local n = name or ''
+ return 'cannot open ' .. n .. ': No such file or directory'
+ end,
+}
+
--- @class ConsoleController
--- @field time number
--- @field model Model
@@ -161,26 +168,30 @@ function ConsoleController:run_project(name)
{ "There's already a project running!" })
return
end
- local P = self.model.projects
+ local P = self.model.projects
+ local cur = P.current
local ok
- if P.current then
+ if cur and (not name or cur.name == name) then
ok = true
else
ok = self:open_project(name, false)
end
+
if ok then
- local runner_env = self:get_project_env()
+ local runner_env = self:get_project_env()
local f, load_err, path = P:run(name, runner_env)
if f then
local n = name or P.current.name or 'project'
Log.info('Running \'' .. n .. '\'')
+ love.state.app_state = 'running'
local rok, run_err = run_user_code(f, self, path)
- if rok then
- if self.main_ctrl.user_is_blocking() then
- love.state.app_state = 'running'
- end
- else
+ if not rok then
+ love.state.app_state = 'project_open'
print('Error: ', run_err)
+ else
+ if not self.main_ctrl.user_is_blocking() then
+ love.state.app_state = 'project_open'
+ end
end
else
--- TODO extract error message here
@@ -189,32 +200,45 @@ function ConsoleController:run_project(name)
end
end
-_G.o_require = _G.require
---- @param cc ConsoleController
+local o_require = _G.require
+_G.o_require = o_require
--- @param name string
-local function project_require(cc, name)
+--- @param run 'run'?
+local function project_require(name, run)
+ if run then
+ Log.info('req', name)
+ end
+ if _G.web and name == 'bit' then
+ return o_require('util.luabit')
+ else
+ return o_require(name)
+ end
+end
+
+_G.o_dofile = _G.dofile
+--- @param cc ConsoleController
+--- @param filename string
+--- @param env LuaEnv?
+local function project_dofile(cc, filename, env)
local P = cc.model.projects
- local fn = name .. '.lua'
+ local fn = filename
local open = P.current
if open then
local chunk = open:load_file(fn)
- local pr_env = cc:get_project_env()
if chunk then
- setfenv(chunk, pr_env)
- return chunk()
- else
- --- hack around love.js not having the bit lib
- if name == 'bit' and _G.web then
- ---@diagnostic disable-next-line: inject-field
- pr_env.bit = o_require('util.luabit')
+ if env then
+ setfenv(chunk, env)
end
+ return true, chunk()
+ else
+ print(messages.file_does_not_exist(filename))
end
- --- TODO: is it desirable to allow out-of-project require?
- -- else
- -- _require(name)
end
end
+-- Set up audio table
+local compy_audio = require("util.audio")
+
function ConsoleController.prepare_env(cc)
local prepared = cc.main_env
prepared.gfx = love.graphics
@@ -222,7 +246,7 @@ function ConsoleController.prepare_env(cc)
local P = cc.model.projects
prepared.require = function(name)
- return project_require(cc, name)
+ return project_require(name)
end
--- @param f function
@@ -234,6 +258,14 @@ function ConsoleController.prepare_env(cc)
end
end
+ prepared.require = project_require
+
+ prepared.dofile = function(name)
+ return check_open_pr(function()
+ return project_dofile(cc, name)
+ end)
+ end
+
prepared.list_projects = function()
local ps = P:list()
if ps:is_empty() then
@@ -344,7 +376,8 @@ function ConsoleController.prepare_env(cc)
clear = function()
return terminal:clear()
end
- }
+ },
+ audio = compy_audio,
}
prepared.compy = compy_namespace
prepared.tty = compy_namespace.terminal
@@ -375,9 +408,19 @@ function ConsoleController.prepare_project_env(cc)
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(cc, name)
+ return project_require(name)
end
+ project_env.dofile = function(name)
+ return project_dofile(cc, name, cc:get_project_env())
+ end
+ -- project_env.require = function(name)
+ -- return project_require(name, 'run')
+ -- end
--- @param msg string?
project_env.pause = function(msg)
@@ -548,13 +591,13 @@ function ConsoleController:restart()
end
---@return LuaEnv
-function ConsoleController:get_console_env()
- return self.main_env
+function ConsoleController:get_pre_env_c()
+ return table.clone(self.pre_env)
end
---@return LuaEnv
-function ConsoleController:get_pre_env_c()
- return table.clone(self.pre_env)
+function ConsoleController:get_console_env()
+ return self.main_env
end
---@return LuaEnv
@@ -567,6 +610,18 @@ function ConsoleController:get_base_env()
return self.base_env
end
+---@return LuaEnv
+function ConsoleController:get_effective_env()
+ if
+ love.state.app_state == 'running'
+ or love.state.app_state == 'inspect'
+ then
+ return self:get_project_env()
+ else
+ return self:get_console_env()
+ end
+end
+
---@param t LuaEnv
function ConsoleController:_set_project_env(t)
self.project_env = t
@@ -615,19 +670,20 @@ function ConsoleController:open_project(name, play)
print('No project name provided!')
return false
end
+ local cur = P.current
+ if cur then
+ self:close_project()
+ end
+
local open, create, err = P:opreate(name, play)
local ok = open or create
if ok then
- local project_loader = (function()
- local cached = self.loaders[name]
- if cached then
- return cached
- else
- local loader = P.current:get_loader()
- self:cache_loader(name, loader)
- return loader
- end
- end)()
+ local project_loader =
+ P.current:get_loader(function()
+ return self:get_effective_env()
+ end)
+ self:cache_loader(name, project_loader)
+
if not table.is_member(package.loaders, project_loader)
then
table.insert(package.loaders, 1, project_loader)
@@ -664,7 +720,30 @@ function ConsoleController:close_project()
return true
end
+--- @return Project?
+function ConsoleController:get_current_project()
+ local P = self.model.projects
+ return P.current
+end
+
+function ConsoleController:evacuate_required()
+ local open = self:get_current_project()
+ if not open then return end
+ local files = open:contents()
+ local lua = '.lua$'
+ for _, v in ipairs(files) do
+ if string.matches(v.name, lua, true) then
+ local fn = v.name
+ local modname = fn:gsub(lua, '')
+ if package.loaded[modname] then
+ package.loaded[modname] = nil
+ end
+ end
+ end
+end
+
function ConsoleController:stop_project_run()
+ self:evacuate_required()
self.main_ctrl.set_default_handlers(self, self.view)
self.main_ctrl.set_love_update(self)
love.state.user_input = nil
@@ -687,14 +766,14 @@ 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
local filename
- if state and state.buffer then
- filename = state.buffer.filename
- else
- filename = name or ProjectService.MAIN
- end
+ -- if state and state.buffer then
+ -- filename = state.buffer.filename
+ -- else
+ filename = name or ProjectService.MAIN
+ -- end
local fpath = p:get_path(filename)
local ex = FS.exists(fpath)
local text
@@ -722,16 +801,26 @@ end
--- @return EditorState?
function ConsoleController:finish_edit()
self.editor:save_state()
- local name, newcontent = self.editor:close()
- local ok, err = self:_writefile(name, newcontent)
+ 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
- --- TODO clear bufferlist
- return self.editor:get_state()
else
- print(err)
+ print(string.unlines(errs))
end
+ self.buffers = {}
+ return self.editor:get_state()
end
--- Handlers ---
@@ -761,6 +850,11 @@ function ConsoleController:keypressed(k)
local function terminal_test()
local out = self.model.output
+ if love.state.app_state ~= 'ready'
+ or love.state.app_state ~= 'project_open'
+ then
+ return
+ end
if not love.state.testing then
love.state.testing = 'running'
input:cancel()
@@ -817,7 +911,7 @@ function ConsoleController:keypressed(k)
self.model.output:reset()
end
if love.DEBUG then
- if k == 't' then
+ if Key.alt() and k == 't' then
terminal_test()
return
end
diff --git a/src/controller/controller.lua b/src/controller/controller.lua
index ed63a764..de98d29a 100644
--- a/src/controller/controller.lua
+++ b/src/controller/controller.lua
@@ -47,6 +47,7 @@ local _C, _mode
--- @param msg string
local function user_error_handler(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)
@@ -400,15 +401,15 @@ Controller = {
local uup = Controller._userhandlers.update
if user_update and uup
then
- if love.state.app_state == 'snapshot' then
- gfx.captureScreenshot(function(img)
- local snap = gfx.newImage(img)
- View.snapshot = snap
- C:suspend()
- end)
- end
wrap(uup, dt)
end
+ if love.state.app_state == 'snapshot' then
+ gfx.captureScreenshot(function(img)
+ local snap = gfx.newImage(img)
+ View.snapshot = snap
+ C:suspend()
+ end)
+ end
if love.harmony then
love.harmony.timer_update(dt)
@@ -537,7 +538,7 @@ Controller = {
handlers.keypressed = function(k)
--- Power shortcuts
local function quickswitch()
- if Key.ctrl() and k == 't' then
+ if Key.ctrl() and not Key.alt() and k == 't' then
if love.state.app_state == 'running'
or love.state.app_state == 'inspect'
or love.state.app_state == 'project_open'
@@ -563,18 +564,22 @@ Controller = {
if k == "pause" then
C:suspend_run(messages.user_break)
end
- if Key.shift() then
- -- Ensure the user can get back to the console
- if k == "q" then
- C:quit_project()
- end
- if k == "s" then
- if love.state.app_state == 'running' then
- C:stop_project_run()
- elseif love.state.app_state == 'editor' then
+ if k == "q" then
+ C:quit_project()
+ end
+ if k == "s" then
+ if love.state.app_state == 'running' then
+ C:stop_project_run()
+ elseif love.state.app_state == 'editor' then
+ if Key.shift() then
+ C:finish_edit()
+ else
C:close_buffer()
end
end
+ end
+ if Key.shift() then
+ --- Ensure the user can get back to the console
if k == "r" then
C:reset()
end
diff --git a/src/controller/editorController.lua b/src/controller/editorController.lua
index b60b92ac..3289c9a1 100644
--- a/src/controller/editorController.lua
+++ b/src/controller/editorController.lua
@@ -69,7 +69,7 @@ function EditorController:open(name, content, save)
return parser.trunc(code, self.model.cfg.view.fold_lines)
end
elseif is_md then
- local mdEval = MdEval(name)
+ local mdEval = MdEval()
hl = mdEval.highlighter
self.input:set_eval(mdEval)
else
@@ -85,7 +85,7 @@ function EditorController:open(name, content, save)
end
--- @private
-function EditorController:_dump_bufferlist()
+function EditorController:_print_bufferlist()
for i, v in ipairs(self.model.buffers) do
Log.debug(i, v.name)
end
@@ -116,16 +116,15 @@ function EditorController:pop_buffer()
bs:pop_front()
local b = bs:first()
self.view:get_current_buffer():open(b)
+ self:update_status()
end
function EditorController:close_buffer()
local bs = self.model.buffers
local n_buffers = bs:length()
if n_buffers < 2 then
- -- Log.debug('fin', n_buffers)
self.console:finish_edit()
else
- -- Log.debug(':bd', n_buffers)
self:pop_buffer()
end
end
@@ -235,13 +234,14 @@ function EditorController:restore_state(state)
end
end
---- @return string name
---- @return Dequeue content
+--- @return {name: string, content: string[]}[]
function EditorController:close()
- local buf = self:get_active_buffer()
self.input:clear()
- local content = buf:get_text_content()
- return buf.name, content
+ local bfs = self.model:get_buffers_content()
+ self.model.buffers = Dequeue()
+ self.view.buffers = {}
+ --- TODO is this needed?
+ return bfs
end
--- @return BufferModel
@@ -269,9 +269,9 @@ function EditorController:_generate_status(sel)
local ct = bufview.content_type
if ct == 'lua' then
local range = bufview.content:get_block_app_pos(sel)
- cs = CustomStatus(ct, len, more, sel, m, range)
+ cs = CustomStatus(buffer.name, ct, len, more, sel, m, range)
else
- cs = CustomStatus(ct, len, more, sel, m)
+ cs = CustomStatus(buffer.name, ct, len, more, sel, m)
end
return cs
@@ -329,10 +329,8 @@ function EditorController:_handle_submit(go)
local sel = buf:get_selection()
local block = buf:get_content():get(sel)
if not block then return end
- --- TODO: why did I do this?
- -- local ln = block.pos.start
- -- if ln then go({ Empty(ln) }) end
else
+ local _, raw_chunks = buf.chunker(raw, true)
local pretty = buf.printer(raw)
if pretty then
inter:set_text(pretty)
@@ -343,6 +341,16 @@ function EditorController:_handle_submit(go)
local ok, res = inter:evaluate()
local _, chunks = buf.chunker(pretty, true)
if ok then
+ if #chunks < #raw_chunks then
+ local rc = raw_chunks
+ if rc[1].tag == 'empty' then
+ table.insert(chunks, 1, Empty(0))
+ end
+ if rc[#rc].tag == 'empty' then
+ local li = chunks[#chunks].pos.fin
+ table.insert(chunks, Empty(li + 1))
+ end
+ end
go(chunks)
else
local eval_err = res
@@ -489,16 +497,23 @@ function EditorController:_normal_mode_keys(k)
local buf = self:get_active_buffer()
local function newline()
- if not Key.ctrl() and Key.shift() and Key.is_enter(k) then
- buf:insert_newline()
- self:save(buf)
- self.view:refresh()
- block_input()
+ if Key.is_enter(k) then
+ --- insert empty block if input is empty
+ if is_empty
+ and (Key.shift() or Key.ctrl())
+ and not Key.alt() then
+ buf:insert_newline()
+ self:save(buf)
+ self.view:refresh()
+ block_input()
+ end
end
end
local function delete_block()
+ local t = string.unlines(buf:get_selected_text())
buf:delete_selected_text()
+ love.system.setClipboardText(t)
self:save(buf)
self.view:refresh()
end
@@ -546,9 +561,9 @@ function EditorController:_normal_mode_keys(k)
end
if is_empty then
- newline()
copycut()
end
+ newline()
paste_k()
@@ -573,29 +588,53 @@ function EditorController:_normal_mode_keys(k)
--- handlers
local function submit()
- if not Key.ctrl() and not Key.shift() and Key.is_enter(k) then
- local bufv = self.view:get_current_buffer()
- local function go(newtext)
+ local bufv = self.view:get_current_buffer()
+ local function replace(newtext)
+ if bufv:is_selection_visible() then
+ if buf:loaded_is_sel(true) then
+ local _, n = buf:replace_content(newtext)
+ buf:clear_loaded()
+ self:save(buf)
+ input:clear()
+ self.view:refresh()
+ self:_move_sel('down', n)
+ load_selection()
+ self:update_status()
+ else
+ buf:select_loaded()
+ bufv:follow_selection()
+ end
+ else
+ bufv:follow_selection()
+ end
+ end
+
+ if Key.ctrl()
+ and not Key.shift()
+ and not Key.alt()
+ and Key.is_enter(k) then
+ local function add(newtext)
if bufv:is_selection_visible() then
- if buf:loaded_is_sel(true) then
- local _, n = buf:replace_selected_text(newtext)
- buf:clear_loaded()
- self:save(buf)
- input:clear()
- self.view:refresh()
- self:_move_sel('down', n)
- load_selection()
- self:update_status()
- else
- buf:select_loaded()
- bufv:follow_selection()
- end
+ local sel = buf:get_selection()
+ local _, n = buf:insert_content(newtext, sel)
+ self:save(buf)
+ self.view:refresh()
+ self:_move_sel('down', n)
+ buf:clear_loaded()
+ self:update_status()
else
bufv:follow_selection()
end
end
- self:_handle_submit(go)
+ self:_handle_submit(add)
+ end
+
+ if not Key.ctrl()
+ and not Key.shift()
+ and not Key.alt()
+ and Key.is_enter(k) then
+ self:_handle_submit(replace)
end
end
local function load()
@@ -666,7 +705,7 @@ function EditorController:_normal_mode_keys(k)
end
-- step into
- if love.DEBUG and Key.ctrl() then
+ if Key.ctrl() then
if k == "o" then
self:follow_require()
end
diff --git a/src/controller/searchController.lua b/src/controller/searchController.lua
index 4f549dc0..5f2806b4 100644
--- a/src/controller/searchController.lua
+++ b/src/controller/searchController.lua
@@ -129,10 +129,14 @@ function SearchController:keypressed(k)
removers()
if Key.is_enter(k) then
+ --- no linebreaks in search
+ if Key.shift() or Key.ctrl() then return end
local sel = self.model.selection
local r = self.model.resultset[sel].r
+ self.input:update_view()
return r
end
+ self.input:update_view()
end
--- @param t string
diff --git a/src/controller/userInputController.lua b/src/controller/userInputController.lua
index 60885cd8..ed087f7f 100644
--- a/src/controller/userInputController.lua
+++ b/src/controller/userInputController.lua
@@ -1,5 +1,6 @@
local class = require('util.class')
require("util.key")
+require("util.view")
require("util.string.string")
--- @param model UserInputModel
@@ -131,7 +132,7 @@ function UserInputController:clear_error()
self.model:clear_error()
end
---- @param error string[]?
+--- @param error string[]|Error[]?
function UserInputController:set_error(error)
self.model:set_error(error)
end
@@ -144,8 +145,9 @@ end
--- @return boolean
--- @return Error[]
function UserInputController:evaluate()
+ local ok, res = self.model:handle(true)
self:update_view()
- return self.model:handle(true)
+ return ok, res
end
function UserInputController:cancel()
diff --git a/src/examples/tixy/main.lua b/src/examples/tixy/main.lua
index da687455..993a50fb 100644
--- a/src/examples/tixy/main.lua
+++ b/src/examples/tixy/main.lua
@@ -3,7 +3,7 @@ math.randomseed(os.time())
cw, ch = gfx.getDimensions()
midx = cw / 2
-require("math")
+require("mathlib")
examples = require("examples")
size = 28
diff --git a/src/examples/tixy/math.lua b/src/examples/tixy/mathlib.lua
similarity index 100%
rename from src/examples/tixy/math.lua
rename to src/examples/tixy/mathlib.lua
diff --git a/src/harmony/scenarios/examples.lua b/src/harmony/scenarios/examples.lua
index d4645658..5671c815 100644
--- a/src/harmony/scenarios/examples.lua
+++ b/src/harmony/scenarios/examples.lua
@@ -23,8 +23,8 @@ local function examples()
wait(.1)
-- wait(.1)
h.love_key('return')
- wait(.1)
- assert(love.state.app_state == 'running')
+ wait(.3)
+ assert(love.state.app_state == 'running')
h.screenshot('turtl')
wait(.3)
h.love_key('space')
diff --git a/src/lib/metalua b/src/lib/metalua
index 72d242fe..26eaa786 160000
--- a/src/lib/metalua
+++ b/src/lib/metalua
@@ -1 +1 @@
-Subproject commit 72d242fe90279ac80a4847f0720f9cb3226b003b
+Subproject commit 26eaa786a330dc4ffc08be7c58c8a189d56e8950
diff --git a/src/main.lua b/src/main.lua
index aaf88408..5591a00d 100644
--- a/src/main.lua
+++ b/src/main.lua
@@ -10,6 +10,7 @@ require("view.consoleView")
local colors = require("conf.colors")
local hostconf = prequire('host')
+require("util.lua")
require("util.key")
require("util.debug")
local FS = require("util.filesystem")
@@ -81,7 +82,12 @@ local config_view = function(flags)
end
local drawableChars = math.floor(drawableWidth / fw)
- if love.DEBUG then drawableChars = drawableChars - 3 end
+
+ if love.DEBUG then
+ drawableChars = parse_int(os.getenv("COMPY_WRAP"))
+ or drawableChars - 3
+ lines = parse_int(os.getenv("COMPY_LINES")) or lines
+ end
return {
font = font_main,
diff --git a/src/model/editor/bufferModel.lua b/src/model/editor/bufferModel.lua
index 75e51a07..207d063a 100644
--- a/src/model/editor/bufferModel.lua
+++ b/src/model/editor/bufferModel.lua
@@ -20,7 +20,6 @@ local function render_blocks(blocks)
ret:append_all(v.lines)
end
end
- ret:append('')
return ret
end
@@ -51,7 +50,10 @@ local function new(
local function plaintext()
ct = 'plain'
_content = Dequeue(lines, 'string')
- sel = #_content + 1
+ if _content:last() ~= '' then
+ _content:push('')
+ end
+ sel = #_content
end
--- only passing this around so the linter shuts up about nil
--- @param chk function
@@ -60,7 +62,7 @@ local function new(
local ok, blocks = chk(lines)
if ok then
local len = #blocks
- sel = len + 1
+ sel = len
else
readonly = true
sel = 1
@@ -153,7 +155,7 @@ function BufferModel:rechunk()
end
function BufferModel:save()
- self:highlight()
+ self:_text_change()
local text = self:get_text_content()
self:analyze()
return self.save_file(text)
@@ -207,7 +209,7 @@ function BufferModel:move_selection(dir, by, warp, move)
local l = self:get_content_length()
local last = (function()
if move then return l end
- return l + 1
+ return l
end)()
if warp then
if dir == 'up' then
@@ -303,9 +305,13 @@ function BufferModel:_text_change(rechunk)
self:rechunk()
self:rechunk()
end
- end
- if self.content_type == 'md' then
+ else
self:highlight()
+ local ll = self.content:last()
+ if ll ~= '' then
+ -- Log.warn('asd')
+ self.content:push('')
+ end
end
end
@@ -315,13 +321,8 @@ function BufferModel:delete_selected_text()
local sb = self.content[sel]
if not sb then return end
- local l = sb.pos:len()
self.content:remove(sel)
- for i = sel, self:get_content_length() do
- local b = self.content[i]
- local r = b.pos
- b.pos = r:translate(-l)
- end
+ self:rechunk()
else
self.content:remove(sel)
end
@@ -329,21 +330,23 @@ function BufferModel:delete_selected_text()
end
--- @param t string[]|Block[]
+--- @param coord integer?
--- @return boolean insert
--- @return integer? inserted_lines
-function BufferModel:replace_selected_text(t)
+function BufferModel:replace_content(t, coord)
if self.content_type == 'lua' then
local chunks = t
local n = #chunks
if n == 0 then
return false
end
- local sel = self.selection
+ local blocknum = coord or self:get_selection()
+ local rechunk = false
--- content start and original length
local cs, ol = (function()
- local current = self.content[sel]
+ local current = self.content[blocknum]
if current then
- return current.pos.start, self.content[sel].pos:len()
+ return current.pos.start, self.content[blocknum].pos:len()
end
local last = self.content:last()
if last then
@@ -357,33 +360,34 @@ function BufferModel:replace_selected_text(t)
local c = chunks[1]
local nr = c.pos:translate(cs - 1)
c.pos = nr
- self.content[sel] = chunks[1]
+ self.content[blocknum] = chunks[1]
else
--- remove old chunk
- self.content:remove(sel)
+ self.content:remove(blocknum)
--- insert new version of the chunk(s)
for i = #chunks, 1, -1 do
local c = chunks[i]
local nr = c.pos:translate(cs - 1)
c.pos = nr
- self.content:insert(c, sel)
+ self.content:insert(c, blocknum)
end
+ rechunk = true
end
--- move subsequent chunks down
local diff = chunks[n].pos:len() - ol
if diff ~= 0 then
- for i = sel + 1, self:get_content_length() do
+ for i = blocknum + 1, self:get_content_length() do
local b = self.content[i]
b.pos = b.pos:translate(diff)
end
end
- self:_text_change()
+ self:_text_change(rechunk)
return true, n
else
- local sel = self.selection
+ local linenum = coord or self:get_selection()
local clen = #(self.content)
- local ti = sel
+ local ti = linenum
if #t == 1 then
self.content[ti] = t[1]
if ti > clen then
@@ -403,26 +407,92 @@ function BufferModel:replace_selected_text(t)
end
--- Insert a new line or empty block _before_ the selection
+--- Returns true if a block/line was inserted
--- @param i integer?
+--- @return boolean?
function BufferModel:insert_newline(i)
--- block or line number
local bln = i or self:get_selection()
if self.content_type == 'lua' then
- local sb = self.content[bln]
- if not sb then return end
+ local b = self.content[bln]
+ if not b then return end
+
local prev_b = self.content[bln - 1]
-- disallow consecutive empties
local prev_empty = prev_b and prev_b:is_empty()
- local sel_empty = sb:is_empty()
+ local sel_empty = b:is_empty()
local cons = prev_empty or sel_empty
if cons then return end
local ln = self:get_selection_start_line()
self.content:insert(Empty(ln), bln)
self:_text_change(true)
+ return true
else
self.content:insert('', bln)
self:_text_change()
+ return true
+ end
+end
+
+--- Insert new lines or blocks
+--- Returns success flag and number of inserted
+--- @param t string[]|Block[]
+--- @param lbn integer?
+--- @return boolean?
+--- @return integer? inserted_lines
+function BufferModel:insert_content(t, lbn)
+ local num = lbn or self:get_selection()
+ local len = self:get_content_length() + 1
+ if lbn < 1 or lbn > len then
+ return false
+ end
+ if self.content_type == 'lua' then
+ local chunks = t
+ local n = #chunks
+ if n == 0 then
+ return false
+ end
+ --- content start and original length
+ local cs, ol = (function()
+ local current = self.content[num]
+ if current then
+ return current.pos.start,
+ self.content[num].pos:len()
+ end
+ local last = self.content:last()
+ if last then
+ return self.content:last().pos.fin + 1, 0
+ else --- empty file
+ return 1, 0
+ end
+ end)()
+
+ for i = #chunks, 1, -1 do
+ local c = chunks[i]
+ local nr = c.pos:translate(cs - 1)
+ c.pos = nr
+ self.content:insert(c, num)
+ end
+
+ --- move subsequent chunks down
+ local diff = chunks[n].pos:len() - ol
+ if diff ~= 0 then
+ for i = num + 1, self:get_content_length() do
+ local b = self.content[i]
+ b.pos = b.pos:translate(diff)
+ end
+ end
+
+ self:_text_change(true)
+ return true, n
+ else
+ local ti = num
+ for i = #t, 1, -1 do
+ self.content:insert(t[i], ti)
+ end
+ self:_text_change()
+ return true, #t
end
end
diff --git a/src/model/editor/content.lua b/src/model/editor/content.lua
index e07586a2..0beb0d22 100644
--- a/src/model/editor/content.lua
+++ b/src/model/editor/content.lua
@@ -30,6 +30,7 @@ end
Chunk = class.create()
--- @param lines str
+--- @param pos Range
--- @return Chunk
function Chunk.new(lines, pos)
local ls = (function()
diff --git a/src/model/editor/editorModel.lua b/src/model/editor/editorModel.lua
index c9fb4cbe..e5e80ca6 100644
--- a/src/model/editor/editorModel.lua
+++ b/src/model/editor/editorModel.lua
@@ -17,3 +17,16 @@ EditorModel = class.create(function(cfg)
cfg = cfg,
}
end)
+
+--- @return {name: string, content: string[]}[]
+function EditorModel:get_buffers_content()
+ local ret = {}
+ for _, buf in ipairs(self.buffers) do
+ local b = {
+ name = buf.name,
+ content = buf:get_text_content(),
+ }
+ table.insert(ret, b)
+ end
+ return ret
+end
diff --git a/src/model/input/history.lua b/src/model/input/history.lua
index d40e2284..cd02fc95 100644
--- a/src/model/input/history.lua
+++ b/src/model/input/history.lua
@@ -26,6 +26,14 @@ function History:remember(input)
if not self.index then
self:append(input)
return true
+ else
+ local entry = self:get(self.index)
+ local new = input or {}
+ local hist = string.unlines(entry)
+ local new_s = string.unlines(new)
+ if hist ~= new_s then
+ self:append(input)
+ end
end
end
return false
@@ -97,8 +105,9 @@ function History:_get_entries()
end
--- For debug purposes, log content
+--- @private
--- @param f function
-function History:_dump(f)
+function History:_dbgprint(f)
local log = f or Log.debug
local h = self:items()
local t = table.map(h, function(t)
@@ -106,5 +115,5 @@ function History:_dump(f)
end)
local i = self.index or '-'
local l = ' [' .. self:length() .. '] '
- log(i .. l .. Debug.text_table(t, false, nil, 64) )
+ log(i .. l .. Debug.text_table(t, false, nil, 64))
end
diff --git a/src/model/input/userInputModel.lua b/src/model/input/userInputModel.lua
index badb427b..97d0d537 100644
--- a/src/model/input/userInputModel.lua
+++ b/src/model/input/userInputModel.lua
@@ -820,8 +820,11 @@ function UserInputModel:handle(eval)
end
end
else
+ --- @TODO fix
local perr = result[1]
+ -- Log.debug(Debug.terse_t(perr, nil, nil, true))
if perr then
+ --- @TODO check line len and move to next if at end
if perr.c then
local c = perr.c
if c > 1 then c = c + 1 end
@@ -848,20 +851,29 @@ end
--- @return string[]?
function UserInputModel:get_wrapped_error()
if self.error then
+ local e = table.map(self.error, function(er)
+ return tostring(er)
+ end)
local we = string.wrap_array(
- self.error,
+ e,
self.visible.wrap_w)
- table.insert(we, 1, 'Errors:')
+ table.insert(we, 1, 'Errors:')
return we
end
end
+--- @TODO support error[] and string[]
--- @param errors Error[]?
function UserInputModel:set_error(errors)
- self.error = {}
+ self.error = nil
if type(errors) == "table" then
- for _, e in ipairs(errors) do
- table.insert(self.error, tostring(e))
+ if type(errors[1] == "string") then
+ self.error = errors
+ else
+ self.error = {}
+ for _, e in ipairs(errors) do
+ table.insert(self.error, tostring(e))
+ end
end
end
end
diff --git a/src/model/lang/lua/parser.lua b/src/model/lang/lua/parser.lua
index ad3cf448..714612ca 100644
--- a/src/model/lang/lua/parser.lua
+++ b/src/model/lang/lua/parser.lua
@@ -381,6 +381,8 @@ return function(lib)
end
end
+ --- multiline empty collapse offset
+ local of = 0
for _, v in ipairs(r) do
has_lines = true
local li = v.lineinfo
@@ -389,14 +391,15 @@ return function(lib)
local comments = ast_extract_comments(v, {}, wrap)
get_comments(comments, 'first')
-
- -- account for empty lines, including the zeroth
+ --- account for empty lines, including the zeroth
if fl > last + 1 then
- ret:insert(Empty(last + 1), idx)
+ ret:insert(Empty(last + 1 - of), idx)
idx = idx + 1
+ last = last + 1
+ of = of + (fl - last - 1)
end
local tex = table.slice(text or {}, fl, ll)
- local chunk = Chunk(tex, Range(fl, ll))
+ local chunk = Chunk(tex, Range(fl - of, ll - of))
ret:insert(chunk, idx)
idx = idx + 1
last = ll
@@ -410,13 +413,23 @@ return function(lib)
get_comments(single_comment, 'first')
end
+ if
+ ret:last().tag ~= 'empty' and (
+ --- no empty line at EOF
+ not single
+ --- there is an empty line at the end
+ or single and (#string.lines(text) > last)
+ )
+ then
+ ret:push_back(Empty(last + 1 - of))
+ end
return true, ret, r
else
--- content is not valid lua
return false, Dequeue(text, 'string'), r
end
else
- return true, Dequeue(Empty(1), 'block'), r
+ return true, Dequeue({ Empty(1) }, 'block'), r
end
end
diff --git a/src/model/project/project.lua b/src/model/project/project.lua
index 12ea4f40..97afdb1f 100644
--- a/src/model/project/project.lua
+++ b/src/model/project/project.lua
@@ -113,14 +113,18 @@ function Project:load_file(filename)
end
end
+--- @param get_env fun(): LuaEnv
--- @return function
-function Project:get_loader()
+function Project:get_loader(get_env)
--- @param modname string
--- @return unknown|string
return function(modname)
local fn = modname .. '.lua'
local f = self:load_file(fn)
+ Log.debug(string.format("%s loader, loading %s",
+ self.name, modname))
if f then
+ setfenv(f, get_env())
return assert(f)
else
return string.format(
@@ -289,11 +293,11 @@ function ProjectService:opreate(name, play)
local ok = self:open('play', true)
return ok, false
else
- local ook, _ = self:open(name)
+ local ook, _ = self:open(name, false)
if ook then
return ook, false
else
- local cok, c_err = self:create(name)
+ local cok, c_err = self:create(name, false)
if cok then
self:open(name)
return false, cok
@@ -362,7 +366,7 @@ function ProjectService:clone(old, new)
end
--- @param name string
---- @param env table
+--- @param env LuaEnv
--- @return function?
--- @return string? error
--- @return string? path
diff --git a/src/util/audio.lua b/src/util/audio.lua
new file mode 100644
index 00000000..7b005ce1
--- /dev/null
+++ b/src/util/audio.lua
@@ -0,0 +1,35 @@
+-- Table to become compy.audio
+local audio = { }
+
+-- Add noises to the audio table
+local names = {
+ "beep",
+ "blast",
+ "boom",
+ "correct",
+ "gameover",
+ "hyperjump",
+ "jump",
+ "knock",
+ "lose",
+ "pew",
+ "ping",
+ "punch",
+ "rawr",
+ "shot",
+ "sword",
+ "toggle",
+ "win",
+ "wow",
+ "wrong"
+}
+for _,name in pairs(names) do
+ local filename = "assets/sounds/"..name..".ogg"
+ local source = love.audio.newSource(filename, "static")
+ audio[name] = function()
+ love.audio.stop(source)
+ love.audio.play(source)
+ end
+end
+
+return audio
diff --git a/src/util/debug.lua b/src/util/debug.lua
index 1209f057..4e22aa38 100644
--- a/src/util/debug.lua
+++ b/src/util/debug.lua
@@ -150,12 +150,12 @@ local function terse_array(a, skip)
end
end
---- @alias dumpstyle
+--- @alias tablestyle
--- | 'lua'
--- | 'json5'
--- @param ast token[]?
--- @param skip_lineinfo boolean?
---- @param style dumpstyle?
+--- @param style tablestyle?
--- @return string
local function terse_ast(ast, skip_lineinfo, style)
if type(ast) ~= 'table' then
@@ -165,7 +165,7 @@ local function terse_ast(ast, skip_lineinfo, style)
--- @param t table?
--- @param omit any[]?
- --- @param style dumpstyle?
+ --- @param style tablestyle?
--- @param level integer?
--- @param prev_seen table?
--- @return string
@@ -320,7 +320,7 @@ Debug = {
if type(t) == 'table' then
local start = math.max(1, skip or 0)
for i = start, #t do
- local l = t[i]
+ local l = t[i] or ''
local line = (function()
if not no_ln then
return string.format("#%02d: %s\n", i, text(l))
@@ -495,6 +495,7 @@ local error = function(...)
printer(s)
end
local debug = function(...)
+ if love and not love.DEBUG then return end
local args = { ... }
local ts = string.format("%.3f ", os.clock())
local s = annot(ts .. 'DEBUG ',
@@ -502,7 +503,7 @@ local debug = function(...)
printer(s)
end
local once = function(...)
- if not love.DEBUG then return end
+ if not love or not love.DEBUG then return end
local args = { ... }
local key = love.debug.once .. string.join(args, '')
local kh = hash(key)
diff --git a/src/util/lua.lua b/src/util/lua.lua
index de1572fa..35407a05 100644
--- a/src/util/lua.lua
+++ b/src/util/lua.lua
@@ -34,6 +34,9 @@ local t = {
b2s = function(b)
return b and '#t' or '#f'
end,
+ parse_int = function(s)
+ return tonumber(s or '', 10)
+ end
}
for k, v in pairs(t) do
diff --git a/src/view/editor/bufferView.lua b/src/view/editor/bufferView.lua
index 4f43eb64..1926a2f3 100644
--- a/src/view/editor/bufferView.lua
+++ b/src/view/editor/bufferView.lua
@@ -84,7 +84,6 @@ function BufferView:open(buffer)
local ir = self:_get_end_range()
self:_update_visible(ir)
- if off > 0 then self:scroll('down', 1) end
end
--- @private
@@ -205,7 +204,7 @@ end
--- @param ln integer
function BufferView:scroll_to_line(ln)
- local off = self.content.wrap_forward[ln][1]
+ local off = self.content.wrap_forward[ln][1] or 0
self:scroll_to(off)
end
diff --git a/src/view/editor/editorView.lua b/src/view/editor/editorView.lua
index a9d1cba0..8d122dc5 100644
--- a/src/view/editor/editorView.lua
+++ b/src/view/editor/editorView.lua
@@ -8,7 +8,7 @@ local class = require('util.class')
--- @param cfg ViewConfig
--- @param ctrl EditorController
local function new(cfg, ctrl)
- local ev = {
+ local self = {
cfg = cfg,
controller = ctrl,
input = UserInputView(cfg, ctrl.input),
@@ -16,8 +16,8 @@ local function new(cfg, ctrl)
search = SearchView(cfg, ctrl.search),
}
--- hook the view in the controller
- ctrl:init_view(ev)
- return ev
+ ctrl:init_view(self)
+ return self
end
--- @class EditorView : ViewBase
diff --git a/src/view/input/customStatus.lua b/src/view/input/customStatus.lua
index 19e2d32c..72f423de 100644
--- a/src/view/input/customStatus.lua
+++ b/src/view/input/customStatus.lua
@@ -1,6 +1,7 @@
local class = require('util.class')
--- @class CustomStatus table
+--- @field name string
--- @field content_type ContentType
--- @field buflen integer
--- @field buffer_more More
@@ -8,8 +9,9 @@ local class = require('util.class')
--- @field mode EditorMode
--- @field range Range?
CustomStatus = class.create(
- function(ct, len, more, sel, mode, range)
+ function(name, ct, len, more, sel, mode, range)
return {
+ name = name,
content_type = ct,
buflen = len,
buffer_more = more,
diff --git a/src/view/input/statusline.lua b/src/view/input/statusline.lua
index 46742572..71fabfd2 100644
--- a/src/view/input/statusline.lua
+++ b/src/view/input/statusline.lua
@@ -78,8 +78,9 @@ function Statusline:draw(status, start_y)
midX - (8 * cf.fw),
start_text.y + corr)
end
- local lw = font:getWidth(state) / 2
- gfx.print((state or '???'),
+ local sl = state or '???'
+ local lw = font:getWidth(sl) / 2
+ gfx.print((sl),
midX - lw, start_text.y)
gfx.setColor(colors.fg)
end
@@ -101,15 +102,19 @@ function Statusline:draw(status, start_y)
end
local more_b = morelabel(custom.buffer_more) .. ' '
local more_i = morelabel(status.input_more) .. ' '
+ local name = custom.name .. ' '
gfx.setColor(colors.fg)
- local w_il = gfx.getFont():getWidth(" 999:9999")
- local w_br = gfx.getFont():getWidth("B999 L999-999(99)")
- local w_mb = gfx.getFont():getWidth(" ↕↕ ")
- local w_mi = gfx.getFont():getWidth(" ↕↕ ")
+ local font = gfx.getFont()
+ local w_il = font:getWidth(" 999:9999")
+ local w_br = font:getWidth("B999 L999-999(99)")
+ local w_mb = font:getWidth(" ↕↕ ")
+ local w_mi = font:getWidth(" ↕↕ ")
local s_mb = endTextX - w_br - w_il - w_mi - w_mb
- local cw_p = gfx.getFont():getWidth(t_blp)
- local cw_il = gfx.getFont():getWidth(t_ic)
+ local w_n = font:getWidth(name)
+ local s_n = s_mb - w_n - 5
+ local cw_p = font:getWidth(t_blp)
+ local cw_il = font:getWidth(t_ic)
local sxl = endTextX - (cw_p + w_il + w_mi)
local s_mi = endTextX - w_il
@@ -146,6 +151,9 @@ function Statusline:draw(status, start_y)
--- buffer more
gfx.setColor(colors.fg)
gfx.print(more_b, s_mb, start_text.y)
+ -- filename
+ gfx.setColor(Color[Color.white])
+ gfx.print(custom.name, s_n, start_text.y)
else
--- normal statusline
local pos_c = ':' .. c.c
diff --git a/tests/brief_output.lua b/tests/brief_output.lua
new file mode 100644
index 00000000..c73a892b
--- /dev/null
+++ b/tests/brief_output.lua
@@ -0,0 +1,234 @@
+local s = require 'say'
+local pretty = require 'pl.pretty'
+local term = require 'term'
+local luassert = require 'luassert'
+local io = io
+local type = type
+local string_format = string.format
+local string_gsub = string.gsub
+local io_write = io.write
+local io_flush = io.flush
+local pairs = pairs
+local colors
+
+local isatty = io.type(io.stdout) == 'file' and term.isatty(io.stdout)
+
+return function(options)
+ local busted = require 'busted'
+ local handler = require 'busted.outputHandlers.base' ()
+ local cli = require 'cliargs'
+ local args = options.arguments
+
+ cli:set_name('utfTerminal output handler')
+ cli:flag('--color', 'force use of color')
+ cli:flag('--plain', 'force use of no color')
+
+ local cliArgs, err = cli:parse(args)
+ if not cliArgs and err then
+ io.stderr:write(string.format('%s: %s\n\n', cli.name, err))
+ io.stderr:write(cli.printer.generate_help_and_usage() .. '\n')
+ os.exit(1)
+ end
+
+ if cliArgs.plain then
+ colors = setmetatable({}, {
+ __index = function()
+ return function(s) return s end
+ end
+ })
+ luassert:set_parameter("TableErrorHighlightColor", "none")
+ elseif cliArgs.color then
+ colors = require 'term.colors'
+ luassert:set_parameter("TableErrorHighlightColor", "red")
+ else
+ if package.config:sub(1, 1) == '\\' and not os.getenv("ANSICON") or not isatty then
+ -- Disable colors on Windows.
+ colors = setmetatable({}, { __index = function() return function(s) return s end end })
+ luassert:set_parameter("TableErrorHighlightColor", "none")
+ else
+ colors = require 'term.colors'
+ luassert:set_parameter("TableErrorHighlightColor", "red")
+ end
+ end
+
+ local successDot = colors.green('\226\151\143') -- '\226\151\143' = '●' = utf8.char(9679)
+ local failureDot = colors.red('\226\151\188') -- '\226\151\188' = '◼' = utf8.char(9724)
+ local errorDot = colors.magenta('\226\156\177') -- '\226\156\177' = '✱' = utf8.char(10033)
+ local pendingDot = colors.yellow('\226\151\140') -- '\226\151\140' = '◌' = utf8.char(9676)
+
+ local pendingDescription = function(pending)
+ local name = pending.name
+
+ -- '\226\134\146' = '→' = utf8.char('8594')
+ local string = colors.yellow(s('output.pending')) .. ' \226\134\146 ' ..
+ colors.cyan(pending.trace.short_src) .. ' @ ' ..
+ colors.cyan(pending.trace.currentline) ..
+ '\n' .. colors.bright(name)
+
+ if type(pending.message) == 'string' then
+ string = string .. '\n' .. pending.message
+ elseif pending.message ~= nil then
+ string = string .. '\n' .. pretty.write(pending.message)
+ end
+
+ return string
+ end
+
+ local failureMessage = function(failure)
+ local string = failure.randomseed
+ and ('Random seed: ' .. failure.randomseed .. '\n')
+ or ''
+ if type(failure.message) == 'string' then
+ string = string .. failure.message
+ elseif failure.message == nil then
+ string = string .. 'Nil error'
+ else
+ string = string .. pretty.write(failure.message)
+ end
+
+ return string
+ end
+
+ local failureDescription = function(failure, isError)
+ -- '\226\134\146' = '→' = utf8.char(8594)
+ local string = colors.red(s('output.failure')) .. ' \226\134\146 '
+ if isError then
+ string = colors.magenta(s('output.error')) .. ' \226\134\146 '
+ end
+
+ if not failure.element.trace
+ or not failure.element.trace.short_src
+ then
+ string = string ..
+ colors.cyan(failureMessage(failure)) .. '\n' ..
+ colors.bright(failure.name)
+ else
+ string = string ..
+ colors.cyan(failure.element.trace.short_src) .. ' @ ' ..
+ colors.cyan(failure.element.trace.currentline)
+ -- .. '\n'
+ -- .. colors.bright(failure.name) .. '\n'
+ .. ' ' ..string.lines( failureMessage(failure) )[1]
+ end
+
+ if options.verbose and failure.trace and failure.trace.traceback then
+ string = string .. '\n' .. failure.trace.traceback
+ end
+
+ return string
+ end
+
+ local statusString = function()
+ local successString = s('output.success_plural')
+ local failureString = s('output.failure_plural')
+ local pendingString = s('output.pending_plural')
+ local errorString = s('output.error_plural')
+
+ local sec = handler.getDuration()
+ local successes = handler.successesCount
+ local pendings = handler.pendingsCount
+ local failures = handler.failuresCount
+ local errors = handler.errorsCount
+
+ if successes == 0 then
+ successString = s('output.success_zero')
+ elseif successes == 1 then
+ successString = s('output.success_single')
+ end
+
+ if failures == 0 then
+ failureString = s('output.failure_zero')
+ elseif failures == 1 then
+ failureString = s('output.failure_single')
+ end
+
+ if pendings == 0 then
+ pendingString = s('output.pending_zero')
+ elseif pendings == 1 then
+ pendingString = s('output.pending_single')
+ end
+
+ if errors == 0 then
+ errorString = s('output.error_zero')
+ elseif errors == 1 then
+ errorString = s('output.error_single')
+ end
+
+ local formattedTime = string_gsub(string_format('%.6f', sec), '([0-9])0+$', '%1')
+
+ return colors.green(successes) .. ' ' .. successString .. ' / ' ..
+ colors.red(failures) .. ' ' .. failureString .. ' / ' ..
+ colors.magenta(errors) .. ' ' .. errorString .. ' / ' ..
+ colors.yellow(pendings) .. ' ' .. pendingString .. ' : ' ..
+ colors.bright(formattedTime) .. ' ' .. s('output.seconds')
+ end
+
+ handler.testEnd = function(element, parent, status, debug)
+ if not options.deferPrint then
+ local string = successDot
+
+ if status == 'pending' then
+ string = pendingDot
+ elseif status == 'failure' then
+ string = failureDot
+ elseif status == 'error' then
+ string = errorDot
+ end
+
+ io_write(string)
+ io_flush()
+ end
+
+ return nil, true
+ end
+
+ handler.suiteStart = function(suite, count, total)
+ local runString = (total > 1 and '\nRepeating all tests (run %u of %u) . . .\n\n' or '')
+ io_write(string_format(runString, count, total))
+ io_flush()
+
+ return nil, true
+ end
+
+ handler.suiteEnd = function()
+ -- io_write('\n')
+ io_write(statusString() .. '\n')
+
+ for _, pending in pairs(handler.pendings) do
+ -- io_write('\n')
+ -- io_write(pendingDescription(pending)..'\n')
+ end
+
+ for _, err in pairs(handler.failures) do
+ -- io_write('\n')
+ io_write(failureDescription(err) .. '\n')
+ end
+
+ for _, err in pairs(handler.errors) do
+ -- io_write('\n')
+ -- io_write(failureDescription(err, true)..'\n')
+ end
+
+ io_write(statusString() .. '\n')
+
+ return nil, true
+ end
+
+ handler.error = function(element, parent, message, debug)
+ io_write(errorDot)
+ io_flush()
+
+ return nil, true
+ end
+
+ busted.subscribe({ 'test', 'end' }, handler.testEnd,
+ { predicate = handler.cancelOnPending })
+ busted.subscribe({ 'suite', 'start' }, handler.suiteStart)
+ busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
+ -- busted.subscribe({ 'error', 'file' }, handler.error)
+ -- busted.subscribe({ 'failure', 'file' }, handler.error)
+ -- busted.subscribe({ 'error', 'describe' }, handler.error)
+ -- busted.subscribe({ 'failure', 'describe' }, handler.error)
+
+ return handler
+end
diff --git a/tests/editor/buffer_spec.lua b/tests/editor/buffer_spec.lua
index a61ece0d..c5823874 100644
--- a/tests/editor/buffer_spec.lua
+++ b/tests/editor/buffer_spec.lua
@@ -3,13 +3,13 @@ local parser = require('model.lang.lua.parser')()
require('util.table')
-
describe('Buffer #editor', function()
local w = 64
local chunker = function(t, single)
return parser.chunker(t, w, single)
end
- -- local chunker = parser.chunker
+
+ local printer = parser.pprint
local hl = parser.highlighter
local noop = function() end
@@ -23,11 +23,12 @@ describe('Buffer #editor', function()
local cbuffer = BufferModel('untitled', tst, noop)
local bc = cbuffer:get_content()
assert.same(cbuffer.content_type, 'plain')
- assert.same(4, #bc)
+ assert.same(5, #bc)
assert.same(l1, bc[1])
assert.same(l2, bc[2])
assert.same(l3, bc[3])
assert.same(l4, bc[4])
+ assert.same('', bc[5])
end)
it('renders lua', function()
@@ -39,7 +40,7 @@ describe('Buffer #editor', function()
local cbuffer = BufferModel('untitled.lua', tst, noop, chunker, hl)
local bc = cbuffer:get_content()
assert.same(cbuffer.content_type, 'lua')
- assert.same(4, #bc)
+ assert.same(5, #bc)
assert.same({ l1 }, bc[1].lines)
assert.same({ l2 }, bc[2].lines)
assert.same({ l3 }, bc[3].lines)
@@ -47,6 +48,7 @@ describe('Buffer #editor', function()
end)
describe('setup', function()
+ local buffer, bufcon
local meat = [[function sierpinski(depth)
lines = { '*' }
for i = 2, depth + 1 do
@@ -65,14 +67,18 @@ end]]
print(sierpinski(4))]])
- local buffer = BufferModel('test.lua', txt, noop, chunker, hl)
+
+ lazy_setup(function()
+ buffer = BufferModel('test.lua', txt,
+ noop, chunker, hl)
+ bufcon = buffer:get_content()
+ end)
it('sets name', function()
assert.same('test.lua', buffer.name)
end)
- local bufcon = buffer:get_content()
it('sets content', function()
assert.same('block', bufcon:type())
- assert.same(4, #bufcon)
+ assert.same(5, #bufcon)
assert.same({ '--- @param depth integer' }, bufcon[1].lines)
assert.same(string.lines(meat), bufcon[2].lines)
assert.is_true(table.is_instance(bufcon[3], 'empty'))
@@ -81,113 +87,155 @@ print(sierpinski(4))]])
end)
describe('modifications', function()
+ local trtl =
+ 'Turtle graphics game inspired the LOGO family of languages.'
+ local turtle_doc = {
+ '',
+ trtl,
+ }
+ local turtle = {
+ '--- @diagnostic disable',
+ 'width, height = gfx.getDimensions()',
+ 'midx = width / 2',
+ 'midy = height / 2',
+ 'incr = 5',
+ '',
+ 'tx, ty = midx, midy',
+ 'debug = false',
+ 'debugColor = Color.yellow',
+ '',
+ 'bg_color = Color.black',
+ '',
+ 'local function drawHelp()',
+ ' gfx.setColor(Color[Color.white])',
+ ' gfx.print("Press [I] to open console", 20, 20)',
+ ' gfx.print("Enter \'forward\', \'back\', \'left\', or \'right\' to move the turtle!", 20, 40)',
+ 'end',
+ '',
+ 'local function drawDebuginfo()',
+ ' gfx.setColor(Color[debugColor])',
+ ' local label = string.format("Turtle position: (%d, %d)", tx, ty)',
+ ' gfx.print(label, width - 200, 20)',
+ 'end',
+ '',
+ 'function love.draw()',
+ ' drawBackground()',
+ ' drawHelp()',
+ ' drawTurtle(tx, ty)',
+ ' if debug then drawDebuginfo() end',
+ 'end',
+ '',
+ 'function love.keypressed(key)',
+ ' if love.keyboard.isDown("lshift", "rshift") then',
+ ' if key == \'r\' then',
+ ' tx, ty = midx, midy',
+ ' end',
+ ' end',
+ ' if key == \'space\' then',
+ ' debug = not debug',
+ ' end',
+ ' if key == \'pause\' then',
+ ' stop()',
+ ' end',
+ 'end',
+ '',
+ 'function love.keyreleased(key)',
+ ' if key == \'i\' then',
+ ' input_text(r)',
+ ' end',
+ ' if key == \'return\' then',
+ ' eval()',
+ ' end',
+ '',
+ ' if love.keyboard.isDown("lctrl", "rctrl") then',
+ ' if key == "escape" then',
+ ' love.event.quit()',
+ ' end',
+ ' end',
+ 'end',
+ '',
+ 'local t = 0',
+ 'function love.update(dt)',
+ ' t = t + dt',
+ ' if ty > midy then',
+ ' debugColor = Color.red',
+ ' end',
+ 'end',
+ '',
+ }
+ local buffer
describe('plaintext', function()
- local turtle_doc = {
+ lazy_setup(function()
+ buffer = BufferModel('README', turtle_doc)
+ end)
+ local qed = 'qed.'
+ local new = {
+ '',
+ trtl,
'',
- 'Turtle graphics game inspired the LOGO family of languages.',
+ qed,
+ ''
}
- local buffer = BufferModel('README', turtle_doc)
it('insert newline', function()
assert.same(#turtle_doc + 1, buffer:get_selection())
- local qed = 'qed.'
- buffer:replace_selected_text({ qed })
+ buffer:replace_content({ qed })
assert.same(#turtle_doc + 1, buffer:get_selection())
assert.same(qed, buffer:get_selected_text())
buffer:insert_newline()
- local new = {
- '',
- 'Turtle graphics game inspired the LOGO family of languages.',
- '',
- qed,
- }
+
assert.same(new, buffer:get_text_content())
end)
+
+ describe('insert', function()
+ local addbuf
+ lazy_setup(function()
+ addbuf = BufferModel('README', turtle_doc)
+ end)
+ it('one', function()
+ local newlines = { 'test' }
+ addbuf:insert_content(newlines, 2)
+ local exp = {
+ '',
+ 'test',
+ 'Turtle graphics game inspired the LOGO family of languages.',
+ '',
+ }
+ local res = addbuf:get_text_content()
+ assert.same(exp, res)
+ end)
+
+ it('multiple', function()
+ local newlines = { 'line1', 'line2', }
+ addbuf:insert_content(newlines, 1)
+ local exp = {
+ 'line1',
+ 'line2',
+ '',
+ 'test',
+ 'Turtle graphics game inspired the LOGO family of languages.',
+ '',
+ }
+ local res = addbuf:get_text_content()
+ assert.same(exp, res)
+ end)
+ end)
end)
describe('lua', function()
- local turtle = {
- '--- @diagnostic disable',
- 'width, height = gfx.getDimensions()',
- 'midx = width / 2',
- 'midy = height / 2',
- 'incr = 5',
- '',
- 'tx, ty = midx, midy',
- 'debug = false',
- 'debugColor = Color.yellow',
- '',
- 'bg_color = Color.black',
- '',
- 'local function drawHelp()',
- ' gfx.setColor(Color[Color.white])',
- ' gfx.print("Press [I] to open console", 20, 20)',
- ' gfx.print("Enter \'forward\', \'back\', \'left\', or \'right\' to move the turtle!", 20, 40)',
- 'end',
- '',
- 'local function drawDebuginfo()',
- ' gfx.setColor(Color[debugColor])',
- ' local label = string.format("Turtle position: (%d, %d)", tx, ty)',
- ' gfx.print(label, width - 200, 20)',
- 'end',
- '',
- 'function love.draw()',
- ' drawBackground()',
- ' drawHelp()',
- ' drawTurtle(tx, ty)',
- ' if debug then drawDebuginfo() end',
- 'end',
- '',
- 'function love.keypressed(key)',
- ' if love.keyboard.isDown("lshift", "rshift") then',
- ' if key == \'r\' then',
- ' tx, ty = midx, midy',
- ' end',
- ' end',
- ' if key == \'space\' then',
- ' debug = not debug',
- ' end',
- ' if key == \'pause\' then',
- ' stop()',
- ' end',
- 'end',
- '',
- 'function love.keyreleased(key)',
- ' if key == \'i\' then',
- ' input_text(r)',
- ' end',
- ' if key == \'return\' then',
- ' eval()',
- ' end',
- '',
- ' if love.keyboard.isDown("lctrl", "rctrl") then',
- ' if key == "escape" then',
- ' love.event.quit()',
- ' end',
- ' end',
- 'end',
- '',
- 'local t = 0',
- 'function love.update(dt)',
- ' t = t + dt',
- ' if ty > midy then',
- ' debugColor = Color.red',
- ' end',
- 'end',
- '',
- }
- local text = turtle
-
- local buffer = BufferModel('main.lua', text,
- noop, chunker, hl)
- local bc = buffer:get_content()
- local n_blocks = 24
+ local bc
+ lazy_setup(function()
+ buffer = BufferModel('main.lua', turtle,
+ noop, chunker, hl)
+ bc = buffer:get_content()
+ end)
+ local n_blocks = 25
it('invariants', function()
assert.same(n_blocks, #bc)
assert.same(n_blocks, buffer:get_content_length())
- assert.same(text, buffer:get_text_content())
+ assert.same(turtle, buffer:get_text_content())
- assert.same(n_blocks + 1, buffer:get_selection())
+ assert.same(n_blocks, buffer:get_selection())
local ln = buffer:get_selection_start_line()
assert.same(68, ln)
end)
@@ -201,7 +249,7 @@ print(sierpinski(4))]])
delbuf:delete_selected_text()
assert.same(n_blocks - 2, delbuf:get_content_length())
assert.same(1, delbuf:get_selection())
- assert.same({ text[3] }, delbuf:get_selected_text())
+ assert.same({ turtle[3] }, delbuf:get_selected_text())
end)
it('insert empty', function()
@@ -242,13 +290,14 @@ print(sierpinski(4))]])
local ln = replbuf:get_selection_start_line()
assert.same(61, ln)
assert.same({ 'local t = 0' }, replbuf:get_selected_text())
- assert.same({ text[ln] }, replbuf:get_selected_text())
+ assert.same({ turtle[ln] }, replbuf:get_selected_text())
local empty = Empty(ln)
- local ins, n = replbuf:replace_selected_text({ empty })
+ local ins, n = replbuf:replace_content({ empty })
assert.truthy(ins)
assert.same(1, n)
end)
+
it('replacing block with empty', function()
local replbuf = table.clone(buffer)
replbuf:move_selection('down', nil, true)
@@ -265,10 +314,11 @@ print(sierpinski(4))]])
replbuf:get_selected_text())
local empty = Empty(ln)
- local ins, n = replbuf:replace_selected_text({ empty })
+ local ins, n = replbuf:replace_content({ empty })
assert.truthy(ins)
assert.same(1, n)
end)
+
it('replacing middle block with empty', function()
local replbuf = table.clone(buffer)
replbuf:move_selection('down', nil, true)
@@ -295,7 +345,7 @@ print(sierpinski(4))]])
replbuf:get_selected_text())
local empty = Empty(ln)
- local ins, n = replbuf:replace_selected_text({ empty })
+ local ins, n = replbuf:replace_content({ empty })
assert.truthy(ins)
assert.same(1, n)
@@ -322,7 +372,7 @@ print(sierpinski(4))]])
local ok, chunks = chunker(new, true)
assert.is_true(ok)
- local _, n = replbuf:replace_selected_text(chunks)
+ local _, n = replbuf:replace_content(chunks)
assert.same(1, n)
end)
it('breaking line in block', function()
@@ -343,20 +393,24 @@ print(sierpinski(4))]])
local ok, chunks = chunker(new, true)
assert.is_true(ok)
- local _, n = replbuf:replace_selected_text(chunks)
+ local _, n = replbuf:replace_content(chunks)
assert.same(1, n)
assert.same(24, replbuf:get_selection())
end)
- describe('lua', function()
- local addbuf = table.clone(buffer)
- local orig_b = { 'function love.update(dt)',
- ' t = t + dt',
- ' if ty > midy then',
- ' debugColor = Color.red',
- ' end',
- 'end' }
+ describe('lua newline', function()
+ local addbuf
+ local orig_b
+ lazy_setup(function()
+ addbuf = table.clone(buffer)
+ orig_b = { 'function love.update(dt)',
+ ' t = t + dt',
+ ' if ty > midy then',
+ ' debugColor = Color.red',
+ ' end',
+ 'end' }
+ end)
it('introducing a new line', function()
addbuf:move_selection('down', nil, true)
@@ -369,7 +423,7 @@ print(sierpinski(4))]])
local ok, chunks = chunker(new, true)
assert.is_true(ok)
- local _, n = addbuf:replace_selected_text(chunks)
+ local _, n = addbuf:replace_content(chunks)
assert.same(2, n)
assert.same(23, addbuf:get_selection())
@@ -383,14 +437,88 @@ print(sierpinski(4))]])
local ok, chunks = chunker(new, true)
assert.is_true(ok)
- local _, n = addbuf:replace_selected_text(chunks)
+ local _, n = addbuf:replace_content(chunks)
assert.same(1, n)
assert.same(25, addbuf:get_selection())
end)
end)
+ describe('lua insert', function()
+ local addbuf
+ lazy_setup(function()
+ addbuf = table.clone(buffer)
+ end)
+ it('insert two lines', function()
+ local lines = { 'x = 1; y =2' }
+ local pretty = printer(lines)
+ local _, newblocks = chunker(pretty, true)
+
+ addbuf:insert_content(newblocks, 4)
+ local exp = {
+ '--- @diagnostic disable',
+ 'width, height = gfx.getDimensions()',
+ 'midx = width / 2',
+ 'x = 1',
+ 'y = 2',
+ 'midy = height / 2',
+ 'incr = 5',
+ }
+ local res = table.slice(
+ addbuf:get_text_content(), 1, 7
+ )
+ assert.same(exp, res)
+ end)
+ it('insert after empty lines', function()
+ local lines = { 'string = "empty"' }
+ local pretty = printer(lines)
+ local _, newblocks = chunker(pretty, true)
+
+ addbuf:insert_content(newblocks, 9)
+ local exp = {
+ 'midx = width / 2',
+ 'x = 1',
+ 'y = 2',
+ 'midy = height / 2',
+ 'incr = 5',
+ '',
+ 'string = "empty"',
+ 'tx, ty = midx, midy'
+ }
+ local res = table.slice(
+ addbuf:get_text_content(), 3, 10
+ )
+ assert.same(exp, res)
+ end)
+ it('insert three lines', function()
+ local lines = {
+ 'a1 = 1',
+ ' a2 = 2',
+ 'z="a"',
+ }
+ local pretty = printer(lines)
+ local _, newblocks = chunker(pretty, true)
+
+ addbuf:insert_content(newblocks, 4)
+ local exp = {
+ '--- @diagnostic disable',
+ 'width, height = gfx.getDimensions()',
+ 'midx = width / 2',
+ 'a1 = 1',
+ 'a2 = 2',
+ 'z = "a"',
+ 'x = 1',
+ 'y = 2',
+ 'midy = height / 2',
+ 'incr = 5',
+ }
+ local res = table.slice(
+ addbuf:get_text_content(), 1, 10
+ )
+ assert.same(exp, res)
+ end)
+ end)
--- end ---
end)
end)
diff --git a/tests/editor/chunker_inputs.lua b/tests/editor/chunker_inputs.lua
new file mode 100644
index 00000000..339834b1
--- /dev/null
+++ b/tests/editor/chunker_inputs.lua
@@ -0,0 +1,204 @@
+require('model.editor.content')
+
+--- @param s str
+--- @param r Block[]
+local prep = function(s, r)
+ return {
+ string.lines(s),
+ r
+ }
+end
+
+local sierp_code = [[function sierpinski(depth)
+ lines = { '*' }
+ for i = 2, depth + 1 do
+ sp = string.rep(' ', 2 ^ (i - 2))
+ tmp = {} -- comment
+ for idx, line in ipairs(lines) do
+ tmp[idx] = sp .. line .. sp
+ tmp[idx + #lines] = line .. ' ' .. line
+ end
+ lines = tmp
+ end
+ return table.concat(lines, '\n')
+end
+
+print(sierpinski(4))]]
+local sierp_code_2 = [[function sierpinski(depth)
+ lines = { '*' }
+ for i = 2, depth + 1 do
+ sp = string.rep(' ', 2 ^ (i - 2))
+ tmp = {} -- comment
+ for idx, line in ipairs(lines) do
+ tmp[idx] = sp .. line .. sp
+ tmp[idx + #lines] = line .. ' ' .. line
+ end
+ lines = tmp
+ end
+ return table.concat(lines, '\n')
+end
+
+
+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))
+ tmp = {} -- comment
+ for idx, line in ipairs(lines) do
+ tmp[idx] = sp .. line .. sp
+ tmp[idx + #lines] = line .. ' ' .. line
+ end
+ lines = tmp
+ end
+ return table.concat(lines, '\n')
+end]]), Range(1, 13)),
+ Empty(14),
+ Chunk({ 'print(sierpinski(4))' }, Range.singleton(15)),
+ Empty(16)
+}
+
+local chonk_1 = Chunk({
+ 'function chonky()',
+ ' return {"big", "chungus"}',
+ 'end'
+ },
+ Range(1, 3)
+)
+local chonk_res = {
+ chonk_1,
+ Empty(4),
+ Chunk({ 'print(string.unlines(chonky()))' },
+ Range.singleton(5)),
+ Empty(6),
+}
+
+return {
+ prep(
+ "local x = 1",
+ { Chunk({ 'local x = 1' }, Range.singleton(1)),
+ Empty(2) }
+ ),
+ prep({ '' }, { Empty(1) }),
+ prep({ '', '' }, { Empty(1) }),
+ prep({ ' ', '' }, { Empty(1) }),
+
+ prep(
+ "\nlocal x = 1",
+ { Empty(1),
+ Chunk({ 'local x = 1' }, Range.singleton(2)),
+ Empty(3) }
+ ),
+ prep(
+ "local x = 1\n",
+ { Chunk({ 'local x = 1' }, Range.singleton(1)),
+ Empty(2) }
+ ),
+ prep(
+ "local x = 1\n\n\n\n",
+ { Chunk({ 'local x = 1' }, Range.singleton(1)),
+ Empty(2) }
+ ),
+ prep(
+ "\nlocal x = 1\n",
+ { Empty(1),
+ Chunk({ 'local x = 1' }, Range.singleton(2)),
+ Empty(3) }
+ ),
+ prep(
+ "\n\n\nlocal x = 1\n\n\n",
+ { Empty(1),
+ Chunk({ 'local x = 1' }, Range.singleton(2)),
+ Empty(3) }
+ ),
+
+ prep(sierp_code, sierp_res),
+ prep(sierp_code_2, sierp_res),
+
+ prep([[function chonky()
+ return {"big", "chungus"}
+end
+
+print(string.unlines(chonky()))]], chonk_res),
+ prep([[function chonky()
+ return {"big", "chungus"}
+end
+print(string.unlines(chonky()))]],
+ {
+ chonk_1,
+ Chunk({ 'print(string.unlines(chonky()))' },
+ Range.singleton(4)),
+ Empty(5),
+ }
+ ),
+
+ --- here comes the fun
+ prep([[function chonky()
+ return {"big", "chungus"}
+end
+
+
+print(string.unlines(chonky()))]], chonk_res),
+
+ prep([[function chonky()
+ return {"big", "chungus"}
+end
+
+
+print(string.unlines(chonky()))
+print(1)]], {
+ chonk_1,
+ Empty(4),
+ Chunk({ 'print(string.unlines(chonky()))' },
+ Range.singleton(5)),
+ Chunk({ 'print(1)' },
+ Range.singleton(6)),
+ Empty(7),
+ }),
+
+ prep([[function chonky()
+ return {"big", "chungus"}
+end
+
+
+print(string.unlines(chonky()))
+print(1)
+
+x = 1
+y = 2]], {
+ chonk_1,
+ Empty(4),
+ Chunk({ 'print(string.unlines(chonky()))' },
+ Range.singleton(5)),
+ Chunk({ 'print(1)' }, Range.singleton(6)),
+ Empty(7),
+ Chunk({ 'x = 1' }, Range.singleton(8)),
+ Chunk({ 'y = 2' }, Range.singleton(9)),
+ Empty(10),
+ }),
+
+ prep([[function chonky()
+ return {"big", "chungus"}
+end
+
+
+print(string.unlines(chonky()))
+print(1)
+
+x = 1
+
+y = 2]], {
+ chonk_1,
+ Empty(4),
+ Chunk({ 'print(string.unlines(chonky()))' },
+ Range.singleton(5)),
+ Chunk({ 'print(1)' }, Range.singleton(6)),
+ Empty(7),
+ Chunk({ 'x = 1' }, Range.singleton(8)),
+ Empty(9),
+ Chunk({ 'y = 2' }, Range.singleton(10)),
+ Empty(11),
+ }),
+
+}
diff --git a/tests/editor/chunker_spec.lua b/tests/editor/chunker_spec.lua
new file mode 100644
index 00000000..d6d9b9ed
--- /dev/null
+++ b/tests/editor/chunker_spec.lua
@@ -0,0 +1,28 @@
+local parser = require('model.lang.lua.parser')()
+
+require('util.table')
+
+local inputs = require('tests.editor.chunker_inputs')
+local TU = require('tests.testutil')
+
+
+describe('parser.chunker #chunk', function()
+ local w = TU.wrap
+ local chunker = function(t, single)
+ return parser.chunker(t, w, single)
+ end
+
+ describe('produces blocks', function()
+ 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
+ end)
+
+end)
diff --git a/tests/editor/editor_spec.lua b/tests/editor/editor_spec.lua
index 2e983e33..2c93f036 100644
--- a/tests/editor/editor_spec.lua
+++ b/tests/editor/editor_spec.lua
@@ -20,9 +20,12 @@ describe('Editor #editor', function()
mock.mock_love(love)
end)
+ local trtl =
+ 'Turtle graphics game inspired the LOGO family of languages.'
+
local turtle_doc = {
'',
- 'Turtle graphics game inspired the LOGO family of languages.',
+ trtl,
'',
}
@@ -78,9 +81,9 @@ describe('Editor #editor', function()
local sel = buffer:get_selection()
local sel_t = buffer:get_selected_text()
--- default selection is at the end
- assert.same(#turtle_doc + 1, sel)
+ assert.same(#turtle_doc, sel)
--- and it's an empty line, of course
- assert.same({}, sel_t)
+ assert.same('', sel_t)
end)
end)
@@ -96,7 +99,7 @@ describe('Editor #editor', function()
controller:open('turtle', turtle_doc, save)
local buffer = controller:get_active_buffer()
- local start_sel = #turtle_doc + 1
+ local start_sel = #turtle_doc
it('opens', function()
local bc = buffer:get_content()
@@ -109,15 +112,13 @@ describe('Editor #editor', function()
--- default selection is at the end
assert.same(start_sel, sel)
--- and it's an empty line, of course
- assert.same({}, sel_t)
+ assert.same('', sel_t)
end)
it('interacts', function()
--- select middle line
mock.keystroke('up', press)
assert.same(start_sel - 1, buffer:get_selection())
- mock.keystroke('up', press)
- assert.same(start_sel - 2, buffer:get_selection())
assert.same(turtle_doc[2], buffer:get_selected_text())
--- load it
local input = function()
@@ -127,7 +128,7 @@ describe('Editor #editor', function()
assert.same({ turtle_doc[2] }, input())
mock.keystroke('end', press)
mock.keystroke('down', press)
- assert.same(start_sel - 1, buffer:get_selection())
+ assert.same(start_sel, buffer:get_selection())
-- load the empty
mock.keystroke('escape', press)
assert.same({ '' }, input())
@@ -144,17 +145,18 @@ describe('Editor #editor', function()
mock.keystroke('return', press)
local new = {
'',
- 'Turtle graphics game inspired the LOGO family of languages.',
+ trtl,
'-- test',
+ ''
}
assert.same(new, buffer:get_text_content())
--- input clears
assert.same({ '' }, input())
--- highlight moves down
- assert.same(start_sel, buffer:get_selection())
+ assert.same(start_sel + 1, buffer:get_selection())
mock.keystroke('up', press)
- assert.same(start_sel - 1, buffer:get_selection())
+ assert.same(start_sel, buffer:get_selection())
--- replace
controller:textinput('i')
controller:textinput('n')
@@ -219,7 +221,7 @@ describe('Editor #editor', function()
end)
it('bottoms out', function()
local limit = #sierpinski + visible.overscroll
- assert.same(Range(limit - l + 1, limit), visible.range)
+ -- assert.same(Range(limit - l + 2, limit), visible.range)
end)
end)
@@ -244,8 +246,8 @@ describe('Editor #editor', function()
local scroll = bv.SCROLL_BY
local clen = visible:get_content_length()
- local off = clen - l + 1
- local start_range = Range(off + 1, clen + 1)
+ local off = clen - l
+ local start_range = Range(off + 1, clen)
it('loads', function()
--- inital scroll is at EOF, meaning last l lines are visible
--- plus the phantom line
@@ -295,19 +297,21 @@ describe('Editor #editor', function()
--- default selection is at the end
assert.same(#sierpinski + 1, sel)
--- and it's an empty line, of course
- assert.same({}, sel_t)
+ assert.same('', sel_t)
it('from below', function()
mock.keystroke('pageup', press)
mock.keystroke('up', press)
- --- it's now one above the starting range, the phantom line not visible
- --- assert.same(start_range:translate(-1), visible.range)
+ --- it's now one above the starting range, the
+ --- phantom line not visible
+ -- assert.same(start_range:translate(-1), visible.range)
mock.keystroke('pageup', press)
mock.keystroke('down', press)
- --- after scrolling up and moving the sel back, we are back to the start
+ --- after scrolling up and moving the sel back, we
+ --- are back to the start
--- TODO
+ assert.same(Range(19, 24), visible.range)
-- assert.same(start_range, visible.range)
- assert.same(Range(18, 23), visible.range)
end)
it('to above', function()
local srs = visible.range.start
@@ -319,10 +323,11 @@ describe('Editor #editor', function()
local d = cs - srs
--- TODO
-- assert.same(start_range:translate(d), visible.range)
- assert.same(start_range:translate(d + 2), visible.range)
+ assert.same(start_range:translate(d + 3),
+ visible.range)
mock.keystroke('up', press)
-- assert.same(start_range:translate(d - 1), visible.range)
- assert.same(start_range:translate(d + 1), visible.range)
+ assert.same(start_range:translate(d + 2), visible.range)
end)
it('tops out', function()
--- move up to the first line
@@ -388,7 +393,7 @@ describe('Editor #editor', function()
--- warps to bottom
--- TODO
-- assert.same(start_range, visible.range)
- assert.same(Range(18, 23), visible.range)
+ assert.same(Range(19, 24), visible.range)
assert.is_not.same(sel, buffer:get_selection())
end)
it('to top', function()
@@ -413,7 +418,7 @@ describe('Editor #editor', function()
assert.same({ '' }, inter:get_text())
end)
it('inserts', function()
- mock.keystroke('up', press)
+ -- mock.keystroke('up', press)
local prefix = 'asd '
local selected = buffer:get_selected_text()
inter:add_text(prefix)
@@ -440,7 +445,7 @@ describe('Editor #editor', function()
assert.same('lua', buffer.content_type)
it('length is correct', function()
assert.same('block', cont:type())
- assert.same(3, buffer:get_content_length())
+ assert.same(4, buffer:get_content_length())
end)
it('changing single line', function()
local modified = table.clone(sierpinski)
diff --git a/tests/editor/visible_structured_content_spec.lua b/tests/editor/visible_structured_content_spec.lua
new file mode 100644
index 00000000..ad254db8
--- /dev/null
+++ b/tests/editor/visible_structured_content_spec.lua
@@ -0,0 +1,72 @@
+require('model.editor.bufferModel')
+local parser = require('model.lang.lua.parser')()
+require("view.editor.visibleStructuredContent")
+
+require("util.string.string")
+TU = require('tests.testutil')
+
+local lua_ex =
+[[local x, y
+
+function eval(input)
+ local f = actions[input]
+ if f then
+ f()
+ end
+end
+
+function love.draw()
+ gfx.setFont(font)
+ drawBackground()
+ drawHelp()
+ drawTurtle(tx, ty)
+ if debug then
+ drawDebuginfo()
+ end
+end
+
+function love.update()
+ if ty > midy then
+ debug_color = Color.red
+ end
+ if not r:is_empty() then
+ eval(r())
+ end
+end]]
+
+local chunker = function(t, single)
+ return parser.chunker(t, TU.w, single)
+end
+-- local chunker = parser.chunker
+local hl = parser.highlighter
+
+describe('VisibleStructuredContent #visible', function()
+ local buffer, vsc
+ local lines = string.lines(lua_ex)
+ lazy_setup(function ()
+ buffer = BufferModel('main.lua',
+ lines, TU.noop, chunker, hl)
+ vsc = VisibleStructuredContent({
+ wrap_w = TU.w,
+ overscroll_max = TU.SCROLL_BY,
+ size_max = TU.LINES,
+ view_config = TU.mock_view_cfg
+ },
+ buffer:get_content(),
+ buffer.highlighter,
+ buffer.truncer
+ )
+ end)
+
+ it('invariants', function()
+ assert.same(TU.LINES, vsc.opts.size_max)
+ assert.same(TU.SCROLL_BY, vsc.opts.overscroll_max)
+ end)
+
+ it('scrolls', function()
+ assert.same(27, #lines)
+ -- assert.same(11, vsc.offset)
+ vsc:move_range(-50)
+ assert.same(Range(1, TU.LINES), vsc.range)
+ end)
+end)
diff --git a/tests/interpreter/ast_spec.lua b/tests/interpreter/ast_spec.lua
index 0f58e524..67fd1d34 100644
--- a/tests/interpreter/ast_spec.lua
+++ b/tests/interpreter/ast_spec.lua
@@ -47,8 +47,7 @@ describe('parser #ast', function()
io.write(term.reset)
end
if show_ast then
- -- Log.info(parser.pprint(v, { hide_lineinfo = false }))
- -- Log.debug(Debug.terse_hash(v, nil, nil, true))
+ Log.debug(Debug.terse_ast(ast, true))
end
return code, seen_comments
end