From 479412a5bb53fe382ad5ec44c1e54aa67ddf7d03 Mon Sep 17 00:00:00 2001 From: 20kdc Date: Mon, 30 Mar 2020 12:08:12 +0100 Subject: [PATCH] Better line editing --- claw/code-claw.lua | 5 +- code/apps/app-control.lua | 2 +- code/apps/app-textedit.lua | 162 +++++++++++-------------------------- code/apps/svc-t.lua | 56 ++++++------- code/libs/lineedit.lua | 69 ++++++++++++++++ code/libs/neoux.lua | 30 ++++--- 6 files changed, 164 insertions(+), 160 deletions(-) create mode 100644 code/libs/lineedit.lua diff --git a/claw/code-claw.lua b/claw/code-claw.lua index 13f4779..b6ac249 100644 --- a/claw/code-claw.lua +++ b/claw/code-claw.lua @@ -3,7 +3,7 @@ return { ["neo"] = { desc = "KittenOS NEO Kernel & Base Libs", - v = 8, + v = 9, deps = { }, dirs = { @@ -18,6 +18,7 @@ return { "libs/serial.lua", "libs/fmttext.lua", "libs/neoux.lua", + "libs/lineedit.lua", "libs/braille.lua", "libs/bmp.lua", "libs/sys-filewrap.lua", @@ -96,7 +97,7 @@ return { }, ["neo-coreapps"] = { desc = "KittenOS NEO Core Apps", - v = 5, + v = 9, deps = { "neo" }, diff --git a/code/apps/app-control.lua b/code/apps/app-control.lua index 1fb90f2..04d929e 100644 --- a/code/apps/app-control.lua +++ b/code/apps/app-control.lua @@ -226,7 +226,7 @@ window = neoux.create(currentGen()) while running do local src, id, k, v = event.pull() if src == "x.neo.sys.manage" then - if id == "set_setting" then + if id == "set_setting" and currentGen ~= logGen then window.reset(currentGen()) end end diff --git a/code/apps/app-textedit.lua b/code/apps/app-textedit.lua index 5afec38..b467eb6 100644 --- a/code/apps/app-textedit.lua +++ b/code/apps/app-textedit.lua @@ -30,32 +30,17 @@ local clipsrc = neo.requireAccess("x.neo.pub.globals", "clipboard") local windows = neo.requireAccess("x.neo.pub.window", "windows") local files = neo.requireAccess("x.neo.pub.base", "files").showFileDialogAsync +local lineEdit = require("lineedit") + local cursorX = 1 local cursorY = math.ceil(#lines / 2) -local cFlash = true -local ctrlFlag, focFlag, appendFlag +local ctrlFlag, appendFlag local dialogLock = false local sW, sH = 37, #lines + 2 local window = windows(sW, sH) local filedialog = nil local flush -local function splitCur() - local s = lines[cursorY] - local st = unicode.sub(s, 1, cursorX - 1) - local en = unicode.sub(s, cursorX) - return st, en -end - -local function clampCursorX() - local s = lines[cursorY] - if unicode.len(s) < (cursorX - 1) then - cursorX = unicode.len(s) + 1 - return true - end - return false -end - local cbs = {} local function fileDialog(writing, callback) @@ -139,31 +124,14 @@ local function getline(y) -- rX is difficult! local rX = 1 local Xthold = math.max(1, math.floor(sW / 2) - 1) - local _, cursorXP = unicode.safeTextFormat(lines[cursorY], cursorX) + local cLine, cursorXP = unicode.safeTextFormat(lines[cursorY], cursorX) rX = (math.max(0, math.floor(cursorXP / Xthold) - 1) * Xthold) + 1 local line = lines[rY] if not line then return ("¬"):rep(sW) end line = unicode.safeTextFormat(line) - -- - local tl = unicode.sub(line, rX, rX + sW - 1) - cursorXP = (cursorXP - rX) + 1 - if cFlash then - if rY == cursorY then - if cursorXP >= 1 then - if cursorXP <= sW then - local start = unicode.sub(tl, 1, cursorXP - 1) - local endx = unicode.sub(tl, cursorXP + 1) - tl = start .. "_" .. endx - end - end - end - end - while unicode.len(tl) < sW do - tl = tl .. " " - end - return tl + return lineEdit.draw(sW, line, cursorXP, rY == cursorY, rX) end local function delLine() local contents = lines[cursorY] @@ -179,22 +147,7 @@ local function delLine() end return contents end --- add a single character -local function putLetter(ch) - if ch == "\r" then - local a, b = splitCur() - lines[cursorY] = a - table.insert(lines, cursorY + 1, b) - cursorY = cursorY + 1 - cursorX = 1 - return - end - local a, b = splitCur() - a = a .. ch - lines[cursorY] = a .. b - cursorX = unicode.len(a) + 1 -end -local function key(ka, kc, down) +local function key(ks, kc, down) if dialogLock then return false end @@ -212,18 +165,22 @@ local function key(ka, kc, down) sH = 1 end sW, sH = window.setSize(sW, sH) + return false elseif kc == 208 then -- Down sH = sH + 1 sW, sH = window.setSize(sW, sH) + return false elseif kc == 203 then -- Left sW = sW - 1 if sW == 0 then sW = 1 end sW, sH = window.setSize(sW, sH) + return false elseif kc == 205 then -- Right sW = sW + 1 sW, sH = window.setSize(sW, sH) + return false elseif kc == 14 then -- ^Backspace delLine() return true @@ -240,7 +197,7 @@ local function key(ka, kc, down) if cursorY < 1 then cursorY = 1 end - clampCursorX() + cursorX = lineEdit.clamp(lines[cursorY], cursorX) return true elseif kc == 208 or kc == 209 then -- Go down one - go down page local moveAmount = 1 @@ -251,36 +208,7 @@ local function key(ka, kc, down) if cursorY > #lines then cursorY = #lines end - clampCursorX() - return true - elseif kc == 203 then - if cursorX > 1 then - cursorX = cursorX - 1 - else - if cursorY > 1 then - cursorY = cursorY - 1 - cursorX = unicode.len(lines[cursorY]) + 1 - else - return false - end - end - return true - elseif kc == 205 then - cursorX = cursorX + 1 - if clampCursorX() then - if cursorY < #lines then - cursorY = cursorY + 1 - cursorX = 1 - end - end - return true - end - -- Extra Non-Control Keys - if kc == 199 then - cursorX = 1 - return true - elseif kc == 207 then - cursorX = unicode.len(lines[cursorY]) + 1 + cursorX = lineEdit.clamp(lines[cursorY], cursorX) return true end -- Major Actions @@ -291,10 +219,13 @@ local function key(ka, kc, down) return true elseif kc == 61 then -- F3 startLoad() + return false elseif kc == 62 then -- F4 startSave() + return false elseif kc == 63 then -- F5 clipsrc.setSetting("clipboard", lines[cursorY]) + return false elseif kc == 64 then -- F6 local tx = clipsrc.getSetting("clipboard") or "" local txi = tx:find("\n") @@ -311,6 +242,7 @@ local function key(ka, kc, down) return true elseif kc == 65 then -- F7 appendFlag = false + return false elseif kc == 66 then -- F8 if appendFlag then local base = clipsrc.getSetting("clipboard") @@ -322,29 +254,33 @@ local function key(ka, kc, down) return true end end - -- Letters - if ka == 8 or kc == 211 then - if cursorX == 1 then - if cursorY == 1 then - return false - end - local l = table.remove(lines, cursorY) - cursorY = cursorY - 1 - cursorX = unicode.len(lines[cursorY]) + 1 - lines[cursorY] = lines[cursorY] .. l - else - local a, b = splitCur() - a = unicode.sub(a, 1, unicode.len(a) - 1) - lines[cursorY] = a.. b - cursorX = cursorX - 1 - end - return true + -- LEL Keys + local lT, lC, lX = lineEdit.key(ks, kc, lines[cursorY], cursorX) + if lT then + lines[cursorY] = lT end - if ka ~= 0 then - putLetter(unicode.char(ka)) - return true + if lC then + cursorX = lC end - return false + if lX == "l<" and cursorY > 1 then + cursorY = cursorY - 1 + cursorX = unicode.len(lines[cursorY]) + 1 + elseif lX == "l>" and cursorY < #lines then + cursorY = cursorY + 1 + cursorX = 1 + elseif lX == "wpl" and cursorY ~= 1 then + local l = table.remove(lines, cursorY) + cursorY = cursorY - 1 + cursorX = unicode.len(lines[cursorY]) + 1 + lines[cursorY] = lines[cursorY] .. l + elseif lX == "nl" then + local line = lines[cursorY] + lines[cursorY] = unicode.sub(line, 1, cursorX - 1) + table.insert(lines, cursorY + 1, unicode.sub(line, cursorX)) + cursorX = 1 + cursorY = cursorY + 1 + end + return true end flush = function () @@ -353,16 +289,9 @@ flush = function () end end -neo.scheduleTimer(os.uptime() + 0.5) - while true do local e = {coroutine.yield()} - if e[1] == "k.timer" and e[2] == focFlag then - cFlash = not cFlash - local csY = math.ceil(sH / 2) - window.span(1, csY, getline(csY), 0xFFFFFF, 0) - focFlag = neo.scheduleTimer(os.uptime() + 0.5) - elseif e[1] == "x.neo.pub.window" then + if e[1] == "x.neo.pub.window" then if e[2] == window.id then if e[3] == "line" then window.span(1, e[4], getline(e[4]), 0xFFFFFF, 0) @@ -373,14 +302,13 @@ while true do local csY = math.ceil(sH / 2) local nY = math.max(1, math.min(#lines, (math.floor(e[5]) - csY) + cursorY)) cursorY = nY - clampCursorX() + cursorX = lineEdit.clamp(lines[cursorY], cursorX) flush() elseif e[3] == "key" then - if key(e[4], e[5], e[6]) then + if key(e[4] ~= 0 and unicode.char(e[4]), e[5], e[6]) then flush() end elseif e[3] == "focus" then - focFlag = e[4] and neo.scheduleTimer(0) ctrlFlag = false elseif e[3] == "close" then return @@ -392,7 +320,7 @@ while true do if c == "\n" then c = "\r" end - putLetter(c) + key(c, 0, true) end end flush() diff --git a/code/apps/svc-t.lua b/code/apps/svc-t.lua index 543e88e..bdb7aad 100644 --- a/code/apps/svc-t.lua +++ b/code/apps/svc-t.lua @@ -32,6 +32,7 @@ local l15 = "" -- sW must not go below 3. -- sH must not go below 2. local sW, sH = 40, 15 +local cX = 1 local windows = neo.requireAccess("x.neo.pub.window", "windows") local window = windows(sW, sH, title) @@ -42,11 +43,12 @@ local function fmtLine(s) end local function line(i) + local l = console[i] or l15 + l = require("lineedit").draw(sW, l, cX, i == sH) if i ~= sH then - assert(console[i], "console" .. i) - window.span(1, i, fmtLine(console[i]), 0xFFFFFF, 0) + window.span(1, i, l, 0xFFFFFF, 0) else - window.span(1, i, fmtLine(l15), 0, 0xFFFFFF) + window.span(1, i, l, 0, 0xFFFFFF) end end @@ -73,22 +75,6 @@ end local sendSigs = {} -local function submitLine() - for _, v in pairs(sendSigs) do - v("line", l15) - end - l15 = "" - line(sH) -end - -local function clipEnt(tx) - tx = tx:gsub("\r", "") - local ci = tx:find("\n") or (#tx + 1) - tx = tx:sub(1, ci - 1) - l15 = l15 .. tx - line(sH) -end - do tReg(function (_, pid, sendSig) sendSigs[pid] = sendSig @@ -112,6 +98,19 @@ do end end +local function key(a, c) + local lT, lC, lX = require("lineedit").key(a, c, l15, cX) + l15 = lT or l15 + cX = lC or cX + if lX == "nl" then + for _, v in pairs(sendSigs) do + v("line", l15) + end + l15 = "" + cX = 1 + end +end + local control = false while not closeNow do local e = {coroutine.yield()} @@ -121,19 +120,22 @@ while not closeNow do if e[3] == "close" then break elseif e[3] == "clipboard" then - clipEnt(e[4]) + for i = 1, unicode.len(e[4]) do + local c = unicode.sub(e[4], i, i) + if c ~= "\r" then + if c == "\n" then + c = "\r" + end + key(c, 0) + end + end + line(sH) elseif e[3] == "key" then if e[5] == 29 or e[5] == 157 then control = e[6] elseif e[6] then if not control then - if e[4] == 8 or e[4] == 127 then - l15 = unicode.sub(l15, 1, -2) - elseif e[4] == 13 then - submitLine() - elseif e[4] >= 32 then - l15 = l15 .. unicode.char(e[4]) - end + key(e[4] ~= 0 and unicode.char(e[4]), e[5]) line(sH) elseif e[5] == 203 and sW > 8 then sW = sW - 1 diff --git a/code/libs/lineedit.lua b/code/libs/lineedit.lua new file mode 100644 index 0000000..2663028 --- /dev/null +++ b/code/libs/lineedit.lua @@ -0,0 +1,69 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +return { + -- note: everything must already be unicode.safeTextFormat'd + draw = function (sW, line, cursorX, cursorOn, rX) + -- if no camera, provide a default + rX = rX or math.max(1, cursorX - math.floor(sW * 2 / 3)) + -- transform into area-relative + local tl = unicode.sub(line, rX, rX + sW - 1) + -- inject cursor + if cursorOn then + cursorX = (cursorX - rX) + 1 + if cursorX >= 1 then + if cursorX <= sW then + tl = unicode.sub(tl, 1, cursorX - 1) .. "_" .. unicode.sub(tl, cursorX + 1) + end + end + end + return tl .. (" "):rep(sW - unicode.len(tl)) + end, + clamp = function (tl, cursorX) + tl = unicode.len(tl) + if tl < cursorX - 1 then + return tl + 1 + end + return cursorX + end, + -- returns line, cursorX, special + -- return values may be nil if irrelevant + key = function (ks, kc, line, cursorX) + local cS = unicode.sub(line, 1, cursorX - 1) + local cE = unicode.sub(line, cursorX) + if kc == 203 then -- navi < + if cursorX > 1 then + return nil, cursorX - 1 + else + -- cline underflow + return nil, nil, "l<" + end + elseif kc == 205 then -- navi > + local ll = unicode.len(line) + if cursorX > ll then + -- cline overflow + return nil, nil, "l>" + end + return nil, cursorX + 1 + elseif kc == 199 then -- home + return nil, 1 + elseif kc == 207 then -- end + return nil, unicode.len(line) + 1 + elseif ks == "\8" or kc == 211 then -- del + if cursorX == 1 then + -- weld prev line + return nil, nil, "wpl" + else + cS = unicode.sub(cS, 1, unicode.len(cS) - 1) + return cS .. cE, cursorX - 1 + end + elseif ks then -- standard letters + if ks == "\r" then + -- new line + return nil, nil, "nl" + end + return cS .. ks .. cE, cursorX + unicode.len(ks) + end + -- :( + end +} diff --git a/code/libs/neoux.lua b/code/libs/neoux.lua index 0731de6..f39d900 100644 --- a/code/libs/neoux.lua +++ b/code/libs/neoux.lua @@ -339,6 +339,7 @@ newNeoux = function (event, neo) end -- Note: w should be at least 2 - this is similar to buttons. neoux.tcfield = function (x, y, w, textprop) + local p = 1 return { x = x, y = y, @@ -347,22 +348,24 @@ newNeoux = function (event, neo) selectable = true, key = function (window, update, a, c, d, f) if d then + local ot = textprop() + local le = require("lineedit") + p = le.clamp(ot, p) if c == 63 then - neo.requireAccess("x.neo.pub.globals", "clipboard").setSetting("clipboard", textprop()) + neo.requireAccess("x.neo.pub.globals", "clipboard").setSetting("clipboard", ot) elseif c == 64 then local contents = neo.requireAccess("x.neo.pub.globals", "clipboard").getSetting("clipboard") contents = contents:match("^[^\r\n]*") textprop(contents) update() - elseif a == 8 then - local str = textprop() - textprop(unicode.sub(str, 1, unicode.len(str) - 1)) - update() - return true - elseif a >= 32 then - textprop(textprop() .. unicode.char(a)) - update() - return true + elseif a ~= 9 then + local lT, lC, lX = le.key(a ~= 0 and unicode.char(a), c, ot, p) + if lT or lC then + if lT then textprop(lT) end + p = lC or p + update() + return true + end end end end, @@ -377,9 +380,10 @@ newNeoux = function (event, neo) fg = bg bg = fg1 end - local text = unicode.safeTextFormat(textprop()) - text = "[" .. neoux.pad(text, w - 2, false, true, true) .. "]" - window.span(x, y, text, bg, fg) + local t, e, r = textprop(), require("lineedit") + p = e.clamp(t, p) + t, r = unicode.safeTextFormat(t, p) + window.span(x, y, "[" .. e.draw(w - 2, t, r, selected) .. "]", bg, fg) end } end