diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 18bfbe0d..3abe56da 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -58,6 +58,11 @@ jobs: submodules: "recursive" fetch-depth: 0 fetch-tags: true + - name: install just + uses: extractions/setup-just@v2 + - name: Add versioninfo + run: | + just version - name: Build bare love package uses: love-actions/love-actions-core@v1 with: @@ -242,7 +247,7 @@ jobs: VER: ${{ github.ref_name }} run: echo VER_CODE="$(echo $VERSION | sed -e 's/^v//' -e 's/\.//g')" >> $GITHUB_ENV - name: Package for android - uses: compy-toys/love-actions-android@v0.2.5 + uses: compy-toys/love-actions-android@v0.2.6 with: love-ref: "compy" no-soft-keyboard: "enabled" diff --git a/.luarc.json b/.luarc.json index ecedad0a..ccc8cc99 100644 --- a/.luarc.json +++ b/.luarc.json @@ -17,6 +17,7 @@ "write_to_input", "validated_input", "stop", + "pause", "continue", // luautils "prequire", diff --git a/README.md b/README.md index 08e56e21..f8cdf686 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ 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 | ## Projects @@ -140,10 +141,6 @@ contents. Write to _file_ the text supplied as the _content_ parameter. This can be either a string, or an array of strings. -- `runfile(file)` - - Run _file_ if it's a lua script. - ## Editor If a project is open, the files inside can be edited or new ones @@ -164,7 +161,7 @@ To modify an existing line, navigate there with ![capitalized](./doc/interface/hello_cap.apng) Happy with the modifications now, we can quit by pressing -Ctrl-Shift-Q +Ctrl-Shift-S ![quit](./doc/interface/quit_editor.apng) diff --git a/doc/development/OOP.md b/doc/development/OOP.md index 24cc3a92..f7e4400b 100644 --- a/doc/development/OOP.md +++ b/doc/development/OOP.md @@ -1,14 +1,14 @@ -### OOP +## OOP -Even though lua is not an object oriented language per se, it can approximate -some OO behaviors with clever use of metatables. +Even though lua is not an object oriented language per se, it +can approximate some OO behaviors with clever use of metatables. See: - [http://lua-users.org/wiki/ObjectOrientedProgramming][oo1] - [http://lua-users.org/wiki/ObjectOrientationTutorial][oo2] -#### Class factory +### Class factory To automate this, a class factory utility was added. @@ -20,26 +20,73 @@ local class = require('util.class') Then it can be used in the following ways: -- passing a constructor (record/dataclass pattern) +#### passing a constructor (record/dataclass pattern) ```lua A = class.create(function() return { a = 'a' } end ) -local a = A() --- results in an instance with the preset values, not very useful +--- results in an instance with the preset values +local a = A() B = class.create(function(x, y) return { x = x, y = y } end) -local b = B(1, 2) --- results in a B instance where x = 1 and y = 2 +--- results in a B instance where x = 1 and y = 2 +local b = B(1, 2) +``` + +Note: in the codebase, the function is often not defined inline, +and very likely is named `new`, _however_ be careful not to +confuse it with the [`new` method][n]. If the function is not a +member of the class, it's this pattern. + +##### late init + +Sometimes it's useful to extract some behavior that's required +both on init but also later on-demand. An instance method is a +fine solution for this, but in order to invoke it, a full-blown +properly initialized instance is necessary, so it can't be done +in the constructor. Also, it would be nice to not have to do it +on each call site where an instance is created. Hence, +`lateinit`. + +Let's say we have Text objects, storing text line-by-line, and +we want to know the average line length. This of course needs to +be calculated on first creation, and any time the text changes. + +```lua +local function new(text) + return { + text = string.lines(text or '') + } +end +local function lateinit(self) + local n = #(self.text) + if n ~= 0 then + local lens = table.map(self.text, string.ulen) + local l = 0 + for _, v in ipairs(lens) do + l = l + v + end + self.avg_len = l / n + else + self.avg_len = 0 + end +end +--- @class Text +--- @field text string[] +--- @field avg_len number +Text = class.create(new, lateinit) ``` -For more advanced use cases, it will probably be necessary to manually control the -metatable setup, this is achieved with the +#### `new` method -- `new()` method +For more advanced use cases, it will probably be necessary to +manually control the metatable setup, this is achieved by +defining the `new()` method on the class. ```lua N = class.create() @@ -61,3 +108,4 @@ local n = N({width = 80, height = 25}) [oo1]: https://archive.vn/B3buW [oo2]: https://archive.vn/muhJx +[n]: #new-method diff --git a/doc/development/editor/visible.md b/doc/development/editor/visible.md index 6bf41649..14d2ed32 100644 --- a/doc/development/editor/visible.md +++ b/doc/development/editor/visible.md @@ -45,6 +45,9 @@ loin█ bresaola veni | 5 son. | ``` We arrive at 'visible coords'. + +#### convert + What is `visible(3, 3)` in the others? * `wrapped(11, 3)` * `normal(4, 41)` diff --git a/doc/development/keyboard.md b/doc/development/keyboard.md new file mode 100644 index 00000000..ce67fa6c --- /dev/null +++ b/doc/development/keyboard.md @@ -0,0 +1,38 @@ + +| Label | KeyConstant | ok? | +| :------------------- | :---------- | :--- | +| Esc | escape | ✓ | +| F1 | f1 | ✗ | +| F2 | f2 | ✗ | +| F3 | f3 | ✗ | +| F4 | f4 | ✗ | +| F5 | f5 | ✗ | +| F6 | f6 | ✗ | +| F7 | f7 | ✗ | +| F8 | f8 | ✗ | +| F9 | f9 | ✗ | +| F10 | f10 | ✓ | +| F11 | f11 | - | +| F12 | f12 | - | +| numlk | code 143 | ✗ | +| Delete | delete | ✓ | +| Tab | tab | ✓ | +| Enter | return | ✓ | +| Caps Lock | capslock | ✓ | +| ⇧ Shift | lshift | ✓ | +| Shift | rshift | ✓ | +| Ctrl | | ✗ | +| Alt | lalt | ✓ | +| PauseBreak | pause | ✓ | +| Menu | menu | ✓ | +| Insert (Fn+F12) | insert | ✓ | +| Scroll Lock (Fn+F10) | scrolllock | ✓ | +| Home (Fn+Left) | home | ✓ | +| End (Fn+Right) | end | ✓ | +| Mute (Fn+F5) | code 164 | ✗ | +| PgUp (Fn+Up) | pageup | ✓ | +| PgDn (Fn+Down) | pagedown | ✓ | +| Next (Fn+F6) | audioplay | ✓ | +| Prev (Fn+F7) | audioprev | ✓ | +| Next (Fn+F8) | audionext | ✓ | +| | | | diff --git a/doc/intro.md b/doc/intro.md new file mode 100644 index 00000000..5dc5d240 --- /dev/null +++ b/doc/intro.md @@ -0,0 +1,53 @@ +# Introduction to Compy + +Compy is a 7'' portable educational computer with an interactive development +environment for the [love2d](https://love2d.org) framework. It runs on top of +Android operating system and uses the Lua programming language for the command +line, scripting, configuration and programming. The primary interface is the +command console, with the standard input displayed at the bottom of the screen, +with real-time syntax highlingting (when appropriate). The standard output is +displayed on a terminal occupying the rest of the screen, which also doubles as +a graphical canvas. Thus, commands with both text-based or graphical output can +be entered. Below are some invariant principles of operations: + +* Pointing devices (such as mice, touch pad or touch screen) might be used, but + are not necessary for the successful operation of Compy. +* Apart from syntax highlighting, anything entered in the console + input will only take effect upon pressing the **Enter** key. +* If the input is syntactically invalid at the time of pressing **Enter**, the + user can continue editing, correcting the mistake. +* Users cannot render compy unusable from the command line. Compy can always + be restored to its initial state by inserting a clean _SD card_ or formatting + the currently inserted one. Example projects are written onto the SD card by + entering `example_projects()` on the command line. + +## Persistence + +All data persisted by the user is saved on the included _SD card_. It is +organized into named _projects_, with no file system hieararchy. Technically, +each project is a directory under `Documents/compy/projects/` on the SD card. +Inside these directories, there is no further directory structure. + +Projects can be selected or, in the absence of one, created by entering +`project("...")` in the console, with the project's name between the double +quotes. Running the project means executing a file called "`main.lua`" in its +directory. It can be done by entering `run()` in the console for the currently +selected project or `run("...")` for a different project, with the name of the +project between the double quotes. + +Text files, including Lua sources can be edited using the built-in text editor +that can be started by entering `edit("...")` in the console, with the name of +the file between the quotes. In its absence, `main.lua` will be edited. + +No changes to the edited file will occur unless the user presses **Enter** _and_ +the text in the edit area at the bottom of the screen is syntactically correct. +Pressing **Enter** takes immediate effect on the SD card, there is no need to +"save" the edited file separately. + +If there is no highlighted section in the edited file, the text inputed in the +console will be appended to the end of the file. If there is a bright white +highlight, the entered text is inserted before it. If there is a bright yellow +highlight, the entered text replaces it. + +For more information, please see the documentation of the [editor](EDITOR.md) +and the [console](../README.md). diff --git a/doc/mermaid/editor.md b/doc/mermaid/editor.md index 3552900b..f80d117c 100644 --- a/doc/mermaid/editor.md +++ b/doc/mermaid/editor.md @@ -73,7 +73,7 @@ class VisibleContent { class VisibleStructuredContent { text: string[] - blocks: VisibleBlock[] + v_blocks: VisibleBlock[] reverse_map: ReverseMap range: Range? diff --git a/src/controller/consoleController.lua b/src/controller/consoleController.lua index 8cb2f51c..0ab31176 100644 --- a/src/controller/consoleController.lua +++ b/src/controller/consoleController.lua @@ -35,13 +35,11 @@ function ConsoleController.new(M, main_ctrl) local config = M.cfg pre_env.font = config.view.font local IC = UserInputController(M.input) - local EC = EditorController(M.editor) local self = setmetatable({ time = 0, model = M, main_ctrl = main_ctrl, input = IC, - editor = EC, -- console runner env main_env = env, -- copy of the application's env before the prep @@ -58,6 +56,9 @@ function ConsoleController.new(M, main_ctrl) cfg = config }, ConsoleController) + --- the editor has to know about us + local EC = EditorController(M.editor, self) + self.editor = EC -- initialize the stub env tables ConsoleController.prepare_env(self) ConsoleController.prepare_project_env(self) @@ -88,11 +89,11 @@ end --- @return boolean success --- @return string? errmsg local function run_user_code(f, cc, project_path) - local G = love.graphics + local gfx = love.graphics local output = cc.model.output local env = cc:get_base_env() - G.setCanvas(cc:get_canvas()) + gfx.setCanvas(cc:get_canvas()) local ok, call_err if project_path then env = cc:get_project_env() @@ -102,7 +103,7 @@ local function run_user_code(f, cc, project_path) cc.main_ctrl.set_user_handlers(env['love']) end output:restore_main() - G.setCanvas() + gfx.setCanvas() if not ok then local msg = LANG.get_call_error(call_err) return false, msg @@ -194,10 +195,11 @@ local function project_require(cc, name) local pr_env = cc:get_project_env() if chunk then setfenv(chunk, pr_env) - chunk() + 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') end end @@ -209,7 +211,7 @@ end function ConsoleController.prepare_env(cc) local prepared = cc.main_env - prepared.G = love.graphics + prepared.gfx = love.graphics local P = cc.model.projects @@ -310,18 +312,6 @@ function ConsoleController.prepare_env(cc) end) end - --- @param name string - --- @return any - prepared.runfile = function(name) - local code = check_open_pr(cc._readfile, cc, name) - local chunk, err = load(code, '', 't') - if chunk then - chunk() - else - print(err) - end - end - --- @param name string prepared.edit = function(name) return check_open_pr(cc.edit, cc, name) @@ -352,24 +342,24 @@ function ConsoleController.prepare_project_env(cc) require("controller.userInputController") require("model.input.userInputModel") require("view.input.userInputView") - local cfg = cc.model.cfg + local cfg = cc.model.cfg ---@type table - local project_env = cc:get_pre_env_c() - project_env.G = love.graphics + local project_env = cc:get_pre_env_c() + project_env.gfx = love.graphics - project_env.require = function(name) + project_env.require = function(name) return project_require(cc, name) end --- @param msg string? - project_env.pause = function(msg) + project_env.pause = function(msg) cc:suspend_run(msg) end - project_env.stop = function() + project_env.stop = function() cc:stop_project_run() end - project_env.continue = function() + project_env.continue = function() if love.state.app_state == 'inspect' then -- resume love.state.app_state = 'running' @@ -399,7 +389,7 @@ function ConsoleController.prepare_project_env(cc) if not input_ref then return end ui_model = UserInputModel(cfg, eval, true, prompt) ui_model:set_text(init) - local inp_con = UserInputController(ui_model, input_ref) + local inp_con = UserInputController(ui_model, input_ref, true) local view = UserInputView(cfg.view, inp_con) love.state.user_input = { M = ui_model, C = inp_con, V = view @@ -600,6 +590,7 @@ function ConsoleController:open_project(name, play) then table.insert(package.loaders, 1, project_loader) end + love.state.app_state = 'project_open' end if open then print('Project ' .. name .. ' opened') @@ -667,8 +658,11 @@ function ConsoleController:edit(name, state) if ex then text = self:_readfile(filename) end - love.state.prev_state = love.state.app_state - love.state.app_state = 'editor' + + if love.state.app_state ~= 'editor' then + love.state.prev_state = love.state.app_state + love.state.app_state = 'editor' + end local save = function(newcontent) return self:_writefile(filename, newcontent) end @@ -677,6 +671,11 @@ function ConsoleController:edit(name, state) self.editor:restore_state(state) end +--- @return EditorState? +function ConsoleController:close_buffer() + self.editor:close_buffer() +end + --- @return EditorState? function ConsoleController:finish_edit() self.editor:save_state() @@ -685,6 +684,7 @@ function ConsoleController:finish_edit() 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) diff --git a/src/controller/controller.lua b/src/controller/controller.lua index b90cb14e..01283d12 100644 --- a/src/controller/controller.lua +++ b/src/controller/controller.lua @@ -8,6 +8,9 @@ local messages = { user_break = "BREAK into program", exit_anykey = "Press any key to exit.", exec_error = function(err) + Log.error((debug.traceback( + "Error: " .. tostring(err), 1):gsub("\n[^\n]+$", "") + )) return 'Execution error at ' .. err end } @@ -376,7 +379,11 @@ Controller = { end local user_input = get_user_input() if user_input then - user_input.V:draw(user_input.C:get_input(), C.time) + if love.DEBUG then + user_input.V:draw(user_input.C:get_input(), C.time) + else + user_input.V:draw(user_input.C:get_input()) + end end end View.prev_draw = draw @@ -416,11 +423,11 @@ Controller = { View.prev_draw = love.draw View.main_draw = love.draw View.end_draw = function() - local w, h = G.getDimensions() - G.setColor(Color[Color.white]) - G.setFont(C.cfg.view.font) - G.clear() - G.printf(messages.exit_anykey, 0, h / 3, w, "center") + local w, h = gfx.getDimensions() + gfx.setColor(Color[Color.white]) + gfx.setFont(C.cfg.view.font) + gfx.clear() + gfx.printf(messages.exit_anykey, 0, h / 3, w, "center") end end, @@ -521,7 +528,7 @@ Controller = { handlers.keypressed = function(k) local function quickswitch() - if k == 'f8' then + if Key.ctrl() and k == 't' then if love.state.app_state == 'running' or love.state.app_state == 'inspect' or love.state.app_state == 'project_open' @@ -556,7 +563,7 @@ Controller = { if love.state.app_state == 'running' then C:stop_project_run() elseif love.state.app_state == 'editor' then - C:finish_edit() + C:close_buffer() end end if k == "r" then diff --git a/src/controller/editorController.lua b/src/controller/editorController.lua index b79d89c1..58d8ba72 100644 --- a/src/controller/editorController.lua +++ b/src/controller/editorController.lua @@ -6,7 +6,8 @@ require("view.input.customStatus") local class = require('util.class') --- @param M EditorModel -local function new(M) +--- @oaram CC ConsoleController +local function new(M, CC) return { input = UserInputController(M.input, nil, true), model = M, @@ -14,6 +15,7 @@ local function new(M) M.search, UserInputController(M.search.input, nil, true) ), + console = CC, view = nil, mode = 'edit', } @@ -28,36 +30,21 @@ end --- @field model EditorModel --- @field input UserInputController --- @field search SearchController +--- @field console ConsoleController --- @field view EditorView? --- @field state EditorState? --- @field mode EditorMode ---- ---- @field open function ---- @field get_state function ---- @field set_state function ---- @field save_state function ---- @field restore_state function ---- @field get_clipboard function ---- @field set_clipboard function ---- @field set_mode function ---- @field get_mode function ---- @field close function ---- @field get_active_buffer function ---- @field get_input function ---- @field update_status function ---- @field textinput function ---- @field keypressed function EditorController = class.create(new) --- @param name string ---- @param content string? +--- @param content str? --- @param save function function EditorController:open(name, content, save) local w = self.model.cfg.view.drawableChars local is_lua = string.match(name, '.lua$') local is_md = string.match(name, '.md$') - local ch, hl, pp + local ch, hl, pp, tr if is_lua then self.input:set_eval(LuaEditorEval) @@ -73,6 +60,9 @@ function EditorController:open(name, content, save) pp = function(t) return parser.pprint(t, w) end + tr = function(code) + return parser.trunc(code, self.model.cfg.view.fold_lines) + end elseif is_md then local mdEval = MdEval(name) hl = mdEval.highlighter @@ -81,13 +71,59 @@ function EditorController:open(name, content, save) self.input:set_eval(TextEval) end - local b = BufferModel(name, content, save, ch, hl, pp) - self.model.buffer = b - self.view.buffer:open(b) + local b = BufferModel(name, content, save, ch, hl, pp, tr) + self.model.buffers:push_front(b) + self.view:open(b) self:update_status() self:set_state() end +--- @private +function EditorController:_dump_bufferlist() + for i, v in ipairs(self.model.buffers) do + Log.debug(i, v.name) + end + orig_print() +end + +function EditorController:follow_require() + local buf = self:get_active_buffer() + if not buf.semantic then return end + local bn = buf:get_selection() + local reqs = buf.semantic.requires + local reqsel = table.find_by_v(reqs, function(r) + return r.block == bn + end) + + if reqsel then + local name = reqsel.name + self.console:edit(name .. '.lua') + else + self:pop_buffer() + end +end + +function EditorController:pop_buffer() + local bs = self.model.buffers + local n_buffers = bs:length() + if n_buffers < 2 then return end + bs:pop_front() + local b = bs:first() + self.view:get_current_buffer():open(b) +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 + --- @param m EditorMode --- @return boolean local function is_normal(m) @@ -101,10 +137,12 @@ function EditorController:set_mode(mode) self:save_state() end local init_search = function() - self:save_state() local db = buf.semantic - local ds = db.definitions - self.search:load(ds) + if db then + self:save_state() + local ds = db.definitions + self.search:load(ds) + end end local current = self.mode @@ -113,7 +151,6 @@ function EditorController:set_mode(mode) set_reorg() end if mode == 'search' then - if not buf.semantic then return end init_search() end self.mode = mode @@ -149,8 +186,10 @@ end --- @param clipboard string? function EditorController:set_state(clipboard) - local buf_view_state = self.view.buffer:get_state() + --- TODO: multibuffer support local buf = self:get_active_buffer() + local bid = buf:get_id() + local buf_view_state = self.view:get_buffer(bid):get_state() if self.state then self.state.buffer = buf_view_state self.state.moved = buf:get_selection() @@ -170,17 +209,19 @@ function EditorController:get_state() end function EditorController:save_state() + --- TODO: multibuffer support self:set_state(love.system.getClipboardText()) end --- @param state EditorState? function EditorController:restore_state(state) + --- TODO: multibuffer support if state then local buf = self:get_active_buffer() local sel = state.buffer.selection local off = state.buffer.offset buf:set_selection(sel) - self.view.buffer:scroll_to(off) + self.view:get_current_buffer():scroll_to(off) local clip = state.clipboard if string.is_non_empty_string(clip) then love.system.setClipboardText(clip or '') @@ -199,7 +240,13 @@ end --- @return BufferModel function EditorController:get_active_buffer() - return self.model.buffer + return self.model.buffers:first() +end + +--- @return Id +function EditorController:get_active_buffer_id() + local buf = self:get_active_buffer() + return buf:get_id() end --- @private @@ -209,7 +256,7 @@ function EditorController:_generate_status(sel) --- @type BufferModel local buffer = self:get_active_buffer() local len = buffer:get_content_length() + 1 - local bufview = self.view.buffer + local bufview = self.view:get_buffer(buffer:get_id()) local more = bufview.content:get_more() local cs local m = self.mode @@ -319,7 +366,7 @@ function EditorController:_move_sel(dir, by, warp, moved) local m = buf:move_selection(dir, by, warp, mv) if m then if mv then self.view:refresh(moved) end - self.view.buffer:follow_selection() + self.view:get_current_buffer():follow_selection() self:update_status() end end @@ -329,7 +376,7 @@ end --- @param warp boolean? --- @param by integer? function EditorController:_scroll(dir, warp, by) - self.view.buffer:scroll(dir, by, warp) + self.view:get_current_buffer():scroll(dir, by, warp) self:update_status() end @@ -414,7 +461,7 @@ function EditorController:_search_mode_keys(k) local bn = jump.block local ln = jump.line - 1 buf:set_selection(bn) - self.view.buffer:scroll_to_line(ln) + self.view:get_current_buffer():scroll_to_line(ln) self:set_mode('edit') self.search:clear() end @@ -519,7 +566,7 @@ 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.buffer + local bufv = self.view:get_current_buffer() local function go(newtext) if bufv:is_selection_visible() then if buf:loaded_is_sel(true) then @@ -609,6 +656,13 @@ function EditorController:_normal_mode_keys(k) and k == "pagedown" then self:_scroll('down', false, 1) end + + -- step into + if love.DEBUG and Key.ctrl() then + if k == "o" then + self:follow_require() + end + end end local function clear() if Key.ctrl() and k == "w" then @@ -651,7 +705,7 @@ function EditorController:keypressed(k) if love.debug then local buf = self:get_active_buffer() - local bufview = self.view.buffer + local bufview = self.view:get_buffer(buf:get_id()) if k == 'f5' then if Key.ctrl() then buf:rechunk() end bufview:refresh() diff --git a/src/examples/clock/README.md b/src/examples/clock/README.md index 0ab14f54..667f5b74 100644 --- a/src/examples/clock/README.md +++ b/src/examples/clock/README.md @@ -6,19 +6,19 @@ In this example, we explore how to properly create a program with it's own drawi We will be taking over the screen drawing for this simple game. Similar to `update()`, we can override the `love.draw()` function and have the LOVE2D framework handle displaying the content we wish. -Drawing generally follows a simple procedure: set up some values, such as what foreground and background color to use, then build up our desired image using basic elements. These are called graphics "primitives", and we can access them from the `love.graphics` table (aliased here as `G`). +Drawing generally follows a simple procedure: set up some values, such as what foreground and background color to use, then build up our desired image using basic elements. These are called graphics "primitives", and we can access them from the `love.graphics` table (aliased here as `gfx`). So, our example clock: ```lua function love.draw() - G.setColor(Color[color + Color.bright]) - G.setBackgroundColor(Color[bg_color]) - G.setFont(font) + gfx.setColor(Color[color + Color.bright]) + gfx.setBackgroundColor(Color[bg_color]) + gfx.setFont(font) local text = getTimestamp() local off_x = font:getWidth(text) / 2 local off_y = font:getHeight() / 2 - G.print(text, midx - off_x, midy - off_y) + gfx.print(text, midx - off_x, midy - off_y) end ``` Let's see this step-by-step. @@ -28,13 +28,13 @@ The way the `print` helper, and most graphics helpers work, is by starting drawi So, we need to determine the half-width and half-height of our text object to correctly draw it at the center. To do this, we can use the `getWidth()` and `getHeight()` helpers. We determined the midpoint of the screen earlier: ```lua -width, height = G.getDimensions() +width, height = gfx.getDimensions() midx = width / 2 midy = height / 2 ``` Armed with this, we can draw the time dead center: ```lua -G.print(text, midx - off_x, midy - off_y) +gfx.print(text, midx - off_x, midy - off_y) ``` #### Getting the timestamp @@ -60,7 +60,7 @@ function setTime() t = s + M * m + H * h end ``` -Reading the current time is achieved by using `os.date()`, which unlike `os.time()`, allows us to specify the format of the resulting string. We want to achieve the end result of "hour:minute"second", which we could get with the format string "%H:%M:%S", but we also need these intermediate values separately, to keep time. Instead, if the special format string "*t" is passed to the function, it will return a table with the parts of the timestamp instead of a string. +Reading the current time is achieved by using `os.date()`, which unlike `os.time()`, allows us to specify the format of the resulting stringfx. We want to achieve the end result of "hour:minute"second", which we could get with the format string "%H:%M:%S", but we also need these intermediate values separately, to keep time. Instead, if the special format string "*t" is passed to the function, it will return a table with the parts of the timestamp instead of a stringfx. #### Timekeeping @@ -83,11 +83,11 @@ function getTimestamp() return string.format("%s:%s:%s", hours, minutes, seconds) end ``` -Quick aside on format strings: for a digital clock, we usually want to always have the numbers displayed as two digits, with colons in between them. To achieve this, we `pad` all our results, then stitch them together with `string.format()`. +Quick aside on format strings: for a digital clock, we usually want to have the numbers displayed as two digits, with colons in between them. To achieve this, we `pad` all our results, then stitch them together with `string.format()`. In the above code, we are doing two different kinds of division.First, an integer division (`/`), going from seconds to minutes and hours, in this case we are only interested in the whole numbers, for example: 143 seconds is two full minutes and then some, but what the clock will be displaying is '02', so the remaining 23 seconds is not interesing for the minutes part. However, for 143 minutes, the display _should_ say 23, disregarding the two full hours, we are only interested in the remainder part. We can get this value by using `math.fmod()`, in this example, `math.fmod(143, 60)`. #### User documentation This program displays the current time in a randomly selected color over a randomly selected background. These colors can be changed by pressing [Space] and [Shift-Space], respectively. -Should the clock deviate from the correct time (for example, because the program run was paused), it can be reset with the [R] key. \ No newline at end of file +Should the clock deviate from the correct time (for example, because the program run was paused), it can be reset with the [R] key. diff --git a/src/examples/clock/main.lua b/src/examples/clock/main.lua index 2ec69534..b940aa84 100644 --- a/src/examples/clock/main.lua +++ b/src/examples/clock/main.lua @@ -1,6 +1,6 @@ -local G = love.graphics +local gfx = love.graphics -width, height = G.getDimensions() +width, height = gfx.getDimensions() midx = width / 2 midy = height / 2 @@ -22,7 +22,7 @@ setTime() math.randomseed(os.time()) color = math.random(7) bg_color = math.random(7) -font = G.newFont(144) +font = gfx.newFont(144) local function pad(i) return string.format("%02d", i) @@ -36,13 +36,13 @@ function getTimestamp() end function love.draw() - G.setColor(Color[color + Color.bright]) - G.setBackgroundColor(Color[bg_color]) - G.setFont(font) + gfx.setColor(Color[color + Color.bright]) + gfx.setBackgroundColor(Color[bg_color]) + gfx.setFont(font) local text = getTimestamp() local off_x = font:getWidth(text) / 2 local off_y = font:getHeight() / 2 - G.print(text, midx - off_x, midy - off_y) + gfx.print(text, midx - off_x, midy - off_y) end function love.update(dt) diff --git a/src/examples/life/README.md b/src/examples/life/README.md index 17e43eec..5e2479ed 100644 --- a/src/examples/life/README.md +++ b/src/examples/life/README.md @@ -10,7 +10,7 @@ We have used the screen size before, but only in a very limited capacity, to det ```lua cell_size = 10 -screen_w, screen_h = G.getDimensions() +screen_w, screen_h = gfx.getDimensions() grid_w = screen_w / cell_size grid_h = screen_h / cell_size ``` @@ -250,10 +250,10 @@ function drawHelp() local right_edge = screen_w - margin local reset_msg = "Reset: [r] key or long press" local speed_msg = "Set speed: [+]/[-] key or drag up/down" - G.print(reset_msg, margin, (bottom - fh) - fh) - G.print(speed_msg, margin, bottom - fh) + gfx.print(reset_msg, margin, (bottom - fh) - fh) + gfx.print(speed_msg, margin, bottom - fh) local speed_label = string.format("Speed: %02d", speed) local label_w = font:getWidth(speed_label) - G.print(speed_label, right_edge - label_w, bottom - fh) + gfx.print(speed_label, right_edge - label_w, bottom - fh) end ``` diff --git a/src/examples/life/main.lua b/src/examples/life/main.lua index 74b42f7a..c3412a62 100644 --- a/src/examples/life/main.lua +++ b/src/examples/life/main.lua @@ -1,12 +1,12 @@ --- original from https://github.com/Aethelios/Conway-s-Game-of-Life-in-Lua-and-Love2D -G = love.graphics -G.setFont(font) +gfx = love.graphics +gfx.setFont(font) fh = font:getHeight() cell_size = 10 margin = 5 -screen_w, screen_h = G.getDimensions() +screen_w, screen_h = gfx.getDimensions() grid_w = screen_w / cell_size grid_h = screen_h / cell_size grid = {} @@ -146,22 +146,22 @@ function drawHelp() local right_edge = screen_w - margin local reset_msg = "Reset: [r] key or long press" local speed_msg = "Set speed: [+]/[-] key or drag up/down" - G.print(reset_msg, margin, (bottom - fh) - fh) - G.print(speed_msg, margin, bottom - fh) + gfx.print(reset_msg, margin, (bottom - fh) - fh) + gfx.print(speed_msg, margin, bottom - fh) local speed_label = string.format("Speed: %02d", speed) local label_w = font:getWidth(speed_label) - G.print(speed_label, right_edge - label_w, bottom - fh) + gfx.print(speed_label, right_edge - label_w, bottom - fh) end function drawCell(x, y) - G.setColor(.9, .9, .9) - G.rectangle('fill', + gfx.setColor(.9, .9, .9) + gfx.rectangle('fill', (x - 1) * cell_size, (y - 1) * cell_size, cell_size, cell_size) - G.setColor(.3, .3, .3) + gfx.setColor(.3, .3, .3) - G.rectangle('line', + gfx.rectangle('line', (x - 1) * cell_size, (y - 1) * cell_size, cell_size, cell_size) @@ -176,7 +176,7 @@ function love.draw() end end - G.setColor(1, 1, 1, 0.5) + gfx.setColor(1, 1, 1, 0.5) drawHelp() end diff --git a/src/examples/paint/README.md b/src/examples/paint/README.md index acd44128..bfde8aa5 100644 --- a/src/examples/paint/README.md +++ b/src/examples/paint/README.md @@ -42,7 +42,7 @@ Building on the 16-color theme, we will divide the screen into 10 columns (8 col Then halve these columns to get the row height. Display the selected background on a double block, with the foreground color in the middle. ```lua -width, height = G.getDimensions() +width, height = gfx.getDimensions() --- color palette block_w = width / 10 block_h = block_w / 2 @@ -142,13 +142,13 @@ In our case, we are only interested in the whole number to navigate our grid, an local y = height - block_h for c = 0, 7 do local x = block_w * (c + 2) - G.setColor(Color[c]) - G.rectangle("fill", x, y, width, block_h) - G.setColor(Color[c + 8]) - G.rectangle("fill", x, y - block_h, width, block_h) - G.setColor(Color[Color.white]) - G.rectangle("line", x, y, width, block_h) - G.rectangle("line", x, y - block_h, width, block_h) + gfx.setColor(Color[c]) + gfx.rectangle("fill", x, y, width, block_h) + gfx.setColor(Color[c + 8]) + gfx.rectangle("fill", x, y - block_h, width, block_h) + gfx.setColor(Color[Color.white]) + gfx.rectangle("line", x, y, width, block_h) + gfx.rectangle("line", x, y - block_h, width, block_h) end ``` @@ -163,7 +163,7 @@ Let's see how this works. First, we set up the canvas: ```lua can_w = width - box_w can_h = height - pal_h - 1 -canvas = G.newCanvas(can_w, can_h) +canvas = gfx.newCanvas(can_w, can_h) ``` The default size of a canvas would be equivalent to the screen, but we have some UI elements here, so a bit smaller makes more sense. However, this does mean we need to calculate the offsets properly when detecting clicks and displaying it. @@ -177,12 +177,12 @@ function useCanvas(x, y, btn) local aw = getWeight() canvas:renderTo(function() -- ... - G.circle("fill", x - box_w, y, aw) + gfx.circle("fill", x - box_w, y, aw) end) end ``` -Note the _x_ coordinate, which is offset by `box_w` (the width of the side panel). When drawing, we go the opposite direction: `G.draw(canvas, box_w)`. +Note the _x_ coordinate, which is offset by `box_w` (the width of the side panel). When drawing, we go the opposite direction: `gfx.draw(canvas, box_w)`. ### Click detection diff --git a/src/examples/paint/main.lua b/src/examples/paint/main.lua index dc2f19b2..01e4ef7d 100644 --- a/src/examples/paint/main.lua +++ b/src/examples/paint/main.lua @@ -1,4 +1,4 @@ -width, height = G.getDimensions() +width, height = gfx.getDimensions() --- color palette block_w = width / 10 block_h = block_w / 2 @@ -29,7 +29,7 @@ weights = { 1, 2, 4, 5, 6, 9, 11, 13 } --- canvas can_w = width - box_w can_h = height - pal_h - 1 -canvas = G.newCanvas(can_w, can_h) +canvas = gfx.newCanvas(can_w, can_h) --- selected color = 0 -- black @@ -55,42 +55,42 @@ function inWeightRange(x, y) end function drawBackground() - G.setColor(Color[Color.black]) - G.rectangle("fill", 0, 0, width, height) + gfx.setColor(Color[Color.black]) + gfx.rectangle("fill", 0, 0, width, height) end function drawPaletteOutline(y) - G.setColor(Color[bg_color]) - G.rectangle("fill", 0, y - block_h, block_w * 2, block_h * 2) - G.setColor(Color[Color.white]) - G.rectangle("line", 0, y - block_h, sel_w, pal_h) - G.rectangle("line", sel_w, y - block_h, width, pal_h) + gfx.setColor(Color[bg_color]) + gfx.rectangle("fill", 0, y - block_h, block_w * 2, block_h * 2) + gfx.setColor(Color[Color.white]) + gfx.rectangle("line", 0, y - block_h, sel_w, pal_h) + gfx.rectangle("line", sel_w, y - block_h, width, pal_h) end function drawSelectedColor(y) - G.setColor(Color[color]) - G.rectangle("fill", block_w / 2, y - (block_h / 2), + gfx.setColor(Color[color]) + gfx.rectangle("fill", block_w / 2, y - (block_h / 2), block_w, block_h) -- outline local line_color = Color.white + Color.bright if color == line_color then line_color = Color.black end - G.setColor(Color[line_color]) - G.rectangle("line", block_w / 2, y - (block_h / 2), + gfx.setColor(Color[line_color]) + gfx.rectangle("line", block_w / 2, y - (block_h / 2), block_w, block_h) end function drawColorBoxes(y) for c = 0, 7 do local x = block_w * (c + 2) - G.setColor(Color[c]) - G.rectangle("fill", x, y, width, block_h) - G.setColor(Color[c + 8]) - G.rectangle("fill", x, y - block_h, width, block_h) - G.setColor(Color[Color.white]) - G.rectangle("line", x, y, width, block_h) - G.rectangle("line", x, y - block_h, width, block_h) + gfx.setColor(Color[c]) + gfx.rectangle("fill", x, y, width, block_h) + gfx.setColor(Color[c + 8]) + gfx.rectangle("fill", x, y - block_h, width, block_h) + gfx.setColor(Color[Color.white]) + gfx.rectangle("line", x, y, width, block_h) + gfx.rectangle("line", x, y - block_h, width, block_h) end end @@ -102,31 +102,31 @@ function drawColorPalette() end function drawBrush(cx, cy) - G.push() - G.translate(cx, cy) + gfx.push() + gfx.translate(cx, cy) local s = icon_d / 100 * .8 - G.scale(s, s) - G.rotate(math.pi / 4) -- 45 degree rotation + gfx.scale(s, s) + gfx.rotate(math.pi / 4) -- 45 degree rotation -- Draw the brush handle (wooden brown color) - G.setColor(0.6, 0.4, 0.2) - G.rectangle("fill", -8, -80, 16, 60) + gfx.setColor(0.6, 0.4, 0.2) + gfx.rectangle("fill", -8, -80, 16, 60) -- Handle highlight - G.setColor(0.8, 0.6, 0.4) - G.rectangle("fill", -6, -75, 3, 50) + gfx.setColor(0.8, 0.6, 0.4) + gfx.rectangle("fill", -6, -75, 3, 50) -- Metal ferrule - G.setColor(0.7, 0.7, 0.8) - G.rectangle("fill", -10, -25, 20, 12) + gfx.setColor(0.7, 0.7, 0.8) + gfx.rectangle("fill", -10, -25, 20, 12) -- Ferrule shine - G.setColor(0.9, 0.9, 1.0) - G.rectangle("fill", -8, -24, 3, 10) + gfx.setColor(0.9, 0.9, 1.0) + gfx.rectangle("fill", -8, -24, 3, 10) -- Bristles with smooth flame-shaped tip - G.setColor(0.2, 0.2, 0.2) - G.rectangle("fill", -12, -13, 24, 25) + gfx.setColor(0.2, 0.2, 0.2) + gfx.rectangle("fill", -12, -13, 24, 25) -- Create flame tip using bezier curve local curve = love.math.newBezierCurve( @@ -140,38 +140,38 @@ function drawBrush(cx, cy) ) local points = curve:render() - G.polygon("fill", points) + gfx.polygon("fill", points) - G.pop() + gfx.pop() end function drawEraser(cx, cy) - G.push() - G.translate(cx, cy) + gfx.push() + gfx.translate(cx, cy) local s = icon_d / 100 - G.scale(s, s) - G.rotate(math.pi / 4) -- 45 degree rotation + gfx.scale(s, s) + gfx.rotate(math.pi / 4) -- 45 degree rotation -- Main eraser body (light blue) - G.setColor(Color[Color.white]) - G.rectangle("fill", -12, -40, 24, 60) + gfx.setColor(Color[Color.white]) + gfx.rectangle("fill", -12, -40, 24, 60) -- Blue stripes running lengthwise (darker blue) - G.setColor(Color[Color.blue]) - G.rectangle("fill", -12, -40, 6, 60) - G.rectangle("fill", 6, -40, 6, 60) + gfx.setColor(Color[Color.blue]) + gfx.rectangle("fill", -12, -40, 6, 60) + gfx.rectangle("fill", 6, -40, 6, 60) -- Worn eraser tip (slightly darker) - G.setColor(Color[Color.white + Color.bright]) - G.rectangle("fill", -12, 15, 24, 8) + gfx.setColor(Color[Color.white + Color.bright]) + gfx.rectangle("fill", -12, 15, 24, 8) -- Eraser crumbs - G.setColor(Color[Color.white]) - G.circle("fill", 18, 25, 2) - G.circle("fill", 22, 30, 1.5) - G.circle("fill", 15, 32, 1) + gfx.setColor(Color[Color.white]) + gfx.circle("fill", 18, 25, 2) + gfx.circle("fill", 22, 30, 1.5) + gfx.circle("fill", 15, 32, 1) - G.pop() + gfx.pop() end -- this is a color @@ -187,14 +187,14 @@ function drawTools() local x = tool_midx - tb_half local y = (i - 1) * (m_2 + tb) if i == tool then - G.setColor(Color[Color.black]) + gfx.setColor(Color[Color.black]) else - G.setColor(Color[Color.white + Color.bright]) + gfx.setColor(Color[Color.white + Color.bright]) end - G.rectangle("fill", x, y + m_2, tb, tb) + gfx.rectangle("fill", x, y + m_2, tb, tb) - G.setColor(Color[Color.black]) - G.rectangle("line", x, y + m_2, tb, tb) + gfx.setColor(Color[Color.black]) + gfx.rectangle("line", x, y + m_2, tb, tb) local draw = tools[i] draw(tool_midx - m_2, y + tb_half + m_4) @@ -202,20 +202,20 @@ function drawTools() end function drawWeightSelector() - G.setColor(Color[Color.white + Color.bright]) - G.rectangle("line", 0, box_h - weight_h, box_w - 1, weight_h) + gfx.setColor(Color[Color.white + Color.bright]) + gfx.rectangle("line", 0, box_h - weight_h, box_w - 1, weight_h) local h = (weight_h - (2 * margin)) / 8 local w = marg_l for i = 0, 7 do local y = wb_y + margin + (i * h) local lw = i + 1 local mid = y + (h / 2) - G.setColor(Color[Color.white + Color.bright]) - G.rectangle("fill", margin, y, w, h) + gfx.setColor(Color[Color.white + Color.bright]) + gfx.rectangle("fill", margin, y, w, h) if lw == weight then - -- G.setColor(Color[Color.white]) - -- G.rectangle("fill", margin, y, w, h) - G.setColor(goose) + -- gfx.setColor(Color[Color.white]) + -- gfx.rectangle("fill", margin, y, w, h) + gfx.setColor(goose) local rx1 = 3 * margin local rx2 = 5 * margin local ry1 = mid - margin @@ -224,7 +224,7 @@ function drawWeightSelector() local x2 = 7 * margin local y1 = mid - m_2 local y2 = mid + m_2 - G.polygon("fill", + gfx.polygon("fill", -- body rx2, ry1, rx1, ry1, @@ -235,9 +235,9 @@ function drawWeightSelector() x2, mid, x1, y1 ) - G.setColor(Color[Color.black]) - G.setLineWidth(2) - G.polygon("line", + gfx.setColor(Color[Color.black]) + gfx.setLineWidth(2) + gfx.polygon("line", -- body rx2, ry1, rx1, ry1, @@ -248,22 +248,22 @@ function drawWeightSelector() x2, mid, x1, y1 ) - G.setLineWidth(1) + gfx.setLineWidth(1) else end - G.setColor(Color[Color.black]) + gfx.setColor(Color[Color.black]) local aw = weights[lw] - G.rectangle("fill", box_w / 3, mid - (aw / 2), + gfx.rectangle("fill", box_w / 3, mid - (aw / 2), box_w / 2, aw) end end function drawToolbox() --- outline - G.setColor(Color[Color.white]) - G.rectangle("fill", 0, 0, box_w - 1, height - pal_h) - G.setColor(Color[Color.white + Color.bright]) - G.rectangle("line", 0, 0, box_w - 1, box_h) + gfx.setColor(Color[Color.white]) + gfx.rectangle("fill", 0, 0, box_w - 1, height - pal_h) + gfx.setColor(Color[Color.white + Color.bright]) + gfx.rectangle("line", 0, 0, box_w - 1, box_h) drawTools() drawWeightSelector() end @@ -282,8 +282,8 @@ function drawTarget() local x, y = love.mouse.getPosition() if inCanvasRange(x, y) then local aw = getWeight() - G.setColor(Color[Color.white]) - G.circle("line", x, y, aw) + gfx.setColor(Color[Color.white]) + gfx.circle("line", x, y, aw) end end @@ -291,7 +291,7 @@ function love.draw() drawBackground() drawToolbox() drawColorPalette() - G.draw(canvas, box_w) + gfx.draw(canvas, box_w) drawTarget() end @@ -327,14 +327,14 @@ function useCanvas(x, y, btn) canvas:renderTo(function() if btn == 1 then if tool == 1 then - G.setColor(Color[color]) + gfx.setColor(Color[color]) elseif tool == 2 then - G.setColor(Color[bg_color]) + gfx.setColor(Color[bg_color]) end elseif btn == 2 then - G.setColor(Color[bg_color]) + gfx.setColor(Color[bg_color]) end - G.circle("fill", x - box_w, y, aw) + gfx.circle("fill", x - box_w, y, aw) end) end diff --git a/src/examples/sine/main.lua b/src/examples/sine/main.lua index 8348f3a1..bcfcf7f7 100644 --- a/src/examples/sine/main.lua +++ b/src/examples/sine/main.lua @@ -1,20 +1,20 @@ -local G = love.graphics +local gfx = love.graphics local x0 = 0 -local xe = G.getWidth() +local xe = gfx.getWidth() local y0 = 0 -local ye = G.getHeight() +local ye = gfx.getHeight() local xh = xe / 2 local yh = ye / 2 -G.setColor(1, 1, 1, 0.5) -G.setLineWidth(1) -G.line(xh, y0, xh, ye) -G.line(x0, yh, xe, yh) +gfx.setColor(1, 1, 1, 0.5) +gfx.setLineWidth(1) +gfx.line(xh, y0, xh, ye) +gfx.line(x0, yh, xe, yh) -G.setColor(1, 0, 0) -G.setPointSize(2) +gfx.setColor(1, 0, 0) +gfx.setPointSize(2) local amp = 100 local times = 2 @@ -27,4 +27,4 @@ for x = 0, xe do table.insert(points, y) end -G.points(points) +gfx.points(points) diff --git a/src/examples/tixy/README.md b/src/examples/tixy/README.md index d5ac52bd..77dca387 100644 --- a/src/examples/tixy/README.md +++ b/src/examples/tixy/README.md @@ -132,14 +132,14 @@ end ```lua function drawCircle(color, radius, x, y) - G.setColor(color) - G.circle( + gfx.setColor(color) + gfx.circle( "fill", x * (size + spacing) + offset, y * (size + spacing) + offset, radius ) - G.circle( + gfx.circle( "line", x * (size + spacing) + offset, y * (size + spacing) + offset, diff --git a/src/examples/tixy/examples.lua b/src/examples/tixy/examples.lua index 963ffdf2..618eb7f5 100644 --- a/src/examples/tixy/examples.lua +++ b/src/examples/tixy/examples.lua @@ -1,4 +1,4 @@ -examples = {} +local examples = {} function example(c, l) table.insert(examples, { @@ -98,3 +98,5 @@ example( "return (x-5)^2 + (y-5)^2 - 99*sin(t)", "create your own!" ) + +return examples diff --git a/src/examples/tixy/main.lua b/src/examples/tixy/main.lua index b89da9b8..da687455 100644 --- a/src/examples/tixy/main.lua +++ b/src/examples/tixy/main.lua @@ -1,10 +1,10 @@ -local G = love.graphics +local gfx = love.graphics math.randomseed(os.time()) -cw, ch = G.getDimensions() +cw, ch = gfx.getDimensions() midx = cw / 2 require("math") -require("examples") +examples = require("examples") size = 28 spacing = 3 @@ -44,7 +44,6 @@ function advance() load_example(e) if ex_idx < #examples then ex_idx = ex_idx + 1 - time = 0 end end @@ -53,7 +52,6 @@ function retreat() local e = examples[ex_idx] load_example(e) ex_idx = ex_idx - 1 - time = 0 end end @@ -96,24 +94,25 @@ function setupTixy() local f = loadstring(code) if f then setfenv(f, _G) + time = 0 tixy = f() end end function drawBackground() - G.setColor(colors.bg) - G.rectangle("fill", 0, 0, cw, ch) + gfx.setColor(colors.bg) + gfx.rectangle("fill", 0, 0, cw, ch) end function drawCircle(color, radius, x, y) - G.setColor(color) - G.circle( + gfx.setColor(color) + gfx.circle( "fill", x * (size + spacing) + offset, y * (size + spacing) + offset, radius ) - G.circle( + gfx.circle( "line", x * (size + spacing) + offset, y * (size + spacing) + offset, @@ -148,14 +147,14 @@ function drawOutput() end function drawText() - G.setColor(colors.text) + gfx.setColor(colors.text) local sof = (size / 2) + offset local hof = sof / 2 - G.printf(legend, midx + hof, sof, midx - sof) + gfx.printf(legend, midx + hof, sof, midx - sof) if showHelp then - G.setColor(colors.help) - G.setFont(font) - G.printf(help, midx + hof, ch - (5 * sof), midx - sof) + gfx.setColor(colors.help) + gfx.setFont(font) + gfx.printf(help, midx + hof, ch - (5 * sof), midx - sof) end end diff --git a/src/examples/turtle/README.md b/src/examples/turtle/README.md index bc114c43..8704ece6 100644 --- a/src/examples/turtle/README.md +++ b/src/examples/turtle/README.md @@ -29,11 +29,11 @@ For the most simple example of this, let's represent the turtle with only an ell local x_r = 15 local y_r = 20 function turtleA(x, y) - G.ellipse("fill", x, y, x_r, y_r, 100) + gfx.ellipse("fill", x, y, x_r, y_r, 100) end function turtleB(x, y) - G.translate(x, y) - G.ellipse("fill", 0, 0, x_r, y_r, 100) + gfx.translate(x, y) + gfx.ellipse("fill", 0, 0, x_r, y_r, 100) end ``` We can draw it at (x, y) either by drawing the shape to (x, y), or first translating the whole drawing to (x, y), and drawing at (0, 0). This might not seem that big of a deal in this simple case, but when the number of transformations and shapes go up, things cat get hard to track very quickly. @@ -45,7 +45,7 @@ The way we translate this for LOVE is an "x radius" and a "y radius".
In o Next, we are adding the turtle's head, which is in some sort of relation to it's body, but also the location where the whole drawing is. ```lua -G.circle("fill", 0, ((0 - y_r) - head_r) + neck, head_r, 100) +gfx.circle("fill", 0, ((0 - y_r) - head_r) + neck, head_r, 100) ``` Using the second method, we are able to provide the head position in "turtle coordinates". So far, there's nothing about this we couldn't have done the other route, but let's proceed to the legs, which we want to draw at an angle. LOVE doesn't provide us any way to do this with only the ellipse function, we do need to `rotate` first. @@ -53,15 +53,15 @@ So far, there's nothing about this we couldn't have done the other route, but le See this condensed example: ```lua function frontLeftLeg(x, y, x_r, y_r, leg_xr, leg_yr) - G.setColor(Color[Color.green + Color.bright]) + gfx.setColor(Color[Color.green + Color.bright]) --- move to the turtle's position - G.translate(x, y) + gfx.translate(x, y) --- move to where the leg attaches to the body - G.translate(-x_r, -y_r / 2 - leg_xr) + gfx.translate(-x_r, -y_r / 2 - leg_xr) --- rotate - G.rotate(-math.pi / 4) + gfx.rotate(-math.pi / 4) --- draw the leg - G.ellipse("fill", 0, 0, leg_xr, leg_yr, 100) + gfx.ellipse("fill", 0, 0, leg_xr, leg_yr, 100) end ``` @@ -77,17 +77,17 @@ You will notice that the actual code does not look like that. For one, in the le Another, more interesting difference is the `push()` - `pop()` pairs around each leg. ```lua --- left front leg -G.push("all") -G.translate(-x_r, -y_r / 2 - leg_xr) -G.rotate(-math.pi / 4) -G.ellipse("fill", 0, 0, leg_xr, leg_yr, 100) -G.pop() +gfx.push("all") +gfx.translate(-x_r, -y_r / 2 - leg_xr) +gfx.rotate(-math.pi / 4) +gfx.ellipse("fill", 0, 0, leg_xr, leg_yr, 100) +gfx.pop() --- right front leg -G.push("all") -G.translate(x_r, -y_r / 2 - leg_xr) -G.rotate(math.pi / 4) -G.ellipse("fill", 0, 0, leg_xr, leg_yr, 100) -G.pop() +gfx.push("all") +gfx.translate(x_r, -y_r / 2 - leg_xr) +gfx.rotate(math.pi / 4) +gfx.ellipse("fill", 0, 0, leg_xr, leg_yr, 100) +gfx.pop() ``` Say we are done drawing the left leg, and now we want to proceed to drawing the other one. We could do the opposite transformations to go back to "zero", or transform from our current state to the desired one, but that leads to more complicated math and less readable code. Instead, we work in stages. When done with the first leg, we can "reset" to our previous state (the "turtle coordinates"), and set up our next one again relative to the center. diff --git a/src/examples/turtle/action.lua b/src/examples/turtle/action.lua index e02c700d..1d62f00f 100644 --- a/src/examples/turtle/action.lua +++ b/src/examples/turtle/action.lua @@ -14,11 +14,11 @@ function moveRight(d) tx = tx + (d or (2 * incr)) end -function pause(msg) +function pause_game(msg) pause(msg or "user paused the game") end -actions = { +local actions = { forward = moveForward, fd = moveForward, back = moveBack, @@ -27,5 +27,7 @@ actions = { l = moveLeft, right = moveRight, r = moveRight, - pause = pause + pause = pause_game } + +return actions diff --git a/src/examples/turtle/drawing.lua b/src/examples/turtle/drawing.lua index 2327153f..5c5a2077 100644 --- a/src/examples/turtle/drawing.lua +++ b/src/examples/turtle/drawing.lua @@ -1,6 +1,6 @@ -local G = love.graphics +gfx = love.graphics -font = G.newFont() +font = gfx.newFont() bg_color = Color.black body_color = Color.green limb_color = body_color + Color.bright @@ -14,45 +14,45 @@ function drawBackground(color) if color_valid then c = color end - G.setColor(Color[c]) - G.rectangle("fill", 0, 0, width, height) + gfx.setColor(Color[c]) + gfx.rectangle("fill", 0, 0, width, height) end function drawFrontLegs(x_r, y_r, leg_xr, leg_yr) - G.setColor(Color[limb_color]) - G.push("all") - G.translate(-x_r, -y_r / 2 - leg_xr) - G.rotate(-math.pi / 4) - G.ellipse("fill", 0, 0, leg_xr, leg_yr, 100) - G.pop() - G.push("all") - G.translate(x_r, -y_r / 2 - leg_xr) - G.rotate(math.pi / 4) - G.ellipse("fill", 0, 0, leg_xr, leg_yr, 100) - G.pop() + gfx.setColor(Color[limb_color]) + gfx.push("all") + gfx.translate(-x_r, -y_r / 2 - leg_xr) + gfx.rotate(-math.pi / 4) + gfx.ellipse("fill", 0, 0, leg_xr, leg_yr, 100) + gfx.pop() + gfx.push("all") + gfx.translate(x_r, -y_r / 2 - leg_xr) + gfx.rotate(math.pi / 4) + gfx.ellipse("fill", 0, 0, leg_xr, leg_yr, 100) + gfx.pop() end function drawHindLegs(x_r, y_r, leg_r, leg_yr) - G.setColor(Color[limb_color]) - G.push("all") - G.translate(-x_r, y_r / 2 + leg_r) - G.rotate(math.pi / 4) - G.ellipse("fill", 0, 0, leg_r, leg_yr, 100) - G.pop() - G.push("all") - G.translate(x_r, y_r / 2 + leg_r) - G.rotate(-math.pi / 4) - G.ellipse("fill", 0, 0, leg_r, leg_yr, 100) - G.pop() + gfx.setColor(Color[limb_color]) + gfx.push("all") + gfx.translate(-x_r, y_r / 2 + leg_r) + gfx.rotate(math.pi / 4) + gfx.ellipse("fill", 0, 0, leg_r, leg_yr, 100) + gfx.pop() + gfx.push("all") + gfx.translate(x_r, y_r / 2 + leg_r) + gfx.rotate(-math.pi / 4) + gfx.ellipse("fill", 0, 0, leg_r, leg_yr, 100) + gfx.pop() end function drawBody(x_r, y_r, head_r) --- body - G.setColor(Color[body_color]) - G.ellipse("fill", 0, 0, x_r, y_r, 100) + gfx.setColor(Color[body_color]) + gfx.ellipse("fill", 0, 0, x_r, y_r, 100) --- head local neck = 5 - G.circle("fill", 0, ((0 - y_r) - head_r) + neck, head_r, 100) + gfx.circle("fill", 0, ((0 - y_r) - head_r) + neck, head_r, 100) --- end end @@ -62,24 +62,24 @@ function drawTurtle(x, y) local leg_yr = 10 local x_r = 15 local y_r = 20 - G.push("all") - G.translate(x, y) + gfx.push("all") + gfx.translate(x, y) drawFrontLegs(x_r, y_r, leg_xr, leg_yr) drawHindLegs(x_r, y_r, leg_xr, leg_yr) drawBody(x_r, y_r, head_r) - G.pop() + gfx.pop() end function drawHelp() - G.setColor(Color[Color.white]) - G.print("Press [I] to open console", 20, 20) + gfx.setColor(Color[Color.white]) + gfx.print("Press [I] to open console", 20, 20) local help = "Enter 'forward', 'back', 'left', or 'right'" .. "to move the turtle!" - G.print(help, 20, 50) + gfx.print(help, 20, 50) end function drawDebuginfo() - G.setColor(Color[debug_color]) + gfx.setColor(Color[debug_color]) local dt = string.format("Turtle position: (%d, %d)", tx, ty) - G.print(dt, width - 200, 20) + gfx.print(dt, width - 200, 20) end diff --git a/src/examples/turtle/main.lua b/src/examples/turtle/main.lua index 1833a4c9..7e570807 100644 --- a/src/examples/turtle/main.lua +++ b/src/examples/turtle/main.lua @@ -1,7 +1,7 @@ -require("action") +actions = require("action") require("drawing") -width, height = love.graphics.getDimensions() +width, height = gfx.getDimensions() midx = width / 2 midy = height / 2 incr = 10 @@ -19,7 +19,7 @@ function eval(input) end function love.draw() - G.setFont(font) + gfx.setFont(font) drawBackground() drawHelp() drawTurtle(tx, ty) diff --git a/src/harmony/init.lua b/src/harmony/init.lua index 111aff9f..07518df0 100644 --- a/src/harmony/init.lua +++ b/src/harmony/init.lua @@ -83,10 +83,10 @@ local function new(_lock) if love.update then love.update(dt) end if love.graphics and love.graphics.isActive() then - local G = love.graphics - G.origin() - G.clear( - G.getBackgroundColor() + local gfx = love.graphics + gfx.origin() + gfx.clear( + gfx.getBackgroundColor() ) if love.draw then love.draw() end @@ -150,7 +150,7 @@ local function utils() if not love.harmony then return end if love.harmony.utils then return end - G = love.graphics + local gfx = love.graphics --- @param name love.Event local love_event = function(name, ...) @@ -182,6 +182,9 @@ local function utils() lgui = false, rgui = false, } + local shortcuts = { + toggle = 'C-t' + } --- @param tag string @@ -203,7 +206,7 @@ local function utils() FS.mkdirp(dir) --- @param img_data love.ImageData - G.captureScreenshot(function(img_data) + gfx.captureScreenshot(function(img_data) if img_data then local from = FS.join_path( love.filesystem.getSaveDirectory(), fn @@ -234,6 +237,7 @@ local function utils() --- @field love_text function --- @field screenshot function --- @field release_keys function + --- @field shortcuts table return { patch_isDown = function() local down = love.keyboard.isDown @@ -291,6 +295,8 @@ local function utils() release_keys = release_keys, + shortcuts = shortcuts, + screenshot = function(tag) timer:script(function(wait) wait(frame_time) @@ -362,6 +368,7 @@ local function runner() end) scrun = coroutine.create(function() + -- timer = Timer.new() for _, v in ipairs(scenarios) do local tag = v.id local sc = v.sc diff --git a/src/harmony/scenarios/editor.lua b/src/harmony/scenarios/editor.lua index c175b393..b9d1e64a 100644 --- a/src/harmony/scenarios/editor.lua +++ b/src/harmony/scenarios/editor.lua @@ -131,14 +131,14 @@ local function editor() wait(.1) h.screenshot('open') wait(.2) - h.love_key('f8') + h.love_key(h.shortcuts.toggle) wait(2) wait(.1) h.screenshot('before') wait(.2) - h.love_key('f8') + h.love_key(h.shortcuts.toggle) wait(.2) h.love_key('C-home') wait(.1) @@ -199,12 +199,12 @@ local function editor() wait(.1) h.screenshot('edited') wait(.2) - h.love_key('f8') + h.love_key(h.shortcuts.toggle) wait(.1) h.screenshot('after') wait(.4) - h.love_key('f8') + h.love_key(h.shortcuts.toggle) wait(.1) h.love_key('C-f') wait(.01) @@ -232,7 +232,7 @@ local function editor() wait(.1) h.screenshot('edited-2') wait(.2) - h.love_key('f8') + h.love_key(h.shortcuts.toggle) wait(.1) h.screenshot('after-2') diff --git a/src/lib/djot/djot.lua b/src/lib/djot/djot.lua index f9a84eb3..3dfbc267 100644 --- a/src/lib/djot/djot.lua +++ b/src/lib/djot/djot.lua @@ -60,7 +60,7 @@ end --- @param sourcepos (boolean?) if true, source positions are included in the AST --- @param warn (function?) function that processes a warning, accepting a warning --- object with `pos` and `message` fields. ---- @return (AST) +--- @return (djotAST) local function parse(input, sourcepos, warn) local parser = Parser:new(input, warn) return ast.to_ast(parser, sourcepos or false) @@ -82,7 +82,7 @@ local function parse_events(input, warn) end --- Render a document's AST in human-readable form. ---- @param doc (AST) the AST +--- @param doc (djotAST) the AST --- @return (string) rendered AST local function render_ast_pretty(doc) local handle = StringHandle:new() @@ -91,14 +91,14 @@ local function render_ast_pretty(doc) end --- Render a document's AST in JSON. ---- @param doc (AST) the AST +--- @param doc (djotAST) the AST --- @return (string) rendered AST (JSON string) local function render_ast_json(doc) return json.encode(doc) .. "\n" end --- Render a document as HTML. ---- @param doc (AST) the AST +--- @param doc (djotAST) the AST --- @return (string) rendered document (HTML string) local function render_html(doc) local handle = StringHandle:new() diff --git a/src/lib/djot/djot/ast.lua b/src/lib/djot/djot/ast.lua index cd875b5a..85cd2a2d 100644 --- a/src/lib/djot/djot/ast.lua +++ b/src/lib/djot/djot/ast.lua @@ -5,10 +5,10 @@ --- @field class? string --- @field id? string ---- @class AST +--- @class djotAST --- @field t string tag for the node --- @field s? string text for the node ---- @field c AST[] child node +--- @field c djotAST[] child node --- @field alias string --- @field level integer --- @field startidx integer @@ -236,7 +236,7 @@ end, function(k) return displaykeys[k] or k end) --- Create a new AST node. --- @param tag (string) tag for the node ---- @return (AST) node (table) +--- @return (djotAST) node (table) local function new_node(tag) local node = { t = tag, c = nil } setmetatable(node, mt) @@ -244,8 +244,8 @@ local function new_node(tag) end --- Add `child` as a child of `node`. ---- @param node (AST) node parent node ---- @param child (AST) node child node +--- @param node (djotAST) node parent node +--- @param child (djotAST) node child node local function add_child(node, child) if (not node.c) then node.c = { child } @@ -255,7 +255,7 @@ local function add_child(node, child) end --- Returns true if `node` has children. ---- @param node (AST) node to check +--- @param node (djotAST) node to check --- @return (boolean) true if node has children local function has_children(node) return (node.c and #node.c > 0) @@ -303,8 +303,8 @@ local function copy_attributes(target, source) end end ---- @param targetnode (AST) ---- @param cs (AST) +--- @param targetnode (djotAST) +--- @param cs (djotAST) local function insert_attributes_from_nodes(targetnode, cs) targetnode.attr = targetnode.attr or new_attributes() local i = 1 @@ -325,7 +325,7 @@ local function insert_attributes_from_nodes(targetnode, cs) end end ---- @param node (AST) +--- @param node (djotAST) local function make_definition_list_item(node) node.t = "definition_list_item" if not has_children(node) then @@ -416,7 +416,7 @@ local function to_ast(parser, sourcepos) local subject = parser.subject local warn = parser.warn if not warn then - warn = function() end + warn = function(o) end end local sourceposmap if sourcepos then @@ -669,7 +669,7 @@ local function to_ast(parser, sourcepos) elseif node.t == "attributes" then -- parse attributes, add to last node local tip = containers[#containers] - --- @type AST|false + --- @type djotAST|false local prevnode = has_children(tip) and tip.c[#tip.c] if prevnode then local endswithspace = false @@ -987,7 +987,7 @@ end --- Render an AST in human-readable form, with indentation --- showing the hierarchy. ---- @param doc (AST) djot AST +--- @param doc (djotAST) djot AST --- @param handle (StringHandle) handle to which to write content local function render(doc, handle) render_node(doc, handle, 0) diff --git a/src/lib/djot/djot/filter.lua b/src/lib/djot/djot/filter.lua index bd28fc2b..f9fd4fd3 100644 --- a/src/lib/djot/djot/filter.lua +++ b/src/lib/djot/djot/filter.lua @@ -104,7 +104,7 @@ local function traverse(node, filterpart) end --- Apply a filter to a document. ---- @param node (AST) +--- @param node (djotAST) --- @param filter table the filter to apply local function apply_filter(node, filter) for _, filterpart in ipairs(filter) do diff --git a/src/lib/hump/timer.lua b/src/lib/hump/timer.lua index cd425bb8..f7f9933a 100644 --- a/src/lib/hump/timer.lua +++ b/src/lib/hump/timer.lua @@ -22,7 +22,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -]]-- +]] -- local Timer = {} Timer.__index = Timer @@ -31,219 +31,220 @@ local function _nothing_() end local unpack = unpack or table.unpack local function updateTimerHandle(handle, dt) - -- handle: { - -- time = , - -- after = , - -- during = , - -- limit = , - -- count = , - -- } - handle.time = handle.time + dt - handle.during(dt, math.max(handle.limit - handle.time, 0)) - - while handle.time >= handle.limit and handle.count > 0 do - if handle.after(handle.after) == false then - handle.count = 0 - break - end - handle.time = handle.time - handle.limit - handle.count = handle.count - 1 - end + -- handle: { + -- time = , + -- after = , + -- during = , + -- limit = , + -- count = , + -- } + handle.time = handle.time + dt + handle.during(dt, math.max(handle.limit - handle.time, 0)) + + while handle.time >= handle.limit and handle.count > 0 do + if handle.after(handle.after) == false then + handle.count = 0 + break + end + handle.time = handle.time - handle.limit + handle.count = handle.count - 1 + end end function Timer:update(dt) - -- timers may create new timers, which leads to undefined behavior - -- in pairs() - so we need to put them in a different table first - local to_update = {} - for handle in pairs(self.functions) do - to_update[handle] = handle - end - - for handle in pairs(to_update) do - if self.functions[handle] then - updateTimerHandle(handle, dt) - if handle.count == 0 then - self.functions[handle] = nil - end - end - end + -- timers may create new timers, which leads to undefined behavior + -- in pairs() - so we need to put them in a different table first + local to_update = {} + for handle in pairs(self.functions) do + to_update[handle] = handle + end + + for handle in pairs(to_update) do + if self.functions[handle] then + updateTimerHandle(handle, dt) + if handle.count == 0 then + self.functions[handle] = nil + end + end + end end function Timer:during(delay, during, after) - local handle = { time = 0, during = during, after = after or _nothing_, limit = delay, count = 1 } - self.functions[handle] = true - return handle + local handle = { time = 0, during = during, after = after or _nothing_, limit = delay, count = 1 } + self.functions[handle] = true + return handle end function Timer:after(delay, func) - return self:during(delay, _nothing_, func) + return self:during(delay, _nothing_, func) end function Timer:every(delay, after, count) - local count = count or math.huge -- exploit below: math.huge - 1 = math.huge - local handle = { time = 0, during = _nothing_, after = after, limit = delay, count = count } - self.functions[handle] = true - return handle + local count = count or math.huge -- exploit below: math.huge - 1 = math.huge + local handle = { time = 0, during = _nothing_, after = after, limit = delay, count = count } + self.functions[handle] = true + return handle end function Timer:cancel(handle) - self.functions[handle] = nil + self.functions[handle] = nil end function Timer:clear() - self.functions = {} + self.functions = {} end function Timer:script(f) - local co = coroutine.wrap(f) - co(function(t) - self:after(t, co) - coroutine.yield() - end) + local co = coroutine.wrap(f) + co(function(t) + self:after(t, co) + coroutine.yield() + end) end local function func_tween(tween, self, len, subject, target, method, after, - setters_and_getters, ...) - -- recursively collects fields that are defined in both subject and target into a flat list - -- re-use of ref is confusing - local to_func_tween = {} - local function set_and_get(subject, k, v) - setters_and_getters = setters_and_getters or {} - - local setter, getter - if setters_and_getters[k] then - setter, getter = unpack(setters_and_getters[k]) - else - setter = subject['set'..k] - getter = subject['get'..k] + setters_and_getters, ...) + -- recursively collects fields that are defined in both subject and target into a flat list + -- re-use of ref is confusing + local to_func_tween = {} + local function set_and_get(subject, k, v) + setters_and_getters = setters_and_getters or {} + + local setter, getter + if setters_and_getters[k] then + setter, getter = unpack(setters_and_getters[k]) + else + setter = subject['set' .. k] + getter = subject['get' .. k] + end + assert(setter and getter, + "key's value in subject is nil with no set/getter") + + if to_func_tween[subject] == nil then + to_func_tween[subject] = {} + end + + local ref = { getter(subject) } + to_func_tween[subject][k] = { ref, setter } + if type(v) == 'number' or #ref == 1 then + v = { v } + end + return ref, v + end + + local function tween_collect_payload(subject, target, out) + for k, v in pairs(target) do + -- this might not be the smoothest way to do this + local ref = subject[k] + if ref == nil then + ref, v = set_and_get(subject, k, v) end - assert(setter and getter, - "key's value in subject is nil with no set/getter") - - if to_func_tween[subject] == nil then - to_func_tween[subject] = {} - end - - ref = {getter(subject)} - to_func_tween[subject][k] = {ref, setter} - if type(v) == 'number' or #ref == 1 then - v = {v} - end - return ref, v - end - - local function tween_collect_payload(subject, target, out) - for k,v in pairs(target) do - - -- this might not be the smoothest way to do this - local ref = subject[k] - if ref == nil then - ref, v = set_and_get(subject, k, v) - end - assert(type(v) == type(ref), 'Type mismatch in field "'..k..'". ' - ..type(v)..' vs '.. type(ref)) - if type(v) == 'table' then - tween_collect_payload(ref, v, out) - else - local ok, delta = pcall(function() return (v-ref)*1 end) - assert(ok, 'Field "'..k..'" does not support arithmetic operations') - out[#out+1] = {subject, k, delta} - end - end - return out - end - - method = tween[method or 'linear'] -- see __index - local payload, t, args = tween_collect_payload(subject, target, {}), 0, {...} - - local last_s = 0 - return self:during(len, function(dt) - t = t + dt - local s = method(math.min(1, t/len), unpack(args)) - local ds = s - last_s - last_s = s - for _, info in ipairs(payload) do - local ref, key, delta = unpack(info) - ref[key] = ref[key] + delta * ds + assert(type(v) == type(ref), 'Type mismatch in field "' .. k .. '". ' + .. type(v) .. ' vs ' .. type(ref)) + if type(v) == 'table' then + tween_collect_payload(ref, v, out) + else + local ok, delta = pcall(function() return (v - ref) * 1 end) + assert(ok, 'Field "' .. k .. '" does not support arithmetic operations') + out[#out + 1] = { subject, k, delta } end - for ref, t in pairs(to_func_tween) do - for key, value in pairs(t) do - local setter_args, setter = unpack(value) - if not pcall(function() setter(ref, unpack(setter_args)) end) then - setter(unpack(setter_args)) - end - end + end + return out + end + + method = tween[method or 'linear'] -- see __index + local payload, t, args = tween_collect_payload(subject, target, {}), 0, { ... } + + local last_s = 0 + return self:during(len, function(dt) + t = t + dt + local s = method(math.min(1, t / len), unpack(args)) + local ds = s - last_s + last_s = s + for _, info in ipairs(payload) do + local ref, key, delta = unpack(info) + ref[key] = ref[key] + delta * ds + end + for ref, t in pairs(to_func_tween) do + for key, value in pairs(t) do + local setter_args, setter = unpack(value) + if not pcall(function() setter(ref, unpack(setter_args)) end) then + setter(unpack(setter_args)) + end end - end, after) + end + end, after) end local function plain_tween(tween, self, len, subject, target, method, after, ...) - return func_tween(tween, self, len, subject, target, method, after, nil, ...) + return func_tween(tween, self, len, subject, target, method, after, nil, ...) end local function def_tween(func) - return setmetatable( - { - -- helper functions - out = function(f) -- 'rotates' a function - return function(s, ...) return 1 - f(1-s, ...) end - end, - chain = function(f1, f2) -- concatenates two functions - return function(s, ...) return (s < .5 and f1(2*s, ...) or 1 + f2(2*s-1, ...)) * .5 end - end, - - -- useful tweening functions - linear = function(s) return s end, - quad = function(s) return s*s end, - cubic = function(s) return s*s*s end, - quart = function(s) return s*s*s*s end, - quint = function(s) return s*s*s*s*s end, - sine = function(s) return 1-math.cos(s*math.pi/2) end, - expo = function(s) return 2^(10*(s-1)) end, - circ = function(s) return 1 - math.sqrt(1-s*s) end, - - back = function(s,bounciness) - bounciness = bounciness or 1.70158 - return s*s*((bounciness+1)*s - bounciness) - end, - - bounce = function(s) -- magic numbers ahead - local a,b = 7.5625, 1/2.75 - return math.min(a*s^2, a*(s-1.5*b)^2 + .75, a*(s-2.25*b)^2 + .9375, a*(s-2.625*b)^2 + .984375) - end, - - elastic = function(s, amp, period) - amp, period = amp and math.max(1, amp) or 1, period or .3 - return (-amp * math.sin(2*math.pi/period * (s-1) - math.asin(1/amp))) * 2^(10*(s-1)) - end, - - - }, { - - -- register new tween - __call = func, - - -- fetches function and generated compositions for method `key` - __index = function(tweens, key) - if type(key) == 'function' then return key end - - assert(type(key) == 'string', 'Method must be function or string.') - if rawget(tweens, key) then return rawget(tweens, key) end - - local function construct(pattern, f) - local method = rawget(tweens, key:match(pattern)) - if method then return f(method) end - return nil - end - - local out, chain = rawget(tweens,'out'), rawget(tweens,'chain') - return construct('^in%-([^-]+)$', function(...) return ... end) - or construct('^out%-([^-]+)$', out) - or construct('^in%-out%-([^-]+)$', function(f) return chain(f, out(f)) end) - or construct('^out%-in%-([^-]+)$', function(f) return chain(out(f), f) end) - or error('Unknown interpolation method: ' .. key) - end}) + return setmetatable( + { + -- helper functions + out = function(f) -- 'rotates' a function + return function(s, ...) return 1 - f(1 - s, ...) end + end, + chain = function(f1, f2) -- concatenates two functions + return function(s, ...) return (s < .5 and f1(2 * s, ...) or 1 + f2(2 * s - 1, ...)) * .5 end + end, + + -- useful tweening functions + linear = function(s) return s end, + quad = function(s) return s * s end, + cubic = function(s) return s * s * s end, + quart = function(s) return s * s * s * s end, + quint = function(s) return s * s * s * s * s end, + sine = function(s) return 1 - math.cos(s * math.pi / 2) end, + expo = function(s) return 2 ^ (10 * (s - 1)) end, + circ = function(s) return 1 - math.sqrt(1 - s * s) end, + + back = function(s, bounciness) + bounciness = bounciness or 1.70158 + return s * s * ((bounciness + 1) * s - bounciness) + end, + + bounce = function(s) -- magic numbers ahead + local a, b = 7.5625, 1 / 2.75 + return math.min(a * s ^ 2, a * (s - 1.5 * b) ^ 2 + .75, a * (s - 2.25 * b) ^ 2 + .9375, a * (s - 2.625 * b) ^ 2 + + .984375) + end, + + elastic = function(s, amp, period) + amp, period = amp and math.max(1, amp) or 1, period or .3 + return (-amp * math.sin(2 * math.pi / period * (s - 1) - math.asin(1 / amp))) * 2 ^ (10 * (s - 1)) + end, + + + }, { + + -- register new tween + __call = func, + + -- fetches function and generated compositions for method `key` + __index = function(tweens, key) + if type(key) == 'function' then return key end + + assert(type(key) == 'string', 'Method must be function or string.') + if rawget(tweens, key) then return rawget(tweens, key) end + + local function construct(pattern, f) + local method = rawget(tweens, key:match(pattern)) + if method then return f(method) end + return nil + end + + local out, chain = rawget(tweens, 'out'), rawget(tweens, 'chain') + return construct('^in%-([^-]+)$', function(...) return ... end) + or construct('^out%-([^-]+)$', out) + or construct('^in%-out%-([^-]+)$', function(f) return chain(f, out(f)) end) + or construct('^out%-in%-([^-]+)$', function(f) return chain(out(f), f) end) + or error('Unknown interpolation method: ' .. key) + end + }) end @@ -252,7 +253,7 @@ Timer.func_tween = def_tween(func_tween) -- Timer instancing function Timer.new() - return setmetatable({functions = {}, tween = Timer.tween}, Timer) + return setmetatable({ functions = {}, tween = Timer.tween }, Timer) end -- default instance @@ -261,14 +262,14 @@ local default = Timer.new() -- module forwards calls to default instance local module = {} for k in pairs(Timer) do - if k ~= "__index" then - module[k] = function(...) return default[k](default, ...) end - end + if k ~= "__index" then + module[k] = function(...) return default[k](default, ...) end + end end module.tween = setmetatable({}, { - __index = Timer.tween, - __newindex = function(k,v) Timer.tween[k] = v end, - __call = function(t, ...) return default:tween(...) end, + __index = Timer.tween, + __newindex = function(k, v) Timer.tween[k] = v end, + __call = function(t, ...) return default:tween(...) end, }) -return setmetatable(module, {__call = Timer.new}) +return setmetatable(module, { __call = Timer.new }) diff --git a/src/main.lua b/src/main.lua index 5ccc3c05..8c24ad5a 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1,5 +1,6 @@ local redirect_to = require("model.io.redirect") local OS = require("util.os") + require("model.consoleModel") require("controller.controller") require("controller.consoleController") @@ -15,7 +16,7 @@ local FS = require("util.filesystem") require("lib.error_explorer") -G = love.graphics +local gfx = love.graphics local messages = { how_to_exit = 'Press Ctrl-Esc to exit', @@ -45,11 +46,11 @@ local config_view = function(flags) local font_dir = "assets/fonts/" local mf = "ubuntu_mono_bold_nerd.ttf" - local font_main = G.newFont( + local font_main = gfx.newFont( font_dir .. mf, font_size) - local font_icon = G.newFont( + local font_icon = gfx.newFont( font_dir .. "SFMonoNerdFontMono-Regular.otf", font_size) - local font_cjk = G.newFont( + local font_cjk = gfx.newFont( font_dir .. "SarasaGothicJ-Bold.ttf", font_size * (2 / 3)) font_main:setFallbacks(font_icon, font_cjk) @@ -64,9 +65,9 @@ local config_view = function(flags) local lines = 16 local input_max = 14 - local font_labels = G.newFont(font_dir .. mf, 12) - local w = love.fixWidth or G.getWidth() - local h = love.fixHeight or G.getHeight() + local font_labels = gfx.newFont(font_dir .. mf, 12) + local w = love.fixWidth or gfx.getWidth() + local h = love.fixHeight or gfx.getHeight() local eh = h - 2 * fh local debugheight = math.floor(eh / (love.test_grid_y * fh)) local debugwidth = math.floor(w / love.test_grid_x) / fw @@ -104,6 +105,7 @@ local config_view = function(flags) drawableWidth = drawableWidth, drawableChars = drawableChars, + fold_lines = 1, drawtest = tf.draw, sizedebug = tf.size, } diff --git a/src/model/canvasModel.lua b/src/model/canvasModel.lua index 767c99c8..e9449f01 100644 --- a/src/model/canvasModel.lua +++ b/src/model/canvasModel.lua @@ -4,7 +4,7 @@ require("util.view") local class = require('util.class') local Terminal = require("lib.terminal") -local G = love.graphics +local gfx = love.graphics --- @class CanvasModel --- @field terminal table @@ -31,7 +31,7 @@ function CanvasModel.new(cfg) -- h = ViewUtils.get_drawable_height(cfg.view) h = cfg.view.h end - local canvas = G.newCanvas(w, h) + local canvas = gfx.newCanvas(w, h) local custom_height = cfg.view.fh * cfg.view.lh local term = Terminal(w, h, cfg.view.font, nil, custom_height) @@ -85,14 +85,14 @@ end function CanvasModel:clear_canvas() return self.canvas:renderTo(function() - G.clear(0, 0, 0, 0) + gfx.clear(0, 0, 0, 0) end) end function CanvasModel:draw_to() - G.setCanvas(self.canvas) + gfx.setCanvas(self.canvas) end function CanvasModel:restore_main() - G.setCanvas() + gfx.setCanvas() end diff --git a/src/model/editor/bufferModel.lua b/src/model/editor/bufferModel.lua index cac3a5c9..75e51a07 100644 --- a/src/model/editor/bufferModel.lua +++ b/src/model/editor/bufferModel.lua @@ -28,16 +28,22 @@ end --- @alias Content Dequeue|Dequeue --- @param name string ---- @param content string +--- @param content str --- @param save function --- @param chunker Chunker? --- @param highlighter Highlighter? ---- @param printer function? +--- @param printer Printer? +--- @param truncer function? --- @return BufferModel? -local function new(name, content, save, - chunker, highlighter, printer) +local function new( + name, + content, + save, + chunker, + highlighter, + printer, + truncer) local _content, sel, ct, semantic - local revmap = {} local readonly = false local lines = string.lines(content or '') @@ -51,21 +57,10 @@ local function new(name, content, save, --- @param chk function local function luacontent(chk) ct = 'lua' - local ok, blocks, ast = chk(lines) + local ok, blocks = chk(lines) if ok then local len = #blocks sel = len + 1 - local anaok, ana = pcall(analyzer.analyze, ast) - if anaok then - for bi, v in ipairs(blocks) do - if (v.pos) then - for _, l in ipairs(v.pos:enumerate()) do - revmap[l] = bi - end - end - end - semantic = bsi.convert(ana, revmap) - end else readonly = true sel = 1 @@ -82,7 +77,7 @@ local function new(name, content, save, plaintext() end - return { + local self = { name = name or 'untitled', content = _content, content_type = ct, @@ -90,13 +85,23 @@ local function new(name, content, save, chunker = chunker, highlighter = highlighter, printer = printer, + truncer = truncer, + revmap = {}, semantic = semantic, selection = sel, readonly = readonly } + local id = tostring(self):gsub('table: ', '') + self.id = id + return self +end + +--- @param self BufferModel +local function lateinit(self) + self:analyze() end ---- @class BufferModel +--- @class BufferModel : Object --- @field name string --- @field content Dequeue -- Content --- @field content_type ContentType @@ -105,17 +110,40 @@ end --- @field loaded integer? --- @field readonly boolean --- @field semantic BufferSemanticInfo? +--- @field revmap table? --- --- @field chunker Chunker --- @field highlighter Highlighter --- @field printer Printer +--- @field truncer function --- @field move_selection function --- @field get_selection function --- @field get_selected_text function --- @field delete_selected_text function --- @field replace_selected_text function --- @field get_text_content function -BufferModel = class.create(new) +BufferModel = class.create(new, lateinit) + +function BufferModel:get_id() + return self.id +end + +function BufferModel:analyze() + if self.content_type ~= 'lua' then return end + local lines = string.lines(self:get_text_content()) + local ok, blocks, ast = self.chunker(lines) + if not ok then return end + local anaok, ana = pcall(analyzer.analyze, ast) + if not anaok then return end + for bi, v in ipairs(blocks) do + if (v.pos) then + for _, l in ipairs(v.pos:enumerate()) do + self.revmap[l] = bi + end + end + end + self.semantic = bsi.convert(ana, self.revmap) +end function BufferModel:rechunk() if self.content_type ~= 'lua' then return end @@ -127,6 +155,7 @@ end function BufferModel:save() self:highlight() local text = self:get_text_content() + self:analyze() return self.save_file(text) end @@ -215,7 +244,7 @@ function BufferModel:set_selection(sel) end --- Get index of selected line/block ---- @return integer +--- @return integer blocknum function BufferModel:get_selection() return self.selection end diff --git a/src/model/editor/bufferSemanticInfo.lua b/src/model/editor/bufferSemanticInfo.lua index 76f7e500..4d854ec8 100644 --- a/src/model/editor/bufferSemanticInfo.lua +++ b/src/model/editor/bufferSemanticInfo.lua @@ -5,21 +5,30 @@ require('util.table') --- @class Definition: Assignment --- @field block blocknum +--- @class RequireCall: Require +--- @field block blocknum + --- @class BufferSemanticInfo --- @field definitions Definition[] +--- @field requires RequireCall[] --- @param si SemanticInfo --- @param rev table --- @return BufferSemanticInfo local function convert(si, rev) - local as = si.assignments - local defs = table.map(as, function(a) + local blockmap = function(a) local r = table.clone(a) r.block = rev[a.line] return r - end) + end + local as = si.assignments + local defs = table.map(as, blockmap) + local rs = si.requires + local reqs = table.map(rs, blockmap) + return { definitions = defs, + requires = reqs, } end diff --git a/src/model/editor/editorModel.lua b/src/model/editor/editorModel.lua index 75bda4ba..c9fb4cbe 100644 --- a/src/model/editor/editorModel.lua +++ b/src/model/editor/editorModel.lua @@ -6,13 +6,13 @@ local class = require('util.class') --- @class EditorModel --- @field input UserInputModel ---- @field buffer BufferModel? +--- @field buffers Dequeue --- @field search Search --- @field cfg Config EditorModel = class.create(function(cfg) return { input = UserInputModel(cfg, LuaEval()), - buffer = nil, + buffers = Dequeue.new({}, 'BufferModel'), search = Search(cfg), cfg = cfg, } diff --git a/src/model/interpreter/eval/evaluator.lua b/src/model/interpreter/eval/evaluator.lua index 12161efb..642870be 100644 --- a/src/model/interpreter/eval/evaluator.lua +++ b/src/model/interpreter/eval/evaluator.lua @@ -48,7 +48,7 @@ end --- @param s string[] --- @return boolean ok --- @return str content|errpr ---- @return AST? ast +--- @return luaAST? ast local function default_apply(self, s) local valid, errors = validate(self, s) local parser = self.parser diff --git a/src/model/lang/lua/analyze.lua b/src/model/lang/lua/analyze.lua index f4c47ed7..1cd29ff7 100644 --- a/src/model/lang/lua/analyze.lua +++ b/src/model/lang/lua/analyze.lua @@ -1,19 +1,7 @@ require('util.tree') +require('model.lang.lua.semantic_info') ---- @alias AssignmentType ---- | 'function' ---- | 'method' ---- | 'local' ---- | 'global' ---- | 'field' - ---- @class Assignment ---- @field name string ---- @field line integer ---- @field type AssignmentType - ---- @class SemanticInfo ---- @field assignments Assignment[] +--- utils local keywords_list = { "and", @@ -43,7 +31,17 @@ for _, kw in pairs(keywords_list) do keywords[kw] = true end ---- @param ast AST +--- @param n luaAST +--- @return number? +local function get_line_number(n) + local li = n.lineinfo + local li_f = type(li) == 'table' and li.first + return type(li_f) == 'table' and li_f.line or nil +end + +--- assignments + +--- @param ast luaAST --- @return string? local function get_idx_stack(ast) --- @return string? @@ -61,7 +59,7 @@ local function get_idx_stack(ast) return go(ast) end ---- @param node AST +--- @param node luaAST --- @return boolean local function is_idx_stack(node) local st = get_idx_stack(node) @@ -77,20 +75,11 @@ local function is_ident(id) return string["match"](id, "^[%a_][%w_]*$") and not keywords[id] end ---- @param node AST +--- @param node luaAST --- @return table? local function definition_extractor(node) local deftags = { 'Local', 'Localrec', 'Set' } - local function get_line_number(n) - local li = n.lineinfo - local li_f = type(li) == 'table' and li.first - return type(li_f) == 'table' and li_f.line - end - local function get_lhs_name(n) - return n[1] - end - if type(node) == 'table' and node.tag then local tag = node.tag if table.is_member(deftags, tag) then @@ -170,7 +159,7 @@ local function definition_extractor(node) at = 'global' end for i, w in ipairs(lhs) do - local n = get_lhs_name(w) + local n = w[1] if is_local and not rhs[i] then dec_only = true end @@ -202,9 +191,9 @@ local function defmatch(name) end end ---- @param ast AST ---- @return SemanticInfo -local function analyze(ast) +--- @param ast luaAST +--- @return Assignment[] +local function get_assignments(ast) local sets = table.flatten( Tree.preorder(ast, definition_extractor) ) @@ -231,7 +220,52 @@ local function analyze(ast) table.insert(candidates, v) end end - return { assignments = assignments } + return assignments +end + +--- requires + +--- @param node luaAST +--- @return Require[] +local function req_extractor(node) + local calltags = { 'Call' } + if type(node) == 'table' and node.tag then + local tag = node.tag + if table.is_member(calltags, tag) then + local lhs = node[1] + local rhs = node[2] + + if tag == 'Call' + and lhs.tag == 'Id' and lhs[1] == 'require' + then + local val = rhs[1] + local li = get_line_number(rhs) + return { { line = li, name = val } } + end + end + end + return {} +end + +--- @param ast luaAST +--- @return Require[] +local function get_requires(ast) + local candidates = table.flatten( + Tree.preorder(ast, req_extractor) + ) + local reqs = {} + for _, v in ipairs(candidates or {}) do + table.insert(reqs, v) + end + return reqs +end + +--- @param ast luaAST +--- @return string[] +local function analyze(ast) + local assignments = get_assignments(ast) + local reqs = get_requires(ast) + return SemanticInfo(assignments, reqs) end return { diff --git a/src/model/lang/lua/parser.lua b/src/model/lang/lua/parser.lua index b5c66baa..ad3cf448 100644 --- a/src/model/lang/lua/parser.lua +++ b/src/model/lang/lua/parser.lua @@ -5,7 +5,7 @@ require("util.debug") require("util.string.string") require("util.dequeue") ---- @class luaAST : token[] +--- @class luaAST : token --- @alias CPos 'first'|'last' @@ -288,6 +288,7 @@ return function(lib) local w = wrap or 80 local ok, r = parse(code) if ok then + --- @diagnostic disable-next-line: param-type-mismatch local src = ast_to_src(r, {}, w) return string.lines(src) end @@ -419,11 +420,24 @@ return function(lib) end end + --- @param code str + --- @param n integer? + --- @return string[]? + local function trunc(code, n) + local lines = n or 1 + if lines == 1 then + local txt = string.lines(code) + local line1 = txt[1]:sub(1, -1) .. '…' + return { line1 } + end + end + return { parse = parse, pprint = pprint, highlighter = highlighter, ast_to_src = ast_to_src, chunker = chunker, + trunc = trunc, } end diff --git a/src/model/lang/lua/semantic_info.lua b/src/model/lang/lua/semantic_info.lua new file mode 100644 index 00000000..908c8d15 --- /dev/null +++ b/src/model/lang/lua/semantic_info.lua @@ -0,0 +1,27 @@ +--- @class SemanticInfoBase +--- @field name string +--- @field line integer + +--- @alias AssignmentType +--- | 'function' +--- | 'method' +--- | 'local' +--- | 'global' +--- | 'field' + +--- @class Assignment : SemanticInfoBase +--- @field type AssignmentType + +--- @class Require : SemanticInfoBase + +local class = require('util.class') + +--- @class SemanticInfo +--- @field assignments Assignment[] +--- @field requires Require[] +SemanticInfo = class.create(function(asn, reqs) + return { + assignments = asn or {}, + requires = reqs or {}, + } +end) diff --git a/src/model/lang/md/parser.lua b/src/model/lang/md/parser.lua index 6d724b93..d5fccf89 100644 --- a/src/model/lang/md/parser.lua +++ b/src/model/lang/md/parser.lua @@ -69,7 +69,7 @@ end --- @param input str --- @param skip_posinfo boolean? ---- @return AST -- djot AST, distinct from metalua +--- @return djotAST local function parse(input, skip_posinfo) local text = string.unlines(input) local posinfo = not (skip_posinfo == true) diff --git a/src/types.lua b/src/types.lua index 1a644972..25c60141 100644 --- a/src/types.lua +++ b/src/types.lua @@ -57,6 +57,7 @@ --- @field debugwidth integer --- @field drawableWidth number --- @field drawableChars integer +--- @field fold_lines integer --- @field drawtest boolean --- @field sizedebug boolean diff --git a/src/util/class.lua b/src/util/class.lua index a33b2818..ac72966d 100644 --- a/src/util/class.lua +++ b/src/util/class.lua @@ -1,7 +1,12 @@ +--- @alias Id string +--- @class Object +--- @field id Id + return { --- Simple factory, to spare boilerplate --- @param constructor function? - create = function(constructor) + --- @param lateinit function? + create = function(constructor, lateinit) local ret = {} ret.__index = ret local function new(...) @@ -16,8 +21,10 @@ return { if type(cls.new) == "function" then return cls.new(...) else - local instance = new(...) - setmetatable(instance, cls) + local instance = setmetatable(new(...), cls) + if type(lateinit) == "function" then + lateinit(instance) + end return instance end end, diff --git a/src/util/debug.lua b/src/util/debug.lua index 352af995..5656a121 100644 --- a/src/util/debug.lua +++ b/src/util/debug.lua @@ -122,6 +122,10 @@ local function terse_hash(t, level, prev_seen, jsonify) return res end +local function nontable(t) + return '(not a table) ' .. tostring(t) +end + --- @param a table? --- @param skip integer? local function terse_array(a, skip) @@ -142,7 +146,7 @@ local function terse_array(a, skip) return res else - return '' + return nontable(a) end end @@ -154,7 +158,9 @@ end --- @param style dumpstyle? --- @return string local function terse_ast(ast, skip_lineinfo, style) - if type(ast) ~= 'table' then return '' end + if type(ast) ~= 'table' then + return nontable(ast) + end local style = style or 'json5' --- @param t table? @@ -516,7 +522,7 @@ Log = { once = once, fire_once = function() - if not love.DEBUG then return end + if not love or not love.DEBUG then return end love.debug.once = love.debug.once + 1 end, --- @param color integer diff --git a/src/util/dequeue.lua b/src/util/dequeue.lua index 99955256..fb5c4917 100644 --- a/src/util/dequeue.lua +++ b/src/util/dequeue.lua @@ -172,7 +172,7 @@ function Dequeue:_checked(i, add, f) return ok, err end ---- Insert element at index +--- Pop element at index --- @param i integer --- @return boolean --- @return string|any err_or_result diff --git a/src/util/scrollableContent.lua b/src/util/scrollableContent.lua index b2e59e95..a5d4b98e 100644 --- a/src/util/scrollableContent.lua +++ b/src/util/scrollableContent.lua @@ -2,7 +2,7 @@ require("util.wrapped_text") require("util.scrollable") require("util.range") ---- @class ScrollableContent +--- @class ScrollableContent: WrappedText --- @field range Range? --- @field size integer --- @field size_max integer diff --git a/src/util/table.lua b/src/util/table.lua index d3d91ecc..dbf93fb3 100644 --- a/src/util/table.lua +++ b/src/util/table.lua @@ -281,6 +281,17 @@ function table.find_by(self, pred) end end +--- Find first element that the predicate holds for +--- @param self table[] +--- @param pred function +--- @return any? +function table.find_by_v(self, pred) + if not self or not pred then return end + for _, v in pairs(self) do + if pred(v) then return v end + end +end + --- Filter elements that satisfy the predicate --- enumerates sequentially --- @param self table[] @@ -352,3 +363,27 @@ function table.map(self, f) end return ret end + +--- Tabulate array values with index (returns new table) +--- @param self table +--- @param f function +--- @return table +function table.imap(self, f) + local ret = {} + for i, v in ipairs(self) do + ret[i] = f(v, i) + end + return ret +end + +--- Create a table of `n` elements by running `f` +--- @param n integer +--- @param f function +--- @return table +function table.fill(n, f) + local ret = {} + for i = 1, n do + ret[i] = f() + end + return ret +end diff --git a/src/util/view.lua b/src/util/view.lua index 385ab219..2dfec3d6 100644 --- a/src/util/view.lua +++ b/src/util/view.lua @@ -21,8 +21,8 @@ end --- @param cfg ViewConfig local write_line = function(l, str, y, breaks, cfg) local dy = y - (-l + 1 + breaks) * cfg.fh - G.setFont(cfg.font) - G.print(str, 0, dy) + gfx.setFont(cfg.font) + gfx.print(str, 0, dy) end --- Write a token to output @@ -34,17 +34,17 @@ end --- @param selected boolean local write_token = function(dy, dx, token, color, bgcolor, selected) - G.push('all') + gfx.push('all') if selected then - G.setColor(color) + gfx.setColor(color) local back = string.rep('█', string.ulen(token)) - G.print(back, dx, dy) - G.setColor(bgcolor) + gfx.print(back, dx, dy) + gfx.setColor(bgcolor) else - G.setColor(color) + gfx.setColor(color) end - G.print(token, dx, dy) - G.pop() + gfx.print(token, dx, dy) + gfx.pop() end --- Hide elements for debugging @@ -73,61 +73,61 @@ BlendMode = Alpha AlphaMode local blendModes = { { -- 1 name = 'Alpha AlphaM', - blend = function() G.setBlendMode('alpha', "alphamultiply") end + blend = function() gfx.setBlendMode('alpha', "alphamultiply") end }, { -- 2 name = 'Alpha PreM', - blend = function() G.setBlendMode('alpha', "premultiplied") end + blend = function() gfx.setBlendMode('alpha', "premultiplied") end }, -- add { name = 'Add AlphaM', - blend = function() G.setBlendMode('add', "alphamultiply") end + blend = function() gfx.setBlendMode('add', "alphamultiply") end }, { name = 'Add PreM', - blend = function() G.setBlendMode('add', "premultiplied") end + blend = function() gfx.setBlendMode('add', "premultiplied") end }, -- subtract { name = 'Subtract AlphaM', - blend = function() G.setBlendMode('subtract', "alphamultiply") end + blend = function() gfx.setBlendMode('subtract', "alphamultiply") end }, { name = 'Subtract PreM', - blend = function() G.setBlendMode('subtract', "premultiplied") end + blend = function() gfx.setBlendMode('subtract', "premultiplied") end }, -- replace { name = 'Replace AlphaM', - blend = function() G.setBlendMode('replace', "alphamultiply") end + blend = function() gfx.setBlendMode('replace', "alphamultiply") end }, { name = 'Replace PreM', - blend = function() G.setBlendMode('replace', "premultiplied") end + blend = function() gfx.setBlendMode('replace', "premultiplied") end }, -- pre only { name = 'Multiply PreM', - blend = function() G.setBlendMode('multiply', "premultiplied") end + blend = function() gfx.setBlendMode('multiply', "premultiplied") end }, { name = 'Darken PreM', - blend = function() G.setBlendMode('darken', "premultiplied") end + blend = function() gfx.setBlendMode('darken', "premultiplied") end }, { name = 'Lighten PreM', - blend = function() G.setBlendMode('lighten', "premultiplied") end + blend = function() gfx.setBlendMode('lighten', "premultiplied") end }, -- screen { name = 'Screen AlphaM', - blend = function() G.setBlendMode('screen', "alphamultiply") end + blend = function() gfx.setBlendMode('screen', "alphamultiply") end }, { name = 'Screen PreM', - blend = function() G.setBlendMode('screen', "premultiplied") end + blend = function() gfx.setBlendMode('screen', "premultiplied") end }, } @@ -188,7 +188,7 @@ local function draw_hl_text(text, highlight, cfg, options) end end end - G.setColor(color) + gfx.setColor(color) local dy = (t_l - 1) * fh local dx = (c - 1) * fw write_token(dy, dx, char, color, bg, false) diff --git a/src/util/wrapped_text.lua b/src/util/wrapped_text.lua index 28b1aab1..e375eede 100644 --- a/src/util/wrapped_text.lua +++ b/src/util/wrapped_text.lua @@ -37,7 +37,7 @@ require("util.lua") --- @field n_breaks integer --- --- @field wrap function ---- @field get_text function +--- @field get_text fun(self): Dequeue --- @field get_line function --- @field get_text_length function WrappedText = class.create() diff --git a/src/view/canvas/bgView.lua b/src/view/canvas/bgView.lua index 512137d8..ec660a14 100644 --- a/src/view/canvas/bgView.lua +++ b/src/view/canvas/bgView.lua @@ -10,14 +10,14 @@ function BGView:draw(drawable_height) local w = cfg.w local fh = cfg.fh - G.push('all') + gfx.push('all') -- background in case input is not visible - G.rectangle("fill", + gfx.rectangle("fill", 0, drawable_height - 2, w, fh * 2 + 2 ) - G.pop() + gfx.pop() end diff --git a/src/view/canvas/canvasView.lua b/src/view/canvas/canvasView.lua index 9cf794da..d12f5197 100644 --- a/src/view/canvas/canvasView.lua +++ b/src/view/canvas/canvasView.lua @@ -4,7 +4,7 @@ require("view.canvas.terminalView") local class = require("util.class") require("util.view") -local G = love.graphics +local gfx = love.graphics --- @class CanvasView : ViewBase --- @field bg BGView @@ -26,60 +26,60 @@ function CanvasView:draw( local cfg = self.cfg local test = cfg.drawtest - G.reset() - G.push('all') - G.setBlendMode('alpha', 'alphamultiply') -- default + gfx.reset() + gfx.push('all') + gfx.setBlendMode('alpha', 'alphamultiply') -- default if ViewUtils.conditional_draw('show_snapshot') then if snapshot then - G.draw(snapshot) + gfx.draw(snapshot) end self.bg:draw(drawable_height) end if not test then if ViewUtils.conditional_draw('show_terminal') then - -- G.setBlendMode('multiply', "premultiplied") + -- gfx.setBlendMode('multiply', "premultiplied") TerminalView.draw(terminal, term_canvas, snapshot) end if ViewUtils.conditional_draw('show_canvas') then - G.draw(canvas) + gfx.draw(canvas) end - G.setBlendMode('alpha', 'alphamultiply') -- default + gfx.setBlendMode('alpha', 'alphamultiply') -- default else - G.setBlendMode('alpha', 'alphamultiply') -- default + gfx.setBlendMode('alpha', 'alphamultiply') -- default for i = 0, love.test_grid_y - 1 do for j = 0, love.test_grid_x - 1 do local off_x = cfg.debugwidth * cfg.fw local off_y = cfg.debugheight * cfg.fh local dx = j * off_x local dy = i * off_y - G.reset() - G.translate(dx, dy) + gfx.reset() + gfx.translate(dx, dy) local index = (i * love.test_grid_x) + j + 1 local b = ViewUtils.blendModes[index] if b then - -- G.setBlendMode('alpha') -- default + -- gfx.setBlendMode('alpha') -- default if ViewUtils.conditional_draw('show_terminal') then b.blend() TerminalView.draw(terminal, term_canvas, snapshot) end - G.setBlendMode('alpha') -- default + gfx.setBlendMode('alpha') -- default if ViewUtils.conditional_draw('show_canvas') then - G.draw(canvas) + gfx.draw(canvas) end - G.setBlendMode('alpha') -- default - G.setColor(1, 1, 1, 1) - G.setFont(cfg.labelfont) + gfx.setBlendMode('alpha') -- default + gfx.setColor(1, 1, 1, 1) + gfx.setFont(cfg.labelfont) - -- G.print(index .. ' ' .. b.name) - G.print(b.name) + -- gfx.print(index .. ' ' .. b.name) + gfx.print(b.name) end end end end - G.pop() + gfx.pop() end diff --git a/src/view/canvas/terminalView.lua b/src/view/canvas/terminalView.lua index 50c56bf6..12a44759 100644 --- a/src/view/canvas/terminalView.lua +++ b/src/view/canvas/terminalView.lua @@ -1,4 +1,4 @@ -local G = love.graphics +local gfx = love.graphics --- @class TerminalView TerminalView = {} @@ -10,11 +10,11 @@ local function terminal_draw(terminal, canvas, overlay) terminal.char_width, terminal.char_height -- if terminal.dirty or overlay then - G.push('all') + gfx.push('all') - G.setCanvas(canvas) - G.setFont(terminal.font) - G.clear(terminal.clear_color_alpha) + gfx.setCanvas(canvas) + gfx.setFont(terminal.font) + gfx.clear(terminal.clear_color_alpha) local font_height = terminal.font:getHeight() for y, row in ipairs(terminal.buffer) do @@ -41,33 +41,33 @@ local function terminal_draw(terminal, canvas, overlay) -- Character background if not overlay then - G.setColor(unpack(bg)) - G.rectangle("fill", + gfx.setColor(unpack(bg)) + gfx.rectangle("fill", left, top + (font_height - char_height), char_width, char_height) end - local bm, am = G.getBlendMode() - G.setBlendMode('alpha', "alphamultiply") + local bm, am = gfx.getBlendMode() + gfx.setBlendMode('alpha', "alphamultiply") -- Character - G.setColor(unpack(fg)) - G.print(char, left, top) + gfx.setColor(unpack(fg)) + gfx.print(char, left, top) - G.setBlendMode(bm, am) + gfx.setBlendMode(bm, am) state.dirty = false -- end end end terminal.dirty = false - G.pop() + gfx.pop() -- end if terminal.show_cursor then - G.setFont(terminal.font) + gfx.setFont(terminal.font) if love.timer.getTime() % 1 > 0.5 then - G.print("_", + gfx.print("_", (terminal.cursor_x - 1) * char_width, (terminal.cursor_y - 1) * char_height) end @@ -76,15 +76,15 @@ end --- @param terminal table function TerminalView.draw(terminal, canvas, snapshot) - G.setCanvas() - G.push('all') + gfx.setCanvas() + gfx.push('all') if snapshot then terminal_draw(terminal, canvas, true) else terminal_draw(terminal, canvas) end - G.draw(canvas) - G.setBlendMode('alpha') -- default - G.pop() + gfx.draw(canvas) + gfx.setBlendMode('alpha') -- default + gfx.pop() end diff --git a/src/view/consoleView.lua b/src/view/consoleView.lua index a46fa024..2f8be205 100644 --- a/src/view/consoleView.lua +++ b/src/view/consoleView.lua @@ -8,7 +8,7 @@ require("util.color") require("util.view") require("util.debug") -local G = love.graphics +local gfx = love.graphics --- @param cfg Config --- @param ctrl ConsoleController @@ -73,15 +73,15 @@ function ConsoleView:draw_placeholder() local band = self.cfg.view.fh local w = self.cfg.view.w local h = self.cfg.view.h - G.push('all') - G.setColor(Color[Color.yellow]) + gfx.push('all') + gfx.setColor(Color[Color.yellow]) for o = -h, w, 2 * band do - G.polygon("fill" + gfx.polygon("fill" , o + 0, h , o + h, 0 , o + h + band, 0 , o + band, h ) end - G.pop() + gfx.pop() end diff --git a/src/view/editor/bufferView.lua b/src/view/editor/bufferView.lua index b4a8aded..4f43eb64 100644 --- a/src/view/editor/bufferView.lua +++ b/src/view/editor/bufferView.lua @@ -15,12 +15,12 @@ local function new(cfg) cfg = cfg, LINES = l, SCROLL_BY = math.floor(l / 2), - w = cfg.drawableChars, + wrap_w = cfg.drawableChars, content = nil, content_type = nil, more = { up = false, down = false }, - offset = 0, + buffer = nil } end @@ -28,12 +28,12 @@ end --- @class BufferView : ViewBase --- @field content VisibleContent|VisibleStructuredContent --- @field content_type ContentType ---- @field buffer BufferModel +--- @field buffers Dequeue --- +--- @field cfg ViewConfig --- @field LINES integer --- @field SCROLL_BY integer ---- @field w integer ---- @field offset integer +--- @field wrap_w integer --- @field more More --- --- @field open function @@ -53,32 +53,31 @@ function BufferView:open(buffer) if not self.buffer then error('no buffer') end - local cont = buffer.content_type - self.content_type = cont + local ct = buffer.content_type + self.content_type = ct - if cont == 'plain' or cont == 'md' then + if ct == 'plain' or ct == 'md' then local bufcon = buffer:get_text_content() self.buffer:highlight() self.content = VisibleContent( - self.w, bufcon, self.SCROLL_BY, L) + self.wrap_w, bufcon, self.SCROLL_BY, L) self.hl = self.buffer:get_highlight() - elseif cont == 'lua' then + elseif ct == 'lua' then local bufcon = buffer:get_content() self.content = - VisibleStructuredContent( - self.w, + VisibleStructuredContent({ + wrap_w = self.wrap_w, + overscroll_max = self.SCROLL_BY, + size_max = L, + cfg = self.cfg, + }, bufcon, - buffer.highlighter, - self.SCROLL_BY, - L) + buffer.highlighter) else error 'unknown filetype' end - -- TODO clean this up - local clen = self.content:get_text_length() - self.offset = math.max(clen - L, 0) - local off = self.offset + local off = self.content.offset if off > 0 then self.more.up = true end @@ -118,7 +117,7 @@ function BufferView:get_state() return { filename = buf.name, selection = buf.selection, - offset = self.offset, + offset = self.content.offset, } end @@ -139,7 +138,7 @@ function BufferView:refresh(moved) local sel = self.buffer:get_selection() if self.content_type == 'lua' then local vsc = self.content - local blocks = vsc.blocks + local blocks = vsc.v_blocks blocks:move(moved, sel) vsc:recalc_range() else @@ -150,7 +149,7 @@ function BufferView:refresh(moved) end local clen = self.content:get_content_length() - local off = self.offset + local off = self.content.offset local si = 1 + off local ei = math.min(self.LINES, clen + 1) + off self:_update_visible(Range(si, ei)) @@ -160,6 +159,11 @@ end --- scrolling --- ------------------- +--- @return integer +function BufferView:get_offset() + return self.content.offset +end + --- @private --- @return Range function BufferView:_get_end_range() @@ -190,8 +194,7 @@ function BufferView:scroll(dir, by, warp) end end end)() - local o = self.content:move_range(n) - self.offset = self.offset + o + self.content:move_range(n) end --- @param off integer @@ -253,9 +256,13 @@ function BufferView:follow_selection() end end +-------------- +--- draw --- +-------------- + --- @param special boolean function BufferView:draw(special) - local G = love.graphics + local gfx = love.graphics local cf_colors = self.cfg.colors local colors = cf_colors.editor local font = self.cfg.font @@ -265,17 +272,17 @@ function BufferView:draw(special) --- @type VisibleContent|VisibleStructuredContent local content_text = vc:get_visible() local last_line_n = #content_text - local width, height = G.getDimensions() + local width, height = gfx.getDimensions() local draw_background = function() - G.push('all') - G.setColor(colors.bg) - G.rectangle("fill", 0, 0, width, height) - G.setColor(Color.with_alpha(colors.fg, .0625)) + gfx.push('all') + gfx.setColor(colors.bg) + gfx.rectangle("fill", 0, 0, width, height) + gfx.setColor(Color.with_alpha(colors.fg, .0625)) local bh = math.min(last_line_n, self.cfg.lines) * fh - G.rectangle("fill", 0, 0, width, bh) - G.pop() + gfx.rectangle("fill", 0, 0, width, bh) + gfx.pop() end local draw_highlight = function() @@ -284,19 +291,19 @@ function BufferView:draw(special) local highlight_line = function(ln) if not ln then return end if special then - G.setColor(colors.highlight_special) + gfx.setColor(colors.highlight_special) else if ls then - G.setColor(colors.highlight_loaded) + gfx.setColor(colors.highlight_loaded) else - G.setColor(colors.highlight) + gfx.setColor(colors.highlight) end end local l_y = (ln - 1) * fh - G.rectangle('fill', 0, l_y, width, fh) + gfx.rectangle('fill', 0, l_y, width, fh) end - local off = self.offset + local off = self.content.offset for _, w in ipairs(ws) do for _, v in ipairs(w) do if self.content.range:inc(v) then @@ -313,7 +320,7 @@ function BufferView:draw(special) end local draw_text = function() - G.setFont(font) + gfx.setFont(font) if self.content_type == 'lua' then local vbl = vc:get_visible_blocks() for _, block in ipairs(vbl) do @@ -323,7 +330,7 @@ function BufferView:draw(special) local text = wt:get_text() local highlight = { hl = block.highlight } local ltf = function(l) - return l + rs - 1 - self.offset + return l + rs - 1 - self.content.offset end local ctf = function(a) return a end local limit = self.cfg.lines @@ -337,9 +344,9 @@ function BufferView:draw(special) if love.DEBUG then --- phantom text - G.setColor(Color.with_alpha(colors.fg, 0.3)) + gfx.setColor(Color.with_alpha(colors.fg, 0.3)) local text = string.unlines(content_text) - G.print(text) + gfx.print(text) end elseif self.content_type == 'md' then local text = vc:get_visible() @@ -357,10 +364,10 @@ function BufferView:draw(special) ltf = ltf, ctf = ctf, limit = limit, }) elseif self.content_type == 'plain' then - G.setColor(colors.fg) + gfx.setColor(colors.fg) local text = string.unlines(content_text) - G.print(text) + gfx.print(text) end end @@ -370,24 +377,24 @@ function BufferView:draw(special) local lnc = colors.fg local x = self.cfg.w - font:getWidth(' ') - 3 local lnvc = Color.with_alpha(lnc, 0.2) - G.setColor(lnvc) - G.rectangle("fill", x, 0, 2, self.cfg.h) + gfx.setColor(lnvc) + gfx.rectangle("fill", x, 0, 2, self.cfg.h) local seen = {} for ln = 1, self.LINES do local l_y = (ln - 1) * fh - local vln = ln + self.offset + local vln = ln + self.content.offset local ln_w = self.content.wrap_reverse[vln] if ln_w then local l = string.format('%3d', ln_w) local l_x = self.cfg.w - font:getWidth(l) local l_xv = l_x - font:getWidth(l) - 3.5 if showap then - G.setColor(lnvc) - G.print(string.format('%3d', vln), l_xv, l_y) + gfx.setColor(lnvc) + gfx.print(string.format('%3d', vln), l_xv, l_y) end if not seen[ln_w] then - G.setColor(lnc) - G.print(l, l_x, l_y) + gfx.setColor(lnc) + gfx.print(l, l_x, l_y) seen[ln_w] = true end end diff --git a/src/view/editor/editorView.lua b/src/view/editor/editorView.lua index e86b68e0..f6cd20a4 100644 --- a/src/view/editor/editorView.lua +++ b/src/view/editor/editorView.lua @@ -12,7 +12,7 @@ local function new(cfg, ctrl) cfg = cfg, controller = ctrl, input = UserInputView(cfg, ctrl.input), - buffer = BufferView(cfg), + buffers = {}, search = SearchView(cfg, ctrl.search), } --- hook the view in the controller @@ -23,7 +23,7 @@ end --- @class EditorView : ViewBase --- @field controller EditorController --- @field input UserInputView ---- @field buffer BufferView +--- @field buffers { [string]: BufferView } --- @field search SearchView EditorView = class.create(new) @@ -34,7 +34,8 @@ function EditorView:draw() self.search:draw(ctrl.search:get_input()) else local spec = mode == 'reorder' - self.buffer:draw(spec) + local bv = self:get_current_buffer() + bv:draw(spec) if ViewUtils.conditional_draw('show_input') then local input = ctrl:get_input() self.input:draw(input) @@ -42,7 +43,35 @@ function EditorView:draw() end end +--- @param buffer BufferModel +--- @return BufferView +function EditorView:open(buffer) + local bid = buffer:get_id() + local opn = self.buffers[bid] + if not opn then + local v = BufferView(self.cfg) + self.buffers[bid] = v + v:open(buffer) + return v + end + return opn +end + +--- @return BufferView +function EditorView:get_current_buffer() + local ctrl = self.controller + local bm = ctrl:get_active_buffer() + local bid = bm:get_id() + return self.buffers[bid] +end + +--- @param bid string +--- @return BufferView +function EditorView:get_buffer(bid) + return self.buffers[bid] +end + --- @param moved integer? function EditorView:refresh(moved) - self.buffer:refresh(moved) + self:get_current_buffer():refresh(moved) end diff --git a/src/view/editor/search/resultsView.lua b/src/view/editor/search/resultsView.lua index 880e8ee1..c24015f5 100644 --- a/src/view/editor/search/resultsView.lua +++ b/src/view/editor/search/resultsView.lua @@ -16,14 +16,14 @@ ResultsView = class.create(new) function ResultsView:draw(results) local colors = self.cfg.colors.editor local fh = self.cfg.fh * 1.032 -- magic constant - local width, height = G.getDimensions() + local width, height = gfx.getDimensions() local has_results = (results.results and #(results.results) > 0) local draw_background = function() - G.push('all') - G.setColor(colors.results.bg) - G.rectangle("fill", 0, 0, width, height) - G.pop() + gfx.push('all') + gfx.setColor(colors.results.bg) + gfx.rectangle("fill", 0, 0, width, height) + gfx.pop() end local draw_results = function() @@ -40,33 +40,33 @@ function ResultsView:draw(results) return "" end end - G.push('all') - G.setFont(self.cfg.font) + gfx.push('all') + gfx.setFont(self.cfg.font) if not has_results then - G.setColor(Color.with_alpha(colors.results.fg, 0.5)) - G.print("No results", 25, 0) + gfx.setColor(Color.with_alpha(colors.results.fg, 0.5)) + gfx.print("No results", 25, 0) else for i, v in ipairs(results.results) do local ln = i local lh = (ln - 1) * fh local t = v.r.type local label = getLabel(t) - G.setColor(Color.with_alpha(colors.results.fg, 0.5)) - G.print(label, 2, lh + 2) - G.setColor(colors.results.fg) - G.print(v.r.name, 25, lh) + gfx.setColor(Color.with_alpha(colors.results.fg, 0.5)) + gfx.print(label, 2, lh + 2) + gfx.setColor(colors.results.fg) + gfx.print(v.r.name, 25, lh) end end - G.pop() + gfx.pop() end local draw_selection = function() local highlight_line = function(ln) if not ln then return end - G.setColor(colors.highlight) + gfx.setColor(colors.highlight) local l_y = (ln - 1) * fh - G.rectangle('fill', 0, l_y, width, fh) + gfx.rectangle('fill', 0, l_y, width, fh) end local v = results.selection highlight_line(v) diff --git a/src/view/editor/visibleContent.lua b/src/view/editor/visibleContent.lua index 68223f78..9ab4d7f6 100644 --- a/src/view/editor/visibleContent.lua +++ b/src/view/editor/visibleContent.lua @@ -134,6 +134,7 @@ function VisibleContent:move_range(by) if r then local nr, n = r:translate_limit(by, 1, upper) self:set_range(nr) + self.offset = nr.start - 1 return n end end diff --git a/src/view/editor/visibleStructuredContent.lua b/src/view/editor/visibleStructuredContent.lua index 4c63c50d..d0cddb37 100644 --- a/src/view/editor/visibleStructuredContent.lua +++ b/src/view/editor/visibleStructuredContent.lua @@ -1,16 +1,24 @@ require("view.editor.visibleBlock") require("util.wrapped_text") +require("util.scrollable") require("util.range") +--- @class VSCOpts +--- @field wrap_w integer +--- @field size_max integer +--- @field overscroll_max integer +--- @field cfg ViewConfig --- @alias ReverseMap Dequeue --- Inverse mapping from line number to block index --- @class VisibleStructuredContent: WrappedText ---- @field overscroll_max integer +--- @field offset integer +--- @field overscroll integer +--- @field highlighter fun(c: string[]): SyntaxColoring --- @field size_max integer --- @field range Range? ---- @field blocks Dequeue +--- @field v_blocks Dequeue --- @field reverse_map ReverseMap --- --- @field set_range fun(self, Range) @@ -36,19 +44,19 @@ setmetatable(VisibleStructuredContent, { end, }) ---- @param w integer +--- @param opts VSCOpts --- @param blocks Block[] --- @param highlighter fun(c: string[]): SyntaxColoring ---- @param overscroll integer ---- @param size_max integer --- @return VisibleStructuredContent -function VisibleStructuredContent.new(w, blocks, highlighter, - overscroll, size_max) +function VisibleStructuredContent.new( + opts, + blocks, + highlighter) local self = setmetatable({ + overscroll = opts.overscroll_max, + opts = opts, highlighter = highlighter, - size_max = size_max, - overscroll_max = overscroll, - w = w, + offset = 0, }, VisibleStructuredContent) self:load_blocks(blocks) self:to_end() @@ -59,7 +67,7 @@ end --- Set the visible range so that last of the content is visible function VisibleStructuredContent:to_end() self.range = Scrollable.to_end( - self.size_max, self:get_text_length()) + self.opts.size_max, self:get_text_length()) self.offset = self.range.start - 1 end @@ -70,16 +78,17 @@ function VisibleStructuredContent:load_blocks(blocks) local revmap = Dequeue.typed('integer') local visible_blocks = Dequeue() local off = 0 + local w = self.opts.wrap_w for bi, v in ipairs(blocks) do if v:is_empty() then fulltext:append('') local npos = v.pos:translate(off) visible_blocks:append( - VisibleBlock(self.w, { '' }, {}, v.pos, npos)) + VisibleBlock(w, { '' }, {}, v.pos, npos)) else fulltext:append_all(v.lines) local hl = self.highlighter(v.lines) - local vblock = VisibleBlock(self.w, v.lines, hl, + local vblock = VisibleBlock(w, v.lines, hl, v.pos, v.pos:translate(off)) off = off + vblock.wrapped.n_breaks visible_blocks:append(vblock) @@ -90,15 +99,15 @@ function VisibleStructuredContent:load_blocks(blocks) end end end - WrappedText._init(self, self.w, fulltext) + WrappedText._init(self, self.opts.wrap_w, fulltext) self:_init() self.reverse_map = revmap - self.blocks = visible_blocks + self.v_blocks = visible_blocks end function VisibleStructuredContent:recalc_range() local ln, aln = 1, 1 - for _, v in ipairs(self.blocks) do + for _, v in ipairs(self.v_blocks) do local l = #(v.wrapped.orig) local al = #(v.wrapped.text) v.pos = Range(ln, ln + l - 1) @@ -121,7 +130,7 @@ end --- @protected function VisibleStructuredContent:_update_overscroll() local len = WrappedText.get_text_length(self) - local over = math.min(self.overscroll_max, len) + local over = math.min(self.opts.overscroll_max, len) self.overscroll = over end @@ -154,6 +163,7 @@ function VisibleStructuredContent:move_range(by) local upper = self:get_text_length() + self.overscroll local nr, n = r:translate_limit(by, 1, upper) self:set_range(nr) + self.offset = nr.start - 1 return n end return 0 @@ -168,7 +178,7 @@ function VisibleStructuredContent:get_visible_blocks() local si = self.wrap_reverse[self.range.start] local ei = self.wrap_reverse[self.range.fin] local sbi, sei = self.reverse_map[si], self.reverse_map[ei] - return table.slice(self.blocks, sbi, sei) + return table.slice(self.v_blocks, sbi, sei) end --- @return integer @@ -179,22 +189,22 @@ end --- @param bn integer --- @return Range? function VisibleStructuredContent:get_block_pos(bn) - local cl = #(self.blocks) + local cl = #(self.v_blocks) if bn > 0 and bn <= cl then - return self.blocks[bn].pos + return self.v_blocks[bn].pos elseif cl == 0 then --- empty/new file Range.singleton(1) elseif bn == cl + 1 then - return Range.singleton(self.blocks[cl].pos.fin + 1) + return Range.singleton(self.v_blocks[cl].pos.fin + 1) end end --- @param bn integer --- @return Range? function VisibleStructuredContent:get_block_app_pos(bn) - local cl = #(self.blocks) + local cl = #(self.v_blocks) if bn > 0 and bn <= cl then - return self.blocks[bn].app_pos + return self.v_blocks[bn].app_pos elseif bn == cl + 1 then local wr = self.wrap_reverse return Range.singleton(#wr) diff --git a/src/view/input/statusline.lua b/src/view/input/statusline.lua index ad2f5fa9..e1cf7669 100644 --- a/src/view/input/statusline.lua +++ b/src/view/input/statusline.lua @@ -10,7 +10,7 @@ end) --- @param nLines integer --- @param time number? function Statusline:draw(status, nLines, time) - local G = love.graphics + local gfx = love.graphics local cf = self.cfg local colors = (function() if love.state.app_state == 'inspect' then @@ -34,10 +34,10 @@ function Statusline:draw(status, nLines, time) local midX = (start_box.x + w) / 2 local function drawBackground() - G.setColor(colors.bg) - G.setFont(font) + gfx.setColor(colors.bg) + gfx.setFont(font) local corr = 2 -- correct for fractional slit left under the terminal - G.rectangle("fill", start_box.x, start_box.y - corr, w, fh + corr) + gfx.rectangle("fill", start_box.x, start_box.y - corr, w, fh + corr) end --- @param m More? @@ -63,21 +63,22 @@ function Statusline:draw(status, nLines, time) y = start_box.y - 2, } - G.setColor(colors.fg) + gfx.setColor(colors.fg) local label = status.label if label then - G.print(label, start_text.x, start_text.y) + gfx.print(label, start_text.x, start_text.y) end if love.DEBUG then - G.setColor(cf.colors.debug) + gfx.setColor(cf.colors.debug) if love.state.testing then - G.print('testing', midX - (8 * cf.fw), start_text.y) + gfx.print('testing', midX - (8 * cf.fw), start_text.y) end - G.print(love.state.app_state, midX - (13 * cf.fw), start_text.y) + gfx.print((love.state.app_state or '???'), + midX - (13 * cf.fw), start_text.y) if time then - G.print(tostring(time), midX, start_text.y) + gfx.print(tostring(time), midX, start_text.y) end - G.setColor(colors.fg) + gfx.setColor(colors.fg) end local c = status.cursor @@ -98,50 +99,50 @@ function Statusline:draw(status, nLines, time) local more_b = morelabel(custom.buffer_more) .. ' ' local more_i = morelabel(status.input_more) .. ' ' - G.setColor(colors.fg) - local w_il = G.getFont():getWidth(" 999:9999") - local w_br = G.getFont():getWidth("B999 L999-999(99)") - local w_mb = G.getFont():getWidth(" ↕↕ ") - local w_mi = G.getFont():getWidth(" ↕↕ ") + 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 s_mb = endTextX - w_br - w_il - w_mi - w_mb - local cw_p = G.getFont():getWidth(t_blp) - local cw_il = G.getFont():getWidth(t_ic) + local cw_p = gfx.getFont():getWidth(t_blp) + local cw_il = gfx.getFont():getWidth(t_ic) local sxl = endTextX - (cw_p + w_il + w_mi) local s_mi = endTextX - w_il - G.setFont(self.cfg.font) - G.setColor(colors.fg) - if colors.fg2 then G.setColor(colors.fg2) end + gfx.setFont(self.cfg.font) + gfx.setColor(colors.fg) + if colors.fg2 then gfx.setColor(colors.fg2) end --- cursor pos - G.print(t_ic, endTextX - cw_il, start_text.y) + gfx.print(t_ic, endTextX - cw_il, start_text.y) --- input more - G.print(more_i, s_mi, start_text.y - 3) + gfx.print(more_i, s_mi, start_text.y - 3) - G.setColor(colors.fg) + gfx.setColor(colors.fg) if custom.mode == 'reorder' and custom.content_type == 'plain' then - G.setColor(colors.special) + gfx.setColor(colors.special) end --- block line range / line - G.print(t_blp, sxl, start_text.y) - G.setColor(colors.fg) + gfx.print(t_blp, sxl, start_text.y) + gfx.setColor(colors.fg) --- block number if custom.content_type == 'lua' then - local bpw = G.getFont():getWidth(t_bbp) + local bpw = gfx.getFont():getWidth(t_bbp) local sxb = sxl - bpw if sel == lim then - G.setColor(colors.indicator) + gfx.setColor(colors.indicator) end if custom.mode == 'reorder' then - G.setColor(colors.special) + gfx.setColor(colors.special) end - G.print(t_bbp, sxb, start_text.y) + gfx.print(t_bbp, sxb, start_text.y) end --- buffer more - G.setColor(colors.fg) - G.print(more_b, s_mb, start_text.y) + gfx.setColor(colors.fg) + gfx.print(more_b, s_mb, start_text.y) else --- normal statusline local pos_c = ':' .. c.c @@ -154,22 +155,22 @@ function Statusline:draw(status, nLines, time) l_lim = status.n_lines end if ln == l_lim then - G.setColor(colors.indicator) + gfx.setColor(colors.indicator) end local pos_l = 'L' .. ln - local lw = G.getFont():getWidth(pos_l) - local cw = G.getFont():getWidth(pos_c) + local lw = gfx.getFont():getWidth(pos_l) + local cw = gfx.getFont():getWidth(pos_c) local sx = endTextX - (lw + cw) - G.print(pos_l, sx, start_text.y) - G.setColor(colors.fg) - G.print(pos_c, sx + lw, start_text.y) + gfx.print(pos_l, sx, start_text.y) + gfx.setColor(colors.fg) + gfx.print(pos_c, sx + lw, start_text.y) end end end - G.push('all') + gfx.push('all') drawBackground() drawStatus() - G.pop() + gfx.pop() end diff --git a/src/view/input/userInputView.lua b/src/view/input/userInputView.lua index ad63190d..625ec029 100644 --- a/src/view/input/userInputView.lua +++ b/src/view/input/userInputView.lua @@ -25,7 +25,7 @@ UserInputView = class.create(new) --- @param input InputDTO --- @param time number? function UserInputView:draw_input(input, time) - local G = love.graphics + local gfx = love.graphics local cfg = self.cfg local status = self.controller:get_status() @@ -46,7 +46,7 @@ function UserInputView:draw_input(input, time) local drawableWidth = cfg.drawableWidth local w = cfg.drawableChars -- drawtest hack - if drawableWidth < G.getWidth() / 3 then + if drawableWidth < gfx.getWidth() / 3 then w = w * 2 end @@ -103,15 +103,15 @@ function UserInputView:draw_input(input, time) local ch = start_y + (vcl - 1) * fh local x_offset = math.fmod(acc, w) - G.push('all') - G.setColor(cf_colors.input.cursor) - G.print('|', (x_offset - .5) * fw, ch) - G.pop() + gfx.push('all') + gfx.setColor(cf_colors.input.cursor) + gfx.print('|', (x_offset - .5) * fw, ch) + gfx.pop() end local drawBackground = function() - G.setColor(colors.bg) - G.rectangle("fill", + gfx.setColor(colors.bg) + gfx.rectangle("fill", 0, start_y, drawableWidth, @@ -120,7 +120,7 @@ function UserInputView:draw_input(input, time) local highlight = input.highlight local visible = vc:get_visible() - G.setFont(self.cfg.font) + gfx.setFont(self.cfg.font) drawBackground() self.statusline:draw(status, apparentLines, time) @@ -255,7 +255,7 @@ function UserInputView:draw_input(input, time) end else for l, str in ipairs(visible) do - G.setColor(colors.fg) + gfx.setColor(colors.fg) ViewUtils.write_line(l, str, start_y, 0, self.cfg) end end @@ -279,8 +279,8 @@ function UserInputView:draw(input, time) local apparentHeight = #err_text local start_y = h - inHeight local drawBackground = function() - G.setColor(colors.input.error_bg) - G.rectangle("fill", + gfx.setColor(colors.input.error_bg) + gfx.rectangle("fill", 0, start_y, drawableWidth, @@ -288,7 +288,7 @@ function UserInputView:draw(input, time) end drawBackground() - G.setColor(colors.input.error) + gfx.setColor(colors.input.error) for l, str in ipairs(err_text) do local breaks = 0 -- starting height is already calculated ViewUtils.write_line(l, str, start_y, breaks, self.cfg) diff --git a/src/view/titleView.lua b/src/view/titleView.lua index 5a4c8f42..27e2dd13 100644 --- a/src/view/titleView.lua +++ b/src/view/titleView.lua @@ -1,20 +1,20 @@ TitleView = { draw = function(title, x, y, w, custom_font) title = title or "LÖVEputer" - local prev_font = G.getFont() + local prev_font = gfx.getFont() local font = custom_font or prev_font local fh = font:getHeight() x = x or 0 - y = y or G.getHeight() - 2 * fh - w = w or G.getWidth() - G.setColor(Color[0]) - G.rectangle("fill", x, y, w, fh) + y = y or gfx.getHeight() - 2 * fh + w = w or gfx.getWidth() + gfx.setColor(Color[0]) + gfx.rectangle("fill", x, y, w, fh) local i = 1 local c = { 13, 12, 14, 10 } for lx = w - fh, w - 4 * fh, -fh do - G.setColor(Color[c[i]]) + gfx.setColor(Color[c[i]]) i = i + 1 - G.polygon("fill", + gfx.polygon("fill", lx, y, lx - fh, @@ -24,14 +24,14 @@ TitleView = { lx - fh, y + fh) end - G.setColor(Color[15]) + gfx.setColor(Color[15]) if custom_font then - G.setFont(font) + gfx.setFont(font) end - G.print(title, x + fh, y) + gfx.print(title, x + fh, y) if custom_font then - G.setFont(prev_font) + gfx.setFont(prev_font) end end } diff --git a/src/view/view.lua b/src/view/view.lua index 4cc29370..b56302ad 100644 --- a/src/view/view.lua +++ b/src/view/view.lua @@ -8,22 +8,22 @@ View = { --- @param C ConsoleController --- @param CV ConsoleView draw = function(C, CV) - G.push('all') + gfx.push('all') local terminal = C:get_terminal() local canvas = C:get_canvas() local input = C.input:get_input() CV:draw(terminal, canvas, input, canvas_snapshot) - G.pop() + gfx.pop() end, snap_canvas = function() - -- G.captureScreenshot(os.time() .. ".png") + -- gfx.captureScreenshot(os.time() .. ".png") if canvas_snapshot then View.clear_snapshot() collectgarbage() end - G.captureScreenshot(function(img) - canvas_snapshot = G.newImage(img) + gfx.captureScreenshot(function(img) + canvas_snapshot = gfx.newImage(img) end) end, diff --git a/tests/editor/buffer_spec.lua b/tests/editor/buffer_spec.lua index 68e40c57..a61ece0d 100644 --- a/tests/editor/buffer_spec.lua +++ b/tests/editor/buffer_spec.lua @@ -107,7 +107,7 @@ print(sierpinski(4))]]) describe('lua', function() local turtle = { '--- @diagnostic disable', - 'width, height = G.getDimensions()', + 'width, height = gfx.getDimensions()', 'midx = width / 2', 'midy = height / 2', 'incr = 5', @@ -119,15 +119,15 @@ print(sierpinski(4))]]) 'bg_color = Color.black', '', 'local function drawHelp()', - ' G.setColor(Color[Color.white])', - ' G.print("Press [I] to open console", 20, 20)', - ' G.print("Enter \'forward\', \'back\', \'left\', or \'right\' to move the turtle!", 20, 40)', + ' 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()', - ' G.setColor(Color[debugColor])', + ' gfx.setColor(Color[debugColor])', ' local label = string.format("Turtle position: (%d, %d)", tx, ty)', - ' G.print(label, width - 200, 20)', + ' gfx.print(label, width - 200, 20)', 'end', '', 'function love.draw()', @@ -212,13 +212,13 @@ print(sierpinski(4))]]) assert.same('', embuf:get_text_content()[1]) embuf:move_selection('down', 2) assert.same(3, embuf:get_selection()) - assert.same({ 'width, height = G.getDimensions()' }, embuf:get_selected_text()) + assert.same({ 'width, height = gfx.getDimensions()' }, embuf:get_selected_text()) local res = { '', '--- @diagnostic disable', '', - 'width, height = G.getDimensions()', + 'width, height = gfx.getDimensions()', 'midx = width / 2', } assert.same(3, embuf:get_selection()) @@ -229,7 +229,7 @@ print(sierpinski(4))]]) embuf:insert_newline() assert.same(res, table.take(embuf:get_text_content(), 5)) embuf:move_selection('down') - assert.same({ 'width, height = G.getDimensions()' }, embuf:get_selected_text()) + assert.same({ 'width, height = gfx.getDimensions()' }, embuf:get_selected_text()) embuf:insert_newline() assert.same(res, table.take(embuf:get_text_content(), 5)) end) diff --git a/tests/editor/editor_spec.lua b/tests/editor/editor_spec.lua index 4e052c41..f0953a10 100644 --- a/tests/editor/editor_spec.lua +++ b/tests/editor/editor_spec.lua @@ -130,7 +130,7 @@ describe('Editor #editor', function() 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], model.buffer:get_selected_text()) + assert.same(turtle_doc[2], buffer:get_selected_text()) --- load it local input = function() return controller.input:get_text():items() @@ -190,10 +190,11 @@ describe('Editor #editor', function() local save = TU.get_save_function(sierpinski) --- use it as plaintext for this test controller:open('sierpinski.txt', sierpinski, save) - view.buffer:open(model.buffer) + local buf = controller:get_active_buffer() + local bv = view:open(buf) - local visible = view.buffer.content - local scroll = view.buffer.SCROLL_BY + local visible = bv.content + local scroll = bv.SCROLL_BY local off = #sierpinski - l + 1 local start_range = Range(off + 1, #sierpinski + 1) @@ -201,7 +202,7 @@ describe('Editor #editor', function() it('loads', function() --- inital scroll is at EOF, meaning last l lines are visible --- plus the phantom line - assert.same(off, view.buffer.offset) + assert.same(off, bv:get_offset()) assert.same(start_range, visible.range) end) local base = Range(1, l) @@ -238,7 +239,6 @@ describe('Editor #editor', function() local l = 6 local controller, _, view = wire(getMockConf(27, l)) - local model = controller.model local save = TU.get_save_function(sierpinski) controller:open('sierpinski.txt', sierpinski, save) @@ -249,11 +249,11 @@ describe('Editor #editor', function() local buffer = controller:get_active_buffer() --- @type BufferView - local bv = view.buffer - bv:open(model.buffer) + local bv = view:open(buffer) + -- bv:open(buffer) - local visible = view.buffer.content - local scroll = view.buffer.SCROLL_BY + local visible = bv.content + local scroll = bv.SCROLL_BY local clen = visible:get_content_length() local off = clen - l + 1 @@ -261,7 +261,7 @@ describe('Editor #editor', function() it('loads', function() --- inital scroll is at EOF, meaning last l lines are visible --- plus the phantom line - assert.same(off, view.buffer.offset) + assert.same(off, bv:get_offset()) assert.same(start_range, visible.range) end) local base = Range(1, l) diff --git a/tests/editor/visible_content_spec.lua b/tests/editor/visible_content_spec.lua index 9ad20188..d6445c01 100644 --- a/tests/editor/visible_content_spec.lua +++ b/tests/editor/visible_content_spec.lua @@ -1,14 +1,69 @@ require("view.editor.visibleContent") +require("model.input.cursor") require("util.string.string") -describe('VisibleContent #wrap', function() +local md_ex = [[### Input validation + +As an extension to the user input functionality, `validated_input()` allows arbitrary user-specified filters. +A "filter" is a function, which takes a string as input and returns a boolean value of whether it is valid and an optional `Error`. +The `Error` is structure which contains the error message (`msg`), and the location the error comes from, with line and character fields (`l` and `c`). + +#### Helper functions + +* `string.ulen(s)` - as opposed to the builtin `len()`, this works for unicode strings +* `string.usub(s, from, to)` - unicode substrings +* `Char.is_alpha(c)` - is `c` a letter +* `Char.is_alnum(c)` - is `c` a letter or a number (alphanumeric) +]] + +describe('VisibleContent #visible', function() + local md_text = string.lines(md_ex) + local w = 64 local turtle_doc = { '', 'Turtle graphics game inspired the LOGO family of languages.', '', } + it('translates', function() + local visible = VisibleContent(w, md_text, 1, 8) + --- scroll to the top + visible:move_range(- #md_text) + local cur11 = Cursor() + local cur33 = Cursor(3, 3) + local cur3w = Cursor(3, w) + local cur3wp1 = Cursor(3, w + 1) + local cur44 = Cursor(4, 4) + assert.same(cur11, visible:translate_to_wrapped(cur11)) + assert.same(cur33, visible:translate_to_wrapped(cur33)) + assert.same(cur3w, visible:translate_to_wrapped(cur3w)) + assert.same(Cursor(4, 1), visible:translate_to_wrapped(cur3wp1)) + + assert.same(cur33, visible:translate_from_visible(cur33)) + local cur3_67 = Cursor(3, 3 + w) + local exp3_67 = Cursor(4, 3) + assert.same(exp3_67, visible:translate_to_wrapped(cur3_67)) + + --- scroll to bottom + visible:to_end() + -- #01: '' + -- #02: '* `string.ulen(s)` - as opposed to the builtin `len()`, this wor' + -- #03: 'ks for unicode strings' + -- #04: '* `string.usub(s, from, to)` - unicode substrings' + -- #05: '* `Char.is_alpha(c)` - is `c` a letter' + -- #06: '* `Char.is_alnum(c)` - is `c` a letter or a number (alphanumeric' + -- #07: ')' + -- #08: '' + assert.same(Cursor(9, 3 + w), + visible:translate_from_visible(cur33)) + assert.same(Cursor(10, 4), + visible:translate_from_visible(cur44)) + assert.is_nil(visible:translate_from_visible(Cursor(5, 40))) + local cur71 = Cursor(7, 1) + assert.same(Cursor(12, 65), + visible:translate_from_visible(cur71)) + end) local os_max = 8 local input_max = 16 diff --git a/tests/interpreter/analyzer_inputs.lua b/tests/interpreter/analyzer_inputs.lua index 4987c615..bf55daab 100644 --- a/tests/interpreter/analyzer_inputs.lua +++ b/tests/interpreter/analyzer_inputs.lua @@ -1,7 +1,7 @@ --- @param s str ---- @param defs Assignment[] +--- @param semi SemanticInfo --- @return table {string[], string[]} -local prep = function(s, defs) +local prep = function(s, semi) local orig = (function() if type(s) == 'string' then return string.lines(s) @@ -14,7 +14,7 @@ local prep = function(s, defs) end end)() - return { orig, defs } + return { orig, semi } end local table1 = prep({ @@ -30,7 +30,7 @@ local table1 = prep({ ' z = 2,', ' 3,', '}', -}, { +}, SemanticInfo({ { name = 't', line = 1, type = 'local' }, { name = 't.ty', line = 2, type = 'field' }, { name = 't2', line = 4, type = 'global' }, @@ -38,13 +38,13 @@ local table1 = prep({ { name = 't2.w2', line = 6, type = 'field' }, { name = 'a', line = 8, type = 'global' }, { name = 'a.z', line = 10, type = 'field' }, -}) +}, {})) local table2 = prep({ 'tmp = {}', 'tmp[1] = 2', -}, { +}, SemanticInfo({ { name = 'tmp', line = 1, type = 'global' } -}) +}, {})) local simple = { --- sets @@ -53,21 +53,22 @@ local simple = { 'y = 3', 'x = 3', 'w, ww = 10, 11', - }, { + }, SemanticInfo({ { line = 1, name = 'x', type = 'global', }, { line = 2, name = 'y', type = 'global', }, { line = 3, name = 'x', type = 'global', }, { line = 4, name = 'w', type = 'global', }, { line = 4, name = 'ww', type = 'global', }, - }), + }, {})), + prep({ 'local l = 1', 'local x, y = 2, 3', - }, { + }, SemanticInfo({ { line = 1, name = 'l', type = 'local', }, { line = 2, name = 'x', type = 'local', }, { line = 2, name = 'y', type = 'local', }, - }), + }, {})), --- tables table1, -- table2, @@ -75,35 +76,35 @@ local simple = { prep({ 'function drawBackground()', 'end', - }, { + }, SemanticInfo({ { line = 1, name = 'drawBackground', type = 'function', }, - }), + }, {})), prep({ 'function love.draw()', ' draw()', 'end', - }, { + }, SemanticInfo({ { line = 1, name = 'love.draw', type = 'function', }, - }), + }, {})), prep({ 'function love.handlers.keypressed()', 'end', - }, { + }, SemanticInfo({ { line = 1, name = 'love.handlers.keypressed', type = 'function', }, - }), + }, {})), prep({ 'local function drawBody()', 'end', - }, { + }, SemanticInfo({ { name = 'drawBody', line = 1, type = 'function' } - }), + }, {})), --- methods prep({ 'function M:draw()', 'end', - }, { + }, SemanticInfo({ { name = 'M:draw', line = 1, type = 'method' } - }), + }, {})), } local sierpinski = [[function sierpinski(depth) @@ -210,7 +211,7 @@ end local fullclock = [[ --- @diagnostic disable: duplicate-set-field,lowercase-global -width, height = G.getDimensions() +width, height = gfx.getDimensions() midx = width / 2 midy = height / 2 @@ -232,7 +233,7 @@ s = 0 math.randomseed(os.time()) color = math.random(7) bg_color = math.random(7) -font = G.newFont(72) +font = gfx.newFont(72) local function pad(i) return string.format("%02d", i) @@ -249,15 +250,15 @@ function getTimestamp() end function love.draw() - G.setColor(Color[color + Color.bright]) - G.setBackgroundColor(Color[bg_color]) - G.setFont(font) + gfx.setColor(Color[color + Color.bright]) + gfx.setBackgroundColor(Color[bg_color]) + gfx.setFont(font) local text = getTimestamp() local l = string.len(text) local off_x = l * font:getWidth(' ') local off_y = font:getHeight() / 2 - G.print(text, midx - off_x, midy - off_y, 0, 1, 1) + gfx.print(text, midx - off_x, midy - off_y, 0, 1, 1) end function love.update(dt) @@ -295,14 +296,14 @@ end ]] local full = { - prep(sierpinski, { + prep(sierpinski, SemanticInfo({ { line = 1, name = 'sierpinski', type = 'function', }, { line = 2, name = 'lines', type = 'global', }, { line = 4, name = 'sp', type = 'global', }, { line = 5, name = 'tmp', type = 'global', }, { line = 10, name = 'lines', type = 'global', }, - }), - prep(clock, { + }, {})), + prep(clock, SemanticInfo({ { line = 1, name = 'love.draw', type = 'function', }, { line = 5, name = 'love.update', type = 'function', }, { line = 6, name = 't', type = 'global', }, @@ -312,8 +313,8 @@ local full = { { line = 16, name = 'love.keyreleased', type = 'function', }, { line = 19, name = 'bg_color', type = 'global', }, { line = 21, name = 'color', type = 'global', }, - }), - prep(meta, { + }, {})), + prep(meta, SemanticInfo({ { line = 3, name = 'M:extract_comments', type = 'method', }, { line = 4, name = 'lfi', type = 'local', }, { line = 5, name = 'lla', type = 'local', }, @@ -346,8 +347,8 @@ local full = { { line = 32, name = 'li.multiline', type = 'field', }, { line = 33, name = 'li.position', type = 'field', }, { line = 34, name = 'li.prepend_newline', type = 'field', }, - }), - prep(fullclock, { + }, {})), + prep(fullclock, SemanticInfo({ { line = 2, name = 'width', type = 'global', }, { line = 2, name = 'height', type = 'global', }, { line = 3, name = 'midx', type = 'global', }, @@ -386,10 +387,31 @@ local full = { { line = 69, name = 'bg_color', type = 'global', }, { line = 71, name = 'color', type = 'global', }, { line = 75, name = 'love.keyreleased', type = 'function', }, - }), + }, {})), +} + +local req = { + prep({ + 'require ("math")', + 'x = sin(pi)', + }, SemanticInfo({ + { name = 'x', line = 2, type = 'global' } + }, { + { name = 'math', line = 1 } + })), + prep({ + 'require("action")', + 'print "req test"', + 'require("drawing")', + }, SemanticInfo({ + }, { + { name = 'action', line = 1 }, + { name = 'drawing', line = 3 }, + })), } return { - { 'simple', simple }, - { 'full', full }, + { 'simple', simple }, + { 'full', full }, + { 'require', req }, } diff --git a/tests/interpreter/analyzer_spec.lua b/tests/interpreter/analyzer_spec.lua index 11d16417..37d62aec 100644 --- a/tests/interpreter/analyzer_spec.lua +++ b/tests/interpreter/analyzer_spec.lua @@ -96,7 +96,7 @@ describe('analyzer #analyzer', function() ---@diagnostic disable-next-line: param-type-mismatch local semDB = analyzer.analyze(r) it('matches ' .. i, function() - assert.same(output, semDB.assignments) + assert.same(output, semDB) end) else Log.warn('syntax error in input #' .. i) diff --git a/tests/testutil.lua b/tests/testutil.lua index 7c1e4c1b..8b324f3e 100644 --- a/tests/testutil.lua +++ b/tests/testutil.lua @@ -1,6 +1,9 @@ require('util.table') require('util.string.string') +local w = 64 + +local noop = function() end --- @param init str --- @return fun(str): boolean, string? --- @return reftable handle @@ -18,5 +21,16 @@ local get_save_function = function(init) end return { - get_save_function = get_save_function + get_save_function = get_save_function, + noop = noop, + LINES = 16, + SCROLL_BY = 8, + w = w, + mock_view_cfg = { + view = { + drawableChars = w, + lines = 16, + input_max = 14 + }, + } } diff --git a/tests/util/visible_spec.lua b/tests/util/visible_spec.lua deleted file mode 100644 index 97fa932f..00000000 --- a/tests/util/visible_spec.lua +++ /dev/null @@ -1,63 +0,0 @@ -require("view.editor.visibleContent") -require("model.input.cursor") -require("util.string.string") -require("util.debug") - -local md_ex = [[### Input validation - -As an extension to the user input functionality, `validated_input()` allows arbitrary user-specified filters. -A "filter" is a function, which takes a string as input and returns a boolean value of whether it is valid and an optional `Error`. -The `Error` is structure which contains the error message (`msg`), and the location the error comes from, with line and character fields (`l` and `c`). - -#### Helper functions - -* `string.ulen(s)` - as opposed to the builtin `len()`, this works for unicode strings -* `string.usub(s, from, to)` - unicode substrings -* `Char.is_alpha(c)` - is `c` a letter -* `Char.is_alnum(c)` - is `c` a letter or a number (alphanumeric) -]] -local text = string.lines(md_ex) -local w = 64 - -describe('VisibleContent #visible', function() - local visible = VisibleContent(w, text, 1, 8) - - -- Log.debug(Debug.terse_ast(visible, true, 'lua')) - it('translates', function() - --- scroll to the top - visible:move_range(- #text) - local cur11 = Cursor() - local cur33 = Cursor(3, 3) - local cur3w = Cursor(3, w) - local cur3wp1 = Cursor(3, w + 1) - local cur44 = Cursor(4, 4) - assert.same(cur11, visible:translate_to_wrapped(cur11)) - assert.same(cur33, visible:translate_to_wrapped(cur33)) - assert.same(cur3w, visible:translate_to_wrapped(cur3w)) - assert.same(Cursor(4, 1), visible:translate_to_wrapped(cur3wp1)) - - assert.same(cur33, visible:translate_from_visible(cur33)) - local cur3_67 = Cursor(3, 3 + w) - local exp3_67 = Cursor(4, 3) - assert.same(exp3_67, visible:translate_to_wrapped(cur3_67)) - - --- scroll to bottom - visible:to_end() - -- #01: '' - -- #02: '* `string.ulen(s)` - as opposed to the builtin `len()`, this wor' - -- #03: 'ks for unicode strings' - -- #04: '* `string.usub(s, from, to)` - unicode substrings' - -- #05: '* `Char.is_alpha(c)` - is `c` a letter' - -- #06: '* `Char.is_alnum(c)` - is `c` a letter or a number (alphanumeric' - -- #07: ')' - -- #08: '' - assert.same(Cursor(9, 3 + w), - visible:translate_from_visible(cur33)) - assert.same(Cursor(10, 4), - visible:translate_from_visible(cur44)) - assert.is_nil(visible:translate_from_visible(Cursor(5, 40))) - local cur71 = Cursor(7, 1) - assert.same(Cursor(12, 65), - visible:translate_from_visible(cur71)) - end) -end)