Initial commit
This commit is contained in:
996
edt.lua
Normal file
996
edt.lua
Normal file
@@ -0,0 +1,996 @@
|
||||
local fs = require("filesystem")
|
||||
local keyboard = require("keyboard")
|
||||
local shell = require("shell")
|
||||
local term = require("term") -- TODO use tty and cursor position instead of global area and gpu
|
||||
local text = require("text")
|
||||
local unicode = require("unicode")
|
||||
|
||||
-- Monokai color scheme
|
||||
local bgColor = 0x272822
|
||||
local lineColor = 0x39392F
|
||||
local textColor = 0xF8F8F2
|
||||
local keywordColor = 0xF92672
|
||||
local commentColor = 0x75715E
|
||||
local stringColor = 0xE6DB74
|
||||
local valueColor = 0xAE81FF
|
||||
local builtinColor = 0x66D9EF
|
||||
local lineNrColor = 0x90908A
|
||||
|
||||
local keywords = {
|
||||
['break'] = true,
|
||||
['do'] = true,
|
||||
['else'] = true,
|
||||
['for'] = true,
|
||||
['if'] = true,
|
||||
['elseif'] = true,
|
||||
['return'] = true,
|
||||
['then'] = true,
|
||||
['repeat'] = true,
|
||||
['while'] = true,
|
||||
['until'] = true,
|
||||
['end'] = true,
|
||||
['function'] = true,
|
||||
['local'] = true,
|
||||
['in'] = true,
|
||||
['and'] = true,
|
||||
['or'] = true,
|
||||
['not'] = true,
|
||||
|
||||
['+'] = true,
|
||||
['-'] = true,
|
||||
['%'] = true,
|
||||
['#'] = true,
|
||||
['*'] = true,
|
||||
['/'] = true,
|
||||
['^'] = true,
|
||||
['='] = true,
|
||||
['=='] = true,
|
||||
['~='] = true,
|
||||
['<'] = true,
|
||||
['<='] = true,
|
||||
['>'] = true,
|
||||
['>='] = true,
|
||||
['..'] = true
|
||||
}
|
||||
|
||||
local builtins = {
|
||||
['assert'] = true,
|
||||
['collectgarbage'] = true,
|
||||
['dofile'] = true,
|
||||
['error'] = true,
|
||||
['getfenv'] = true,
|
||||
['getmetatable'] = true,
|
||||
['ipairs'] = true,
|
||||
['loadfile'] = true,
|
||||
['loadstring'] = true,
|
||||
['module'] = true,
|
||||
['next'] = true,
|
||||
['pairs'] = true,
|
||||
['pcall'] = true,
|
||||
['print'] = true,
|
||||
['rawequal'] = true,
|
||||
['rawget'] = true,
|
||||
['rawset'] = true,
|
||||
['require'] = true,
|
||||
['select'] = true,
|
||||
['setfenv'] = true,
|
||||
['setmetatable'] = true,
|
||||
['tonumber'] = true,
|
||||
['tostring'] = true,
|
||||
['type'] = true,
|
||||
['unpack'] = true,
|
||||
['xpcall'] = true
|
||||
}
|
||||
|
||||
local values = {
|
||||
['false'] = true,
|
||||
['nil'] = true,
|
||||
['true'] = true,
|
||||
['_G'] = true,
|
||||
['_VERSION'] = true
|
||||
}
|
||||
|
||||
local patterns = {
|
||||
{"^%-%-%[%[.-%]%]", commentColor},
|
||||
{"^%-%-.*", commentColor},
|
||||
{"^\"\"", stringColor},
|
||||
{"^\".-[^\\]\"", stringColor},
|
||||
{"^\'\'", stringColor},
|
||||
{"^\'.-[^\\]\'", stringColor},
|
||||
{"^%[%[.-%]%]", stringColor},
|
||||
{"^[%w_%+%-%%%#%*%/%^%=%~%<%>%.]+", function(text)
|
||||
if values[text] or tonumber(text) then
|
||||
local match = text:find('^0x%x%x%x%x%x%x$')
|
||||
|
||||
if match then
|
||||
local luminosity = 0.2126 * tonumber('0x' .. text:sub(3, 4)) + 0.7152 * tonumber('0x' .. text:sub(5, 6)) + 0.0722 * tonumber('0x' .. text:sub(7, 8))
|
||||
|
||||
if luminosity > 20 then
|
||||
return 0x000000, tonumber(text)
|
||||
else
|
||||
return 0xffffff, tonumber(text)
|
||||
end
|
||||
else
|
||||
return valueColor
|
||||
end
|
||||
elseif keywords[text] then
|
||||
return keywordColor
|
||||
elseif builtins[text] then
|
||||
return builtinColor
|
||||
end
|
||||
|
||||
return textColor
|
||||
end}
|
||||
}
|
||||
|
||||
local cache = {}
|
||||
local currentMargin = 7
|
||||
|
||||
if not term.isAvailable() then
|
||||
return
|
||||
end
|
||||
local gpu = term.gpu()
|
||||
|
||||
local originalFg = gpu.setForeground(textColor)
|
||||
local originalBg = gpu.setBackground(bgColor)
|
||||
|
||||
local function resetColors()
|
||||
gpu.setForeground(originalFg)
|
||||
gpu.setBackground(originalBg)
|
||||
end
|
||||
|
||||
local args, options = shell.parse(...)
|
||||
if #args == 0 then
|
||||
resetColors()
|
||||
io.write("Usage: edit <filename>")
|
||||
return
|
||||
end
|
||||
|
||||
local filename = shell.resolve(args[1])
|
||||
local file_parentpath = fs.path(filename)
|
||||
|
||||
if fs.exists(file_parentpath) and not fs.isDirectory(file_parentpath) then
|
||||
resetColors()
|
||||
io.stderr:write(string.format("Not a directory: %s\n", file_parentpath))
|
||||
return 1
|
||||
end
|
||||
|
||||
local readonly = options.r or fs.get(filename) == nil or fs.get(filename).isReadOnly()
|
||||
|
||||
if fs.isDirectory(filename) then
|
||||
resetColors()
|
||||
io.stderr:write("file is a directory\n")
|
||||
return 1
|
||||
elseif not fs.exists(filename) and readonly then
|
||||
resetColors()
|
||||
io.stderr:write("file system is read only\n")
|
||||
return 1
|
||||
end
|
||||
|
||||
local function loadConfig()
|
||||
local env = {}
|
||||
local config = loadfile("/etc/edit.cfg", nil, env)
|
||||
if config then
|
||||
pcall(config)
|
||||
end
|
||||
env.keybinds = env.keybinds or {
|
||||
left = {{"left"}},
|
||||
right = {{"right"}},
|
||||
up = {{"up"}},
|
||||
down = {{"down"}},
|
||||
home = {{"home"}},
|
||||
eol = {{"end"}},
|
||||
pageUp = {{"pageUp"}},
|
||||
pageDown = {{"pageDown"}},
|
||||
|
||||
backspace = {{"back"}},
|
||||
delete = {{"delete"}},
|
||||
deleteLine = {{"control", "delete"}, {"shift", "delete"}},
|
||||
newline = {{"enter"}},
|
||||
|
||||
save = {{"control", "s"}},
|
||||
close = {{"control", "w"}},
|
||||
find = {{"control", "f"}},
|
||||
findnext = {{"control", "g"}, {"control", "n"}, {"f3"}},
|
||||
|
||||
jump = {{'control', 'j'}}
|
||||
}
|
||||
|
||||
if not config then
|
||||
local root = fs.get("/")
|
||||
if root and not root.isReadOnly() then
|
||||
fs.makeDirectory("/etc")
|
||||
local f = io.open("/etc/edit.cfg", "w")
|
||||
if f then
|
||||
local serialization = require("serialization")
|
||||
for k, v in pairs(env) do
|
||||
f:write(k.."="..tostring(serialization.serialize(v, math.huge)).."\n")
|
||||
end
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
return env
|
||||
end
|
||||
|
||||
term.clear()
|
||||
term.setCursorBlink(true)
|
||||
|
||||
local running = true
|
||||
local buffer = {}
|
||||
local scrollX, scrollY = 0, 0
|
||||
local config = loadConfig()
|
||||
|
||||
local getKeyBindHandler
|
||||
|
||||
local function helpStatusText()
|
||||
local function prettifyKeybind(label, command)
|
||||
local keybind = type(config.keybinds) == "table" and config.keybinds[command]
|
||||
if type(keybind) ~= "table" or type(keybind[1]) ~= "table" then return "" end
|
||||
local alt, control, shift, key
|
||||
for _, value in ipairs(keybind[1]) do
|
||||
if value == "alt" then alt = true
|
||||
elseif value == "control" then control = true
|
||||
elseif value == "shift" then shift = true
|
||||
else key = value end
|
||||
end
|
||||
if not key then return "" end
|
||||
return label .. ": [" ..
|
||||
(control and "Ctrl+" or "") ..
|
||||
(alt and "Alt+" or "") ..
|
||||
(shift and "Shift+" or "") ..
|
||||
unicode.upper(key) ..
|
||||
"] "
|
||||
end
|
||||
return prettifyKeybind("Save", "save") ..
|
||||
prettifyKeybind("Close", "close") ..
|
||||
prettifyKeybind("Find", "find") ..
|
||||
prettifyKeybind('Jump to line', 'jump')
|
||||
end
|
||||
|
||||
local currentStatus = ''
|
||||
|
||||
local function setStatus(value)
|
||||
local x, y, w, h = term.getGlobalArea()
|
||||
value = unicode.wlen(value) > w - 10 and unicode.wtrunc(value, w - 9) or value
|
||||
value = text.padRight(value, w - 10)
|
||||
gpu.set(x, y + h - 1, value)
|
||||
|
||||
currentStatus = value
|
||||
end
|
||||
|
||||
local function getArea()
|
||||
local x, y, w, h = term.getGlobalArea()
|
||||
return x + currentMargin, y, w - currentMargin, h - 1
|
||||
end
|
||||
|
||||
local function removePrefix(line, length)
|
||||
if length >= unicode.wlen(line) then
|
||||
return ""
|
||||
else
|
||||
local prefix = unicode.wtrunc(line, length + 1)
|
||||
local suffix = unicode.sub(line, unicode.len(prefix) + 1)
|
||||
length = length - unicode.wlen(prefix)
|
||||
if length > 0 then
|
||||
suffix = (" "):rep(unicode.charWidth(suffix) - length) .. unicode.sub(suffix, 2)
|
||||
end
|
||||
return suffix
|
||||
end
|
||||
end
|
||||
|
||||
local function lengthToChars(line, length)
|
||||
if length > unicode.wlen(line) then
|
||||
return unicode.len(line) + 1
|
||||
else
|
||||
local prefix = unicode.wtrunc(line, length)
|
||||
return unicode.len(prefix) + 1
|
||||
end
|
||||
end
|
||||
|
||||
local function isWideAtPosition(line, x)
|
||||
local index = lengthToChars(line, x)
|
||||
if index > unicode.len(line) then
|
||||
return false, false
|
||||
end
|
||||
local prefix = unicode.sub(line, 1, index)
|
||||
local char = unicode.sub(line, index, index)
|
||||
return unicode.isWide(char), unicode.wlen(prefix) == x
|
||||
end
|
||||
|
||||
local function drawLine(x, y, w, h, lineNr)
|
||||
local yLocal = lineNr - scrollY
|
||||
local drawY = y - 1 + lineNr - scrollY
|
||||
|
||||
if yLocal > 0 and yLocal <= h then
|
||||
local colors = {}
|
||||
local line = buffer[lineNr] or ""
|
||||
|
||||
if cache[lineNr] and cache[lineNr][1] == line then
|
||||
colors = cache[lineNr][2]
|
||||
else
|
||||
local function appendTextInColor(text, color, bgcolor)
|
||||
local data = colors[#colors]
|
||||
|
||||
if data ~= nil then
|
||||
if data[2] == color and data[3] == bgcolor then
|
||||
data[1] = data[1] .. text
|
||||
else
|
||||
colors[#colors + 1] = {text, color, bgcolor}
|
||||
end
|
||||
else
|
||||
colors[#colors + 1] = {text, color, bgcolor}
|
||||
end
|
||||
end
|
||||
|
||||
local len = 0
|
||||
|
||||
for char = 1, line:len() do
|
||||
if char > len then
|
||||
local patternFound = false
|
||||
|
||||
for pat = 1, #patterns do
|
||||
local data = patterns[pat]
|
||||
local foundb, founde = line:find(data[1], char)
|
||||
|
||||
if foundb ~= nil then
|
||||
local text = line:sub(foundb, founde)
|
||||
local color = data[2]
|
||||
local bgcolor = data[3]
|
||||
|
||||
if type(color) == 'function' then
|
||||
color, bgcolor = color(text)
|
||||
end
|
||||
|
||||
appendTextInColor(text, color, bgcolor)
|
||||
len = len + (founde - foundb + 1)
|
||||
|
||||
patternFound = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not patternFound then
|
||||
appendTextInColor(line:sub(char, char), textColor)
|
||||
len = len + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cache[lineNr] = {line, colors}
|
||||
end
|
||||
|
||||
local i = 0
|
||||
local cx, cy = term.getCursor()
|
||||
local lineBg = bgColor
|
||||
|
||||
if cy + scrollY == lineNr then
|
||||
lineBg = lineColor
|
||||
end
|
||||
|
||||
gpu.setBackground(lineBg)
|
||||
gpu.fill(1, y - 1 + lineNr - scrollY, 7, 1, ' ')
|
||||
gpu.fill(x, drawY, w + currentMargin, 1, ' ')
|
||||
|
||||
if lineNr <= #buffer then
|
||||
for l = 1, #colors do
|
||||
local data = colors[l]
|
||||
local text = data[1]
|
||||
local color = data[2]
|
||||
local bg = data[3]
|
||||
local drawAt = i - scrollX + x
|
||||
|
||||
if drawAt + text:len() > 0 then
|
||||
local currentColor = gpu.setForeground(color)
|
||||
local currentBg = gpu.setBackground(bg or lineBg)
|
||||
gpu.set(drawAt, drawY, text)
|
||||
gpu.setForeground(currentColor)
|
||||
gpu.setBackground(currentBg)
|
||||
end
|
||||
|
||||
i = i + text:len()
|
||||
end
|
||||
|
||||
local currentColor = gpu.setForeground(lineNrColor)
|
||||
local number = tostring(math.floor(lineNr))
|
||||
|
||||
gpu.fill(1, y - 1 + lineNr - scrollY, 7, 1, ' ')
|
||||
gpu.set(2 + (5 - number:len()), y - 1 + lineNr - scrollY, number)
|
||||
gpu.setForeground(currentColor)
|
||||
gpu.setBackground(bgColor)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function getCursor()
|
||||
local cx, cy = term.getCursor()
|
||||
return cx + scrollX - currentMargin, cy + scrollY
|
||||
end
|
||||
|
||||
local function line()
|
||||
local cbx, cby = getCursor()
|
||||
return buffer[cby]
|
||||
end
|
||||
|
||||
local function getNormalizedCursor()
|
||||
local cbx, cby = getCursor()
|
||||
local wide, right = isWideAtPosition(buffer[cby], cbx)
|
||||
if wide and right then
|
||||
cbx = cbx - 1
|
||||
end
|
||||
return cbx, cby
|
||||
end
|
||||
|
||||
local function setCursor(nbx, nby)
|
||||
local x, y, w, h = getArea()
|
||||
nby = math.max(1, math.min(#buffer, nby))
|
||||
|
||||
local ncy = nby - scrollY
|
||||
if ncy > h then
|
||||
term.setCursorBlink(false)
|
||||
local sy = nby - h
|
||||
local dy = math.abs(scrollY - sy)
|
||||
scrollY = sy
|
||||
if h > dy then
|
||||
gpu.copy(x - currentMargin, y + dy, w + currentMargin, h - dy, 0, -dy)
|
||||
end
|
||||
for lineNr = nby - (math.min(dy, h) - 1), nby do
|
||||
drawLine(x, y, w, h, lineNr)
|
||||
end
|
||||
elseif ncy < 1 then
|
||||
term.setCursorBlink(false)
|
||||
local sy = nby - 1
|
||||
local dy = math.abs(scrollY - sy)
|
||||
scrollY = sy
|
||||
if h > dy then
|
||||
gpu.copy(x - currentMargin, y, w + currentMargin, h - dy, 0, dy)
|
||||
end
|
||||
for lineNr = nby, nby + (math.min(dy, h) - 1) do
|
||||
drawLine(x, y, w, h, lineNr)
|
||||
end
|
||||
end
|
||||
term.setCursor(term.getCursor(), nby - scrollY)
|
||||
|
||||
nbx = math.max(1, math.min(unicode.wlen(line()) + 1, nbx))
|
||||
local wide, right = isWideAtPosition(line(), nbx)
|
||||
local ncx = nbx - scrollX
|
||||
if ncx > w or (ncx + 1 > w and wide and not right) then
|
||||
term.setCursorBlink(false)
|
||||
scrollX = nbx - w + ((wide and not right) and 1 or 0)
|
||||
for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do
|
||||
drawLine(x, y, w, h, lineNr)
|
||||
end
|
||||
elseif ncx < 1 or (ncx - 1 < 1 and wide and right) then
|
||||
term.setCursorBlink(false)
|
||||
scrollX = nbx - 1 - ((wide and right) and 1 or 0)
|
||||
for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do
|
||||
drawLine(x, y, w, h, lineNr)
|
||||
end
|
||||
end
|
||||
term.setCursor(nbx - scrollX + currentMargin, nby - scrollY)
|
||||
nbx, nby = getCursor()
|
||||
gpu.set(x + w - 10, y + h, text.padLeft(string.format("%d,%d", nby, nbx), 10))
|
||||
end
|
||||
|
||||
local function highlight(bx, by, length, enabled)
|
||||
local x, y, w, h = getArea()
|
||||
local cx, cy = bx - scrollX, by - scrollY
|
||||
cx = math.max(1, math.min(w, cx))
|
||||
cy = math.max(1, math.min(h, cy))
|
||||
length = math.max(1, math.min(w - cx, length))
|
||||
|
||||
local fg, fgp = gpu.getForeground()
|
||||
local bg, bgp = gpu.getBackground()
|
||||
if enabled then
|
||||
gpu.setForeground(bg, bgp)
|
||||
gpu.setBackground(fg, fgp)
|
||||
end
|
||||
local indexFrom = lengthToChars(buffer[by], bx)
|
||||
local value = unicode.sub(buffer[by], indexFrom)
|
||||
if unicode.wlen(value) > length then
|
||||
value = unicode.wtrunc(value, length + 1)
|
||||
end
|
||||
gpu.set(x - 1 + cx, y - 1 + cy, value)
|
||||
if enabled then
|
||||
gpu.setForeground(fg, fgp)
|
||||
gpu.setBackground(bg, bgp)
|
||||
end
|
||||
end
|
||||
|
||||
local function home()
|
||||
local cbx, cby = getCursor()
|
||||
setCursor(1, cby)
|
||||
end
|
||||
|
||||
local function ende()
|
||||
local cbx, cby = getCursor()
|
||||
setCursor(unicode.wlen(line()) + 1, cby)
|
||||
end
|
||||
|
||||
local function left()
|
||||
local cbx, cby = getNormalizedCursor()
|
||||
if cbx > 1 then
|
||||
local wideTarget, rightTarget = isWideAtPosition(line(), cbx - 1)
|
||||
if wideTarget and rightTarget then
|
||||
setCursor(cbx - 2, cby)
|
||||
else
|
||||
setCursor(cbx - 1, cby)
|
||||
end
|
||||
return true
|
||||
elseif cby > 1 then
|
||||
setCursor(cbx, cby - 1)
|
||||
ende()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local function right(n)
|
||||
n = n or 1
|
||||
local cbx, cby = getNormalizedCursor()
|
||||
local be = unicode.wlen(line()) + 1
|
||||
local wide, right = isWideAtPosition(line(), cbx + n)
|
||||
if wide and right then
|
||||
n = n + 1
|
||||
end
|
||||
if cbx + n <= be then
|
||||
setCursor(cbx + n, cby)
|
||||
elseif cby < #buffer then
|
||||
setCursor(1, cby + 1)
|
||||
end
|
||||
end
|
||||
|
||||
local function up(n)
|
||||
n = n or 1
|
||||
local cbx, cby = getCursor()
|
||||
if cby > 1 then
|
||||
setCursor(cbx, cby - n)
|
||||
end
|
||||
end
|
||||
|
||||
local function down(n)
|
||||
n = n or 1
|
||||
local cbx, cby = getCursor()
|
||||
if cby < #buffer then
|
||||
setCursor(cbx, cby + n)
|
||||
end
|
||||
end
|
||||
|
||||
local function delete(fullRow)
|
||||
local cx, cy = term.getCursor()
|
||||
local cbx, cby = getCursor()
|
||||
local x, y, w, h = getArea()
|
||||
local function deleteRow(row)
|
||||
local content = table.remove(buffer, row)
|
||||
local rcy = cy + (row - cby)
|
||||
if rcy <= h then
|
||||
gpu.copy(x, y + rcy, w, h - rcy, 0, -1)
|
||||
drawLine(x, y, w, h, row + (h - rcy))
|
||||
end
|
||||
return content
|
||||
end
|
||||
if fullRow then
|
||||
term.setCursorBlink(false)
|
||||
if #buffer > 1 then
|
||||
deleteRow(cby)
|
||||
else
|
||||
buffer[cby] = ""
|
||||
gpu.fill(x, y - 1 + cy, w, 1, " ")
|
||||
end
|
||||
setCursor(1, cby)
|
||||
elseif cbx <= unicode.wlen(line()) then
|
||||
term.setCursorBlink(false)
|
||||
local index = lengthToChars(line(), cbx)
|
||||
buffer[cby] = unicode.sub(line(), 1, index - 1) ..
|
||||
unicode.sub(line(), index + 1)
|
||||
drawLine(x, y, w, h, cby)
|
||||
elseif cby < #buffer then
|
||||
term.setCursorBlink(false)
|
||||
local append = deleteRow(cby + 1)
|
||||
buffer[cby] = buffer[cby] .. append
|
||||
drawLine(x, y, w, h, cby)
|
||||
else
|
||||
return
|
||||
end
|
||||
setStatus(helpStatusText())
|
||||
end
|
||||
|
||||
-- enter() jetzt mit Schalter für Auto-Indent
|
||||
local function enter(autoIndent)
|
||||
if autoIndent == nil then autoIndent = true end
|
||||
|
||||
term.setCursorBlink(false)
|
||||
local cx, cy = term.getCursor()
|
||||
local cbx, cby = getCursor()
|
||||
local x, y, w, h = getArea()
|
||||
local index = lengthToChars(line(), cbx)
|
||||
table.insert(buffer, cby + 1, unicode.sub(buffer[cby], index))
|
||||
buffer[cby] = unicode.sub(buffer[cby], 1, index - 1)
|
||||
drawLine(x, y, w, h, cby)
|
||||
if cy < h then
|
||||
if cy < h - 1 then
|
||||
gpu.copy(x, y + cy, w, h - (cy + 1), 0, 1)
|
||||
end
|
||||
drawLine(x, y, w, h, cby + 1)
|
||||
end
|
||||
|
||||
if autoIndent then
|
||||
local whitespace = buffer[cby]:match('^[%s]+') or ""
|
||||
buffer[cby + 1] = whitespace .. buffer[cby + 1]
|
||||
setCursor(1 + whitespace:len(), cby + 1)
|
||||
else
|
||||
setCursor(1, cby + 1)
|
||||
end
|
||||
|
||||
setStatus(helpStatusText())
|
||||
end
|
||||
|
||||
local findText = ""
|
||||
|
||||
local function find()
|
||||
local x, y, w, h = getArea()
|
||||
local cx, cy = term.getCursor()
|
||||
local cbx, cby = getCursor()
|
||||
local ibx, iby = cbx, cby
|
||||
while running do
|
||||
if unicode.len(findText) > 0 then
|
||||
local sx, sy
|
||||
for syo = 1, #buffer do
|
||||
sy = (iby + syo - 1 + #buffer - 1) % #buffer + 1
|
||||
sx = string.find(buffer[sy], findText, syo == 1 and ibx or 1, true)
|
||||
if sx and (sx >= ibx or syo > 1) then
|
||||
break
|
||||
end
|
||||
end
|
||||
if not sx then
|
||||
sy = iby
|
||||
sx = string.find(buffer[sy], findText, nil, true)
|
||||
end
|
||||
if sx then
|
||||
sx = unicode.wlen(string.sub(buffer[sy], 1, sx - 1)) + 1
|
||||
cbx, cby = sx, sy
|
||||
setCursor(cbx, cby)
|
||||
highlight(cbx, cby, unicode.wlen(findText), true)
|
||||
end
|
||||
end
|
||||
term.setCursor(7 + unicode.wlen(findText), h + 1)
|
||||
setStatus("Find: " .. findText)
|
||||
|
||||
local _, address, char, code = term.pull("key_down")
|
||||
if address == term.keyboard() then
|
||||
local handler, name = getKeyBindHandler(code)
|
||||
highlight(cbx, cby, unicode.wlen(findText), false)
|
||||
if name == "newline" then
|
||||
break
|
||||
elseif name == "close" then
|
||||
handler()
|
||||
elseif name == "backspace" then
|
||||
findText = unicode.sub(findText, 1, -2)
|
||||
elseif name == "find" or name == "findnext" then
|
||||
ibx = cbx + 1
|
||||
iby = cby
|
||||
elseif not keyboard.isControl(char) then
|
||||
findText = findText .. unicode.char(char)
|
||||
end
|
||||
end
|
||||
end
|
||||
setCursor(cbx, cby)
|
||||
setStatus(helpStatusText())
|
||||
end
|
||||
|
||||
local function fix()
|
||||
local x, y, w, h = getArea()
|
||||
|
||||
gpu.fill(x, y, w, h, ' ')
|
||||
|
||||
for i = 1, #buffer do
|
||||
if i > scrollY and i <= (scrollY + h) then
|
||||
drawLine(x, y, w, h, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function jump()
|
||||
local x, y, w, h = getArea()
|
||||
local cx, cy = term.getCursor()
|
||||
local currentStatus = currentStatus
|
||||
|
||||
setStatus('Jump to line #')
|
||||
|
||||
local current = ''
|
||||
|
||||
while true do
|
||||
local _, address, char, code = term.pull('key_down')
|
||||
|
||||
char = math.floor(char)
|
||||
|
||||
if address == term.keyboard() then
|
||||
if char == 13 then
|
||||
break
|
||||
elseif char >= string.byte('0') and char <= string.byte('9') then
|
||||
if current:len() < 5 then
|
||||
current = current .. string.char(char)
|
||||
end
|
||||
elseif char == 127 then
|
||||
if current:len() > 0 then
|
||||
current = current:sub(1, -1)
|
||||
end
|
||||
end
|
||||
|
||||
term.setCursor(15, h + 1)
|
||||
gpu.set(15, h + 1, ' ')
|
||||
term.write(current)
|
||||
end
|
||||
end
|
||||
|
||||
term.setCursor(cx, cy)
|
||||
|
||||
current = tonumber(current)
|
||||
|
||||
if current then
|
||||
if current <= #buffer then
|
||||
setCursor(1, current)
|
||||
setStatus('Jumped to line ' .. tostring(current))
|
||||
else
|
||||
setStatus('Line ' .. tostring(current) .. ' does not exist')
|
||||
end
|
||||
else
|
||||
setStatus(currentStatus)
|
||||
end
|
||||
end
|
||||
|
||||
local keyBindHandlers = {
|
||||
left = left,
|
||||
right = right,
|
||||
up = up,
|
||||
down = down,
|
||||
home = home,
|
||||
eol = ende,
|
||||
pageUp = function()
|
||||
local x, y, w, h = getArea()
|
||||
up(h - 1)
|
||||
end,
|
||||
pageDown = function()
|
||||
local x, y, w, h = getArea()
|
||||
down(h - 1)
|
||||
end,
|
||||
|
||||
backspace = function()
|
||||
if not readonly and left() then
|
||||
delete()
|
||||
end
|
||||
end,
|
||||
delete = function()
|
||||
if not readonly then
|
||||
delete()
|
||||
end
|
||||
end,
|
||||
deleteLine = function()
|
||||
if not readonly then
|
||||
delete(true)
|
||||
end
|
||||
end,
|
||||
newline = function()
|
||||
if not readonly then
|
||||
enter()
|
||||
end
|
||||
end,
|
||||
|
||||
save = function()
|
||||
if readonly then return end
|
||||
local new = not fs.exists(filename)
|
||||
local backup
|
||||
if not new then
|
||||
backup = filename .. "~"
|
||||
for i = 1, math.huge do
|
||||
if not fs.exists(backup) then
|
||||
break
|
||||
end
|
||||
backup = filename .. "~" .. i
|
||||
end
|
||||
fs.copy(filename, backup)
|
||||
end
|
||||
if not fs.exists(file_parentpath) then
|
||||
fs.makeDirectory(file_parentpath)
|
||||
end
|
||||
local f, reason = io.open(filename, "w")
|
||||
if f then
|
||||
local chars, firstLine = 0, true
|
||||
for _, line in ipairs(buffer) do
|
||||
if not firstLine then
|
||||
line = "\n" .. line
|
||||
end
|
||||
firstLine = false
|
||||
f:write(line)
|
||||
chars = chars + unicode.len(line)
|
||||
end
|
||||
f:close()
|
||||
local format
|
||||
if new then
|
||||
format = [["%s" [New] %dL,%dC written]]
|
||||
else
|
||||
format = [["%s" %dL,%dC written]]
|
||||
end
|
||||
setStatus(string.format(format, fs.name(filename), #buffer, chars))
|
||||
else
|
||||
setStatus(reason)
|
||||
end
|
||||
if not new then
|
||||
fs.remove(backup)
|
||||
end
|
||||
end,
|
||||
close = function()
|
||||
running = false
|
||||
end,
|
||||
find = function()
|
||||
findText = ""
|
||||
find()
|
||||
end,
|
||||
findnext = find,
|
||||
|
||||
jump = jump
|
||||
}
|
||||
|
||||
getKeyBindHandler = function(code)
|
||||
if type(config.keybinds) ~= "table" then return end
|
||||
local result, resultName, resultWeight = nil, nil, 0
|
||||
for command, keybinds in pairs(config.keybinds) do
|
||||
if type(keybinds) == "table" and keyBindHandlers[command] then
|
||||
for _, keybind in ipairs(keybinds) do
|
||||
if type(keybind) == "table" then
|
||||
local alt, control, shift, key
|
||||
for _, value in ipairs(keybind) do
|
||||
if value == "alt" then alt = true
|
||||
elseif value == "control" then control = true
|
||||
elseif value == "shift" then shift = true
|
||||
else key = value end
|
||||
end
|
||||
local keyboardAddress = term.keyboard()
|
||||
if (not alt or keyboard.isAltDown(keyboardAddress)) and
|
||||
(not control or keyboard.isControlDown(keyboardAddress)) and
|
||||
(not shift or keyboard.isShiftDown(keyboardAddress)) and
|
||||
code == keyboard.keys[key] and
|
||||
#keybind > resultWeight
|
||||
then
|
||||
resultWeight = #keybind
|
||||
resultName = command
|
||||
result = keyBindHandlers[command]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return result, resultName
|
||||
end
|
||||
|
||||
local function insert(value)
|
||||
if not value or unicode.len(value) < 1 then
|
||||
return
|
||||
end
|
||||
term.setCursorBlink(false)
|
||||
local cx, cy = term.getCursor()
|
||||
local cbx, cby = getCursor()
|
||||
local x, y, w, h = getArea()
|
||||
local index = lengthToChars(line(), cbx)
|
||||
buffer[cby] = unicode.sub(line(), 1, index - 1) ..
|
||||
value ..
|
||||
unicode.sub(line(), index)
|
||||
drawLine(x, y, w, h, cby)
|
||||
right(unicode.wlen(value))
|
||||
setStatus(helpStatusText())
|
||||
end
|
||||
|
||||
local function onKeyDown(char, code)
|
||||
local handler = getKeyBindHandler(code)
|
||||
if handler then
|
||||
handler()
|
||||
elseif readonly and code == keyboard.keys.q then
|
||||
running = false
|
||||
elseif not readonly then
|
||||
if not keyboard.isControl(char) then
|
||||
insert(unicode.char(char))
|
||||
elseif unicode.char(char) == "\t" then
|
||||
insert(" ")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- WICHTIG: hier das Einfügen fixen
|
||||
local function onClipboard(value)
|
||||
value = value:gsub("\r\n", "\n")
|
||||
local cbx, cby = getCursor()
|
||||
local start = 1
|
||||
local l = value:find("\n", 1, true)
|
||||
if l then
|
||||
repeat
|
||||
local line = string.sub(value, start, l - 1)
|
||||
line = text.detab(line, 2)
|
||||
insert(line)
|
||||
-- beim Einfügen KEIN Auto-Indent
|
||||
enter(false)
|
||||
start = l + 1
|
||||
l = value:find("\n", start, true)
|
||||
until not l
|
||||
end
|
||||
insert(string.sub(value, start))
|
||||
end
|
||||
|
||||
local function onClick(x, y)
|
||||
setCursor(x + scrollX, y + scrollY)
|
||||
end
|
||||
|
||||
local function onScroll(direction)
|
||||
local cbx, cby = getCursor()
|
||||
setCursor(cbx, cby - direction * 12)
|
||||
end
|
||||
|
||||
do
|
||||
local f = io.open(filename)
|
||||
if f then
|
||||
local x, y, w, h = getArea()
|
||||
local chars = 0
|
||||
for line in f:lines() do
|
||||
table.insert(buffer, line)
|
||||
chars = chars + unicode.len(line)
|
||||
if #buffer <= h then
|
||||
drawLine(x, y, w, h, #buffer)
|
||||
end
|
||||
end
|
||||
f:close()
|
||||
if #buffer == 0 then
|
||||
table.insert(buffer, "")
|
||||
end
|
||||
local format
|
||||
if readonly then
|
||||
format = [["%s" [readonly] %dL,%dC]]
|
||||
else
|
||||
format = [["%s" %dL,%dC]]
|
||||
end
|
||||
setStatus(string.format(format, fs.name(filename), #buffer, chars))
|
||||
else
|
||||
table.insert(buffer, "")
|
||||
setStatus(string.format([["%s" [New File] ]], fs.name(filename)))
|
||||
end
|
||||
setCursor(1, 1)
|
||||
end
|
||||
|
||||
while running do
|
||||
local startX = scrollX
|
||||
local startY = scrollY
|
||||
local _, startC = getCursor()
|
||||
|
||||
local event, address, arg1, arg2, arg3 = term.pull()
|
||||
if address == term.keyboard() or address == term.screen() then
|
||||
local blink = true
|
||||
if event == "key_down" then
|
||||
onKeyDown(arg1, arg2)
|
||||
elseif event == "clipboard" and not readonly then
|
||||
onClipboard(arg1)
|
||||
elseif event == "touch" or event == "drag" then
|
||||
local x, y, w, h = getArea()
|
||||
arg1 = arg1 - x + 1
|
||||
arg2 = arg2 - y + 1
|
||||
if arg1 >= 1 and arg2 >= 1 and arg1 <= w and arg2 <= h then
|
||||
onClick(arg1, arg2)
|
||||
end
|
||||
elseif event == "scroll" then
|
||||
onScroll(arg3)
|
||||
else
|
||||
blink = false
|
||||
end
|
||||
if blink then
|
||||
term.setCursorBlink(true)
|
||||
end
|
||||
|
||||
local _, endC = getCursor()
|
||||
local x, y, w, h = getArea()
|
||||
|
||||
if startC ~= endC then
|
||||
drawLine(x, y, w, h, startC)
|
||||
end
|
||||
|
||||
drawLine(x, y, w, h, endC)
|
||||
end
|
||||
end
|
||||
|
||||
resetColors()
|
||||
term.clear()
|
||||
term.setCursorBlink(true)
|
||||
11
programs.cfg
Normal file
11
programs.cfg
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
["edt"] = {
|
||||
files = {
|
||||
["master/edt.lua"] = "/usr/bin"
|
||||
},
|
||||
name = "edt -- shedit 2",
|
||||
description = "An advanced text editor for the terminal.",
|
||||
authors = "edraft",
|
||||
repo = "tree/master/edt"
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user