OC-KittenOS/code/apps/app-textedit.lua

413 lines
9.5 KiB
Lua
Raw Normal View History

-- This is released into the public domain.
-- No warranty is provided, implied or otherwise.
-- 'neolithic': Text Editor
-- This was textedit (femto) from KittenOS 'ported' to NEO.
-- It also has fixes for bugs involving wide text, and runs faster due to the chars -> lines change.
local lines = {
"Neolithic: Text Editor",
"F3, F4, F1: Load, Save, New",
"F5, F6, ^←: Copy, Paste, Delete Line",
-- These two are meant to replace similar functionality in GNU Nano
-- (which I consider the best console text editor out there - Neolithic is an *imitation* and a poor one at that),
-- except fixing a UI flaw by instead adding a visible way to reset the append flag,
-- so the user can more or less arbitrarily mash together lines
"F7: Reset 'append' flag for Cut Lines",
"F8: Cut Line(s)",
"^<arrows>: Resize Win",
"'^' is Control.",
"Wide text & clipboard supported.",
" ",
}
-- If replicating Nano's clipboard :
-- Nano starts off in a "replace" mode,
-- and then after an action occurs switches to "append" until *any cursor action is performed*.
-- The way I have things setup is that you perform J then K(repeat) *instead*, which means you have to explicitly say "destroy current clipboard".
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 cursorX = 1
local cursorY = math.ceil(#lines / 2)
local cFlash = true
local ctrlFlag, focFlag, appendFlag
local dialogLock = false
local sW, sH = 37, #lines + 2
2018-03-28 00:40:05 +11:00
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
2018-03-28 00:40:05 +11:00
local cbs = {}
local function fileDialog(writing, callback)
filedialog = function (res)
local ok, e = pcall(callback, res)
if not ok then
e = unicode.safeTextFormat(tostring(e))
local wnd = windows(unicode.len(e), 1, "ERROR")
cbs[wnd.id] = {
wnd.close,
wnd.span,
e
}
end
end
files(writing)
end
-- Save/Load
local function startSave()
dialogLock = true
fileDialog(true, function (res)
dialogLock = false
local x = ""
if res then
for k, v in ipairs(lines) do
if k ~= 1 then
x = x .. "\n"
end
x = x .. v
while #x >= neo.readBufSize do
res.write(x:sub(1, neo.readBufSize))
x = x:sub(neo.readBufSize + 1)
end
end
res.write(x)
res.close()
end
end)
end
local function startLoad()
dialogLock = true
fileDialog(false, function (res)
dialogLock = false
if res then
lines = {}
local lb = ""
while true do
local l = res.read(neo.readBufSize)
if not l then
table.insert(lines, lb)
cursorX = 1
cursorY = 1
res.close()
flush()
return
end
local lp = l:find("\n")
while lp do
lb = lb .. l:sub(1, lp - 1)
table.insert(lines, lb)
lb = ""
l = l:sub(lp + 1)
lp = l:find("\n")
end
lb = lb .. l
end
end
end)
end
local function getline(y)
-- do rY first since unw
-- only requires that
-- horizontal stuff be
-- messed with...
-- ...thankfully
local rY = (y + cursorY) - math.ceil(sH / 2)
-- rX is difficult!
local rX = 1
local Xthold = math.max(1, math.floor(sW / 2) - 1)
local _, 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
end
local function delLine()
local contents = lines[cursorY]
if cursorY == #lines then
if cursorY == 1 then
lines[1] = ""
else
cursorY = cursorY - 1
lines[#lines] = nil
end
else
table.remove(lines, cursorY)
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)
if dialogLock then
return false
end
if kc == 29 then
ctrlFlag = down
return false
end
-- Action keys
if not down then return false end
if ctrlFlag then
-- Control Action Keys
if kc == 200 then -- Up
sH = sH - 1
if sH == 0 then
sH = 1
end
sW, sH = window.setSize(sW, sH)
elseif kc == 208 then -- Down
sH = sH + 1
sW, sH = window.setSize(sW, sH)
elseif kc == 203 then -- Left
sW = sW - 1
if sW == 0 then
sW = 1
end
sW, sH = window.setSize(sW, sH)
elseif kc == 205 then -- Right
sW = sW + 1
sW, sH = window.setSize(sW, sH)
elseif kc == 14 then -- ^Backspace
delLine()
return true
end
else
-- Non-Control Action Keys
-- Basic Action Keys
if kc == 200 or kc == 201 then -- Go up one - go up page
local moveAmount = 1
if kc == 201 then
moveAmount = math.floor(sH / 2)
end
cursorY = cursorY - moveAmount
if cursorY < 1 then
cursorY = 1
end
clampCursorX()
return true
elseif kc == 208 or kc == 209 then -- Go down one - go down page
local moveAmount = 1
if kc == 209 then
moveAmount = math.floor(sH / 2)
end
cursorY = cursorY + moveAmount
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
return true
end
-- Major Actions
if kc == 59 then -- F1
lines = {""}
cursorX = 1
cursorY = 1
return true
elseif kc == 61 then -- F3
startLoad()
elseif kc == 62 then -- F4
startSave()
elseif kc == 63 then -- F5
clipsrc.setSetting("clipboard", lines[cursorY])
elseif kc == 64 then -- F6
local tx = clipsrc.getSetting("clipboard") or ""
local txi = tx:find("\n")
local nt = {}
while txi do
table.insert(nt, 1, tx:sub(1, txi - 1))
tx = tx:sub(txi + 1)
txi = tx:find("\n")
end
table.insert(lines, cursorY, tx)
for _, v in ipairs(nt) do
table.insert(lines, cursorY, v)
end
return true
elseif kc == 65 then -- F7
appendFlag = false
elseif kc == 66 then -- F8
if appendFlag then
local base = clipsrc.getSetting("clipboard")
clipsrc.setSetting("clipboard", base .. "\n" .. delLine())
else
clipsrc.setSetting("clipboard", delLine())
end
appendFlag = true
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
end
if ka ~= 0 then
putLetter(unicode.char(ka))
return true
end
return false
end
flush = function ()
for i = 1, sH do
window.span(1, i, getline(i), 0xFFFFFF, 0)
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[2] == window.id then
if e[3] == "line" then
window.span(1, e[4], getline(e[4]), 0xFFFFFF, 0)
elseif filedialog then
elseif e[3] == "touch" then
-- reverse:
--local rY = (y + cursorY) - math.ceil(sH / 2)
local csY = math.ceil(sH / 2)
local nY = math.max(1, math.min(#lines, (math.floor(e[5]) - csY) + cursorY))
cursorY = nY
clampCursorX()
flush()
elseif e[3] == "key" then
if key(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
elseif e[3] == "clipboard" then
local t = e[4]
for i = 1, unicode.len(t) do
local c = unicode.sub(t, i, i)
if c ~= "\r" then
if c == "\n" then
c = "\r"
end
putLetter(c)
end
end
flush()
end
2018-03-28 00:40:05 +11:00
elseif cbs[e[2]] then
if e[3] == "line" then
cbs[e[2]][2](1, 1, cbs[e[2]][3], 0, 0xFFFFFF)
elseif e[3] == "close" then
2018-03-28 00:40:05 +11:00
cbs[e[2]][1]()
cbs[e[2]] = nil
end
end
elseif e[1] == "x.neo.pub.base" and e[2] == "filedialog" and filedialog then
filedialog(e[4])
filedialog = nil
end
end