| if not term.isAvailable() then |
| return |
| end |
| |
| local args, options = shell.parse(...) |
| if #args == 0 then |
| print("Usage: edit <filename>") |
| return |
| end |
| |
| local filename = shell.resolve(args[1]) |
| |
| local readonly = options.r or fs.get(filename).isReadOnly() |
| |
| if fs.isDirectory(filename) or readonly and not fs.exists(filename) then |
| print("file not found") |
| return |
| end |
| |
| term.clear() |
| term.setCursorBlink(true) |
| |
| local running = true |
| local buffer = {} |
| local scrollX, scrollY = 0, 0 |
| |
| ------------------------------------------------------------------------------- |
| |
| local function setStatus(value) |
| local w, h = component.gpu.getResolution() |
| component.gpu.set(1, h, text.padRight(unicode.sub(value, 1, w - 10), w - 10)) |
| end |
| |
| local function getSize() |
| local w, h = component.gpu.getResolution() |
| return w, h - 1 |
| end |
| |
| local function getCursor() |
| local cx, cy = term.getCursor() |
| return cx + scrollX, cy + scrollY |
| end |
| |
| local function line() |
| local cbx, cby = getCursor() |
| return buffer[cby] |
| end |
| |
| local function setCursor(nbx, nby) |
| local w, h = getSize() |
| 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 |
| component.gpu.copy(1, 1 + dy, w, h - dy, 0, -dy) |
| for by = nby - (dy - 1), nby do |
| local str = text.padRight(unicode.sub(buffer[by], 1 + scrollX), w) |
| component.gpu.set(1, by - scrollY, str) |
| end |
| elseif ncy < 1 then |
| term.setCursorBlink(false) |
| local sy = nby - 1 |
| local dy = math.abs(scrollY - sy) |
| scrollY = sy |
| component.gpu.copy(1, 1, w, h - dy, 0, dy) |
| for by = nby, nby + (dy - 1) do |
| local str = text.padRight(unicode.sub(buffer[by], 1 + scrollX), w) |
| component.gpu.set(1, by - scrollY, str) |
| end |
| end |
| term.setCursor(term.getCursor(), nby - scrollY) |
| |
| nbx = math.max(1, math.min(unicode.len(line()) + 1, nbx)) |
| local ncx = nbx - scrollX |
| if ncx > w then |
| term.setCursorBlink(false) |
| local sx = nbx - w |
| local dx = math.abs(scrollX - sx) |
| scrollX = sx |
| component.gpu.copy(1 + dx, 1, w - dx, h, -dx, 0) |
| for by = 1 + scrollY, math.min(h + scrollY, #buffer) do |
| local str = unicode.sub(buffer[by], nbx - (dx - 1), nbx) |
| str = text.padRight(str, dx) |
| component.gpu.set(1 + (w - dx), by - scrollY, str) |
| end |
| elseif ncx < 1 then |
| term.setCursorBlink(false) |
| local sx = nbx - 1 |
| local dx = math.abs(scrollX - sx) |
| scrollX = sx |
| component.gpu.copy(1, 1, w - dx, h, dx, 0) |
| for by = 1 + scrollY, math.min(h + scrollY, #buffer) do |
| local str = unicode.sub(buffer[by], nbx, nbx + dx) |
| --str = text.padRight(str, dx) |
| component.gpu.set(1, by - scrollY, str) |
| end |
| end |
| term.setCursor(nbx - scrollX, nby - scrollY) |
| |
| component.gpu.set(w - 9, h + 1, text.padLeft(string.format("%d,%d", nby, nbx), 10)) |
| end |
| |
| local function home() |
| local cbx, cby = getCursor() |
| setCursor(1, cby) |
| end |
| |
| local function ende() |
| local cbx, cby = getCursor() |
| setCursor(unicode.len(line()) + 1, cby) |
| end |
| |
| local function left() |
| local cbx, cby = getCursor() |
| if cbx > 1 then |
| setCursor(cbx - 1, cby) |
| return true -- for backspace |
| elseif cby > 1 then |
| setCursor(cbx, cby - 1) |
| ende() |
| return true -- again, for backspace |
| end |
| end |
| |
| local function right(n) |
| n = n or 1 |
| local cbx, cby = getCursor() |
| local be = unicode.len(line()) + 1 |
| if cbx < 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) |
| if getCursor() > unicode.len(line()) then |
| ende() |
| end |
| end |
| end |
| |
| local function down(n) |
| n = n or 1 |
| local cbx, cby = getCursor() |
| if cby < #buffer then |
| setCursor(cbx, cby + n) |
| if getCursor() > unicode.len(line()) then |
| ende() |
| end |
| end |
| end |
| |
| local function delete() |
| local cx, cy = term.getCursor() |
| local cbx, cby = getCursor() |
| local w, h = getSize() |
| if cbx <= unicode.len(line()) then |
| term.setCursorBlink(false) |
| buffer[cby] = unicode.sub(line(), 1, cbx - 1) .. |
| unicode.sub(line(), cbx + 1) |
| component.gpu.copy(cx + 1, cy, w - cx, 1, -1, 0) |
| local br = cbx + (w - cx) |
| local char = unicode.sub(line(), br, br) |
| if not char or unicode.len(char) == 0 then |
| char = " " |
| end |
| component.gpu.set(w, cy, char) |
| elseif cby < #buffer then |
| term.setCursorBlink(false) |
| local append = table.remove(buffer, cby + 1) |
| buffer[cby] = buffer[cby] .. append |
| component.gpu.set(cx, cy, append) |
| if cy < h then |
| component.gpu.copy(1, cy + 2, w, h - (cy + 1), 0, -1) |
| component.gpu.set(1, h, text.padRight(buffer[cby + (h - cy)], w)) |
| end |
| setStatus("Save: [Ctrl+S] Close: [Ctrl+W]") |
| end |
| 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 w, h = getSize() |
| buffer[cby] = unicode.sub(line(), 1, cbx - 1) .. |
| value .. |
| unicode.sub(line(), cbx) |
| local len = unicode.len(value) |
| local n = w - (cx - 1) - len |
| if n > 0 then |
| component.gpu.copy(cx, cy, n, 1, len, 0) |
| end |
| component.gpu.set(cx, cy, value) |
| right(len) |
| setStatus("Save: [Ctrl+S] Close: [Ctrl+W]") |
| end |
| |
| local function enter() |
| local cx, cy = term.getCursor() |
| local cbx, cby = getCursor() |
| local w, h = getSize() |
| table.insert(buffer, cby + 1, unicode.sub(buffer[cby], cbx)) |
| buffer[cby] = unicode.sub(buffer[cby], 1, cbx - 1) |
| component.gpu.fill(cx, cy, w - (cx - 1), 1, " ") |
| if cy < h then |
| if cy < h - 1 then |
| component.gpu.copy(1, cy + 1, w, h - (cy + 1), 0, 1) |
| end |
| component.gpu.set(1, cy + 1, text.padRight(buffer[cby + 1], w)) |
| end |
| setCursor(1, cby + 1) |
| setStatus("Save: [Ctrl+S] Close: [Ctrl+W]") |
| end |
| |
| local controlKeyCombos = {[keyboard.keys.s]=true,[keyboard.keys.w]=true, |
| [keyboard.keys.c]=true,[keyboard.keys.x]=true} |
| local function onKeyDown(char, code) |
| if code == keyboard.keys.back and not readonly then |
| if left() then |
| delete() |
| end |
| elseif code == keyboard.keys.delete and not readonly then |
| delete() |
| elseif code == keyboard.keys.left then |
| left() |
| elseif code == keyboard.keys.right then |
| right() |
| elseif code == keyboard.keys.home then |
| home() |
| elseif code == keyboard.keys["end"] then |
| ende() |
| elseif code == keyboard.keys.up then |
| up() |
| elseif code == keyboard.keys.down then |
| down() |
| elseif code == keyboard.keys.pageUp then |
| local w, h = getSize() |
| up(h - 1) |
| elseif code == keyboard.keys.pageDown then |
| local w, h = getSize() |
| down(h - 1) |
| elseif code == keyboard.keys.enter and not readonly then |
| enter() |
| elseif keyboard.isControlDown() and controlKeyCombos[code] then |
| local cbx, cby = getCursor() |
| if code == keyboard.keys.s and not readonly then |
| local new = not fs.exists(filename) |
| local f, reason = io.open(filename, "w") |
| if f then |
| local chars = 0 |
| for _, line in ipairs(buffer) do |
| f:write(line) |
| f:write("\n") |
| 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 |
| elseif code == keyboard.keys.w or |
| code == keyboard.keys.c or |
| code == keyboard.keys.x |
| then |
| -- TODO ask to save if changed |
| running = false |
| end |
| elseif readonly and code == keyboard.keys.q then |
| running = false |
| elseif not keyboard.isControl(char) and not readonly then |
| insert(unicode.char(char)) |
| end |
| end |
| |
| local function onClipboard(value) |
| local cbx, cby = getCursor() |
| local start = 1 |
| local l = value:find("\n", 1, true) |
| if l then |
| repeat |
| insert(string.sub(value, start, l - 1)) |
| enter() |
| 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 w, h = getSize() |
| local chars = 0 |
| for line in f:lines() do |
| table.insert(buffer, line) |
| chars = chars + unicode.len(line) |
| if #buffer <= h then |
| component.gpu.set(1, #buffer, line) |
| end |
| end |
| f:close() |
| local format |
| if readonly then |
| format = [["%s" [readonly] %dL,%dC]] |
| else |
| format = [["%s" [New File] %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 event, address, arg1, arg2, arg3 = event.pull() |
| if type(address) == "string" and component.isPrimary(address) then |
| local blink = true |
| if event == "key_down" then |
| onKeyDown(arg1, arg2) |
| elseif event == "clipboard" then |
| onClipboard(arg1) |
| elseif event == "touch" or event == "drag" then |
| onClick(arg1, arg2) |
| elseif event == "scroll" then |
| onScroll(arg3) |
| else |
| blink = false |
| end |
| if blink then |
| term.setCursorBlink(true) |
| term.setCursorBlink(true) -- force toggle to caret |
| end |
| end |
| end |
| |
| term.clear() |
| term.setCursorBlink(false) |