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 {
["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"
},

View File

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

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 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)
-- <alter RX here by 1 if needed>
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()

View File

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

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