mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-11-23 19:08:05 +11:00
dcd7154ec2
This license change should only increase the amount of stuff people can do in countries where the public domain is not a valid concept. If this license somehow restricts you as opposed to the previous one, please file an issue.
349 lines
9.0 KiB
Lua
349 lines
9.0 KiB
Lua
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
|
||
--
|
||
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
||
--
|
||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||
-- THIS SOFTWARE.
|
||
|
||
-- '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.",
|
||
"For example, this.",
|
||
}
|
||
|
||
-- 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 lineEdit = require("lineedit")
|
||
|
||
local cursorX = 1
|
||
local cursorY = math.ceil(#lines / 2)
|
||
local ctrlFlag, appendFlag
|
||
local dialogLock = false
|
||
local sW, sH = 37, #lines + 2
|
||
local window = windows(sW, sH)
|
||
local filedialog = nil
|
||
local flush
|
||
|
||
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 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)
|
||
return lineEdit.draw(sW, line, rY == cursorY and cursorXP, rX)
|
||
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
|
||
local function key(ks, 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)
|
||
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
|
||
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
|
||
cursorX = lineEdit.clamp(lines[cursorY], cursorX)
|
||
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
|
||
cursorX = lineEdit.clamp(lines[cursorY], cursorX)
|
||
return true
|
||
end
|
||
-- Major Actions
|
||
if kc == 59 then -- F1
|
||
lines = {""}
|
||
cursorX = 1
|
||
cursorY = 1
|
||
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")
|
||
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
|
||
return 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
|
||
-- LEL Keys
|
||
local lT, lC, lX = lineEdit.key(ks, kc, lines[cursorY], cursorX)
|
||
if lT then
|
||
lines[cursorY] = lT
|
||
end
|
||
if lC then
|
||
cursorX = lC
|
||
end
|
||
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 == "w<" 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 == "w>" and cursorY ~= #lines then
|
||
local l = table.remove(lines, cursorY)
|
||
cursorX = unicode.len(l) + 1
|
||
lines[cursorY] = l .. lines[cursorY]
|
||
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 ()
|
||
for i = 1, sH do
|
||
window.span(1, i, getline(i), 0xFFFFFF, 0)
|
||
end
|
||
end
|
||
|
||
while true do
|
||
local e = {coroutine.yield()}
|
||
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)
|
||
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
|
||
cursorX = lineEdit.clamp(lines[cursorY], cursorX)
|
||
flush()
|
||
elseif e[3] == "key" then
|
||
if key(e[4] ~= 0 and unicode.char(e[4]), e[5], e[6]) then
|
||
flush()
|
||
end
|
||
elseif e[3] == "focus" then
|
||
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
|
||
key(c, 0, true)
|
||
end
|
||
end
|
||
flush()
|
||
end
|
||
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
|
||
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
|