Better line editing

This commit is contained in:
20kdc 2020-03-30 12:08:12 +01:00
parent d2ee505316
commit 479412a5bb
6 changed files with 164 additions and 160 deletions

View File

@ -3,7 +3,7 @@
return { return {
["neo"] = { ["neo"] = {
desc = "KittenOS NEO Kernel & Base Libs", desc = "KittenOS NEO Kernel & Base Libs",
v = 8, v = 9,
deps = { deps = {
}, },
dirs = { dirs = {
@ -18,6 +18,7 @@ return {
"libs/serial.lua", "libs/serial.lua",
"libs/fmttext.lua", "libs/fmttext.lua",
"libs/neoux.lua", "libs/neoux.lua",
"libs/lineedit.lua",
"libs/braille.lua", "libs/braille.lua",
"libs/bmp.lua", "libs/bmp.lua",
"libs/sys-filewrap.lua", "libs/sys-filewrap.lua",
@ -96,7 +97,7 @@ return {
}, },
["neo-coreapps"] = { ["neo-coreapps"] = {
desc = "KittenOS NEO Core Apps", desc = "KittenOS NEO Core Apps",
v = 5, v = 9,
deps = { deps = {
"neo" "neo"
}, },

View File

@ -226,7 +226,7 @@ window = neoux.create(currentGen())
while running do while running do
local src, id, k, v = event.pull() local src, id, k, v = event.pull()
if src == "x.neo.sys.manage" then if src == "x.neo.sys.manage" then
if id == "set_setting" then if id == "set_setting" and currentGen ~= logGen then
window.reset(currentGen()) window.reset(currentGen())
end end
end end

View File

@ -30,32 +30,17 @@ local clipsrc = neo.requireAccess("x.neo.pub.globals", "clipboard")
local windows = neo.requireAccess("x.neo.pub.window", "windows") local windows = neo.requireAccess("x.neo.pub.window", "windows")
local files = neo.requireAccess("x.neo.pub.base", "files").showFileDialogAsync local files = neo.requireAccess("x.neo.pub.base", "files").showFileDialogAsync
local lineEdit = require("lineedit")
local cursorX = 1 local cursorX = 1
local cursorY = math.ceil(#lines / 2) local cursorY = math.ceil(#lines / 2)
local cFlash = true local ctrlFlag, appendFlag
local ctrlFlag, focFlag, appendFlag
local dialogLock = false local dialogLock = false
local sW, sH = 37, #lines + 2 local sW, sH = 37, #lines + 2
local window = windows(sW, sH) local window = windows(sW, sH)
local filedialog = nil local filedialog = nil
local flush 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 cbs = {}
local function fileDialog(writing, callback) local function fileDialog(writing, callback)
@ -139,31 +124,14 @@ local function getline(y)
-- rX is difficult! -- rX is difficult!
local rX = 1 local rX = 1
local Xthold = math.max(1, math.floor(sW / 2) - 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 rX = (math.max(0, math.floor(cursorXP / Xthold) - 1) * Xthold) + 1
local line = lines[rY] local line = lines[rY]
if not line then if not line then
return ("¬"):rep(sW) return ("¬"):rep(sW)
end end
line = unicode.safeTextFormat(line) line = unicode.safeTextFormat(line)
-- <alter RX here by 1 if needed> return lineEdit.draw(sW, line, cursorXP, rY == cursorY, rX)
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
end end
local function delLine() local function delLine()
local contents = lines[cursorY] local contents = lines[cursorY]
@ -179,22 +147,7 @@ local function delLine()
end end
return contents return contents
end end
-- add a single character local function key(ks, kc, down)
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)
if dialogLock then if dialogLock then
return false return false
end end
@ -212,18 +165,22 @@ local function key(ka, kc, down)
sH = 1 sH = 1
end end
sW, sH = window.setSize(sW, sH) sW, sH = window.setSize(sW, sH)
return false
elseif kc == 208 then -- Down elseif kc == 208 then -- Down
sH = sH + 1 sH = sH + 1
sW, sH = window.setSize(sW, sH) sW, sH = window.setSize(sW, sH)
return false
elseif kc == 203 then -- Left elseif kc == 203 then -- Left
sW = sW - 1 sW = sW - 1
if sW == 0 then if sW == 0 then
sW = 1 sW = 1
end end
sW, sH = window.setSize(sW, sH) sW, sH = window.setSize(sW, sH)
return false
elseif kc == 205 then -- Right elseif kc == 205 then -- Right
sW = sW + 1 sW = sW + 1
sW, sH = window.setSize(sW, sH) sW, sH = window.setSize(sW, sH)
return false
elseif kc == 14 then -- ^Backspace elseif kc == 14 then -- ^Backspace
delLine() delLine()
return true return true
@ -240,7 +197,7 @@ local function key(ka, kc, down)
if cursorY < 1 then if cursorY < 1 then
cursorY = 1 cursorY = 1
end end
clampCursorX() cursorX = lineEdit.clamp(lines[cursorY], cursorX)
return true return true
elseif kc == 208 or kc == 209 then -- Go down one - go down page elseif kc == 208 or kc == 209 then -- Go down one - go down page
local moveAmount = 1 local moveAmount = 1
@ -251,36 +208,7 @@ local function key(ka, kc, down)
if cursorY > #lines then if cursorY > #lines then
cursorY = #lines cursorY = #lines
end end
clampCursorX() cursorX = lineEdit.clamp(lines[cursorY], cursorX)
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
return true return true
end end
-- Major Actions -- Major Actions
@ -291,10 +219,13 @@ local function key(ka, kc, down)
return true return true
elseif kc == 61 then -- F3 elseif kc == 61 then -- F3
startLoad() startLoad()
return false
elseif kc == 62 then -- F4 elseif kc == 62 then -- F4
startSave() startSave()
return false
elseif kc == 63 then -- F5 elseif kc == 63 then -- F5
clipsrc.setSetting("clipboard", lines[cursorY]) clipsrc.setSetting("clipboard", lines[cursorY])
return false
elseif kc == 64 then -- F6 elseif kc == 64 then -- F6
local tx = clipsrc.getSetting("clipboard") or "" local tx = clipsrc.getSetting("clipboard") or ""
local txi = tx:find("\n") local txi = tx:find("\n")
@ -311,6 +242,7 @@ local function key(ka, kc, down)
return true return true
elseif kc == 65 then -- F7 elseif kc == 65 then -- F7
appendFlag = false appendFlag = false
return false
elseif kc == 66 then -- F8 elseif kc == 66 then -- F8
if appendFlag then if appendFlag then
local base = clipsrc.getSetting("clipboard") local base = clipsrc.getSetting("clipboard")
@ -322,29 +254,33 @@ local function key(ka, kc, down)
return true return true
end end
end end
-- Letters -- LEL Keys
if ka == 8 or kc == 211 then local lT, lC, lX = lineEdit.key(ks, kc, lines[cursorY], cursorX)
if cursorX == 1 then if lT then
if cursorY == 1 then lines[cursorY] = lT
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
end end
if ka ~= 0 then if lC then
putLetter(unicode.char(ka)) cursorX = lC
return true
end 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 end
flush = function () flush = function ()
@ -353,16 +289,9 @@ flush = function ()
end end
end end
neo.scheduleTimer(os.uptime() + 0.5)
while true do while true do
local e = {coroutine.yield()} local e = {coroutine.yield()}
if e[1] == "k.timer" and e[2] == focFlag then if e[1] == "x.neo.pub.window" 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[2] == window.id then if e[2] == window.id then
if e[3] == "line" then if e[3] == "line" then
window.span(1, e[4], getline(e[4]), 0xFFFFFF, 0) window.span(1, e[4], getline(e[4]), 0xFFFFFF, 0)
@ -373,14 +302,13 @@ while true do
local csY = math.ceil(sH / 2) local csY = math.ceil(sH / 2)
local nY = math.max(1, math.min(#lines, (math.floor(e[5]) - csY) + cursorY)) local nY = math.max(1, math.min(#lines, (math.floor(e[5]) - csY) + cursorY))
cursorY = nY cursorY = nY
clampCursorX() cursorX = lineEdit.clamp(lines[cursorY], cursorX)
flush() flush()
elseif e[3] == "key" then 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() flush()
end end
elseif e[3] == "focus" then elseif e[3] == "focus" then
focFlag = e[4] and neo.scheduleTimer(0)
ctrlFlag = false ctrlFlag = false
elseif e[3] == "close" then elseif e[3] == "close" then
return return
@ -392,7 +320,7 @@ while true do
if c == "\n" then if c == "\n" then
c = "\r" c = "\r"
end end
putLetter(c) key(c, 0, true)
end end
end end
flush() flush()

View File

@ -32,6 +32,7 @@ local l15 = ""
-- sW must not go below 3. -- sW must not go below 3.
-- sH must not go below 2. -- sH must not go below 2.
local sW, sH = 40, 15 local sW, sH = 40, 15
local cX = 1
local windows = neo.requireAccess("x.neo.pub.window", "windows") local windows = neo.requireAccess("x.neo.pub.window", "windows")
local window = windows(sW, sH, title) local window = windows(sW, sH, title)
@ -42,11 +43,12 @@ local function fmtLine(s)
end end
local function line(i) local function line(i)
local l = console[i] or l15
l = require("lineedit").draw(sW, l, cX, i == sH)
if i ~= sH then if i ~= sH then
assert(console[i], "console" .. i) window.span(1, i, l, 0xFFFFFF, 0)
window.span(1, i, fmtLine(console[i]), 0xFFFFFF, 0)
else else
window.span(1, i, fmtLine(l15), 0, 0xFFFFFF) window.span(1, i, l, 0, 0xFFFFFF)
end end
end end
@ -73,22 +75,6 @@ end
local sendSigs = {} 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 do
tReg(function (_, pid, sendSig) tReg(function (_, pid, sendSig)
sendSigs[pid] = sendSig sendSigs[pid] = sendSig
@ -112,6 +98,19 @@ do
end end
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 local control = false
while not closeNow do while not closeNow do
local e = {coroutine.yield()} local e = {coroutine.yield()}
@ -121,19 +120,22 @@ while not closeNow do
if e[3] == "close" then if e[3] == "close" then
break break
elseif e[3] == "clipboard" then 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 elseif e[3] == "key" then
if e[5] == 29 or e[5] == 157 then if e[5] == 29 or e[5] == 157 then
control = e[6] control = e[6]
elseif e[6] then elseif e[6] then
if not control then if not control then
if e[4] == 8 or e[4] == 127 then key(e[4] ~= 0 and unicode.char(e[4]), e[5])
l15 = unicode.sub(l15, 1, -2)
elseif e[4] == 13 then
submitLine()
elseif e[4] >= 32 then
l15 = l15 .. unicode.char(e[4])
end
line(sH) line(sH)
elseif e[5] == 203 and sW > 8 then elseif e[5] == 203 and sW > 8 then
sW = sW - 1 sW = sW - 1

69
code/libs/lineedit.lua Normal file
View File

@ -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
}

View File

@ -339,6 +339,7 @@ newNeoux = function (event, neo)
end end
-- Note: w should be at least 2 - this is similar to buttons. -- Note: w should be at least 2 - this is similar to buttons.
neoux.tcfield = function (x, y, w, textprop) neoux.tcfield = function (x, y, w, textprop)
local p = 1
return { return {
x = x, x = x,
y = y, y = y,
@ -347,22 +348,24 @@ newNeoux = function (event, neo)
selectable = true, selectable = true,
key = function (window, update, a, c, d, f) key = function (window, update, a, c, d, f)
if d then if d then
local ot = textprop()
local le = require("lineedit")
p = le.clamp(ot, p)
if c == 63 then 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 elseif c == 64 then
local contents = neo.requireAccess("x.neo.pub.globals", "clipboard").getSetting("clipboard") local contents = neo.requireAccess("x.neo.pub.globals", "clipboard").getSetting("clipboard")
contents = contents:match("^[^\r\n]*") contents = contents:match("^[^\r\n]*")
textprop(contents) textprop(contents)
update() update()
elseif a == 8 then elseif a ~= 9 then
local str = textprop() local lT, lC, lX = le.key(a ~= 0 and unicode.char(a), c, ot, p)
textprop(unicode.sub(str, 1, unicode.len(str) - 1)) if lT or lC then
update() if lT then textprop(lT) end
return true p = lC or p
elseif a >= 32 then update()
textprop(textprop() .. unicode.char(a)) return true
update() end
return true
end end
end end
end, end,
@ -377,9 +380,10 @@ newNeoux = function (event, neo)
fg = bg fg = bg
bg = fg1 bg = fg1
end end
local text = unicode.safeTextFormat(textprop()) local t, e, r = textprop(), require("lineedit")
text = "[" .. neoux.pad(text, w - 2, false, true, true) .. "]" p = e.clamp(t, p)
window.span(x, y, text, bg, fg) t, r = unicode.safeTextFormat(t, p)
window.span(x, y, "[" .. e.draw(w - 2, t, r, selected) .. "]", bg, fg)
end end
} }
end end