local fs = require("filesystem") local shell = require("shell") local gpu = require("component").gpu local event = require("event") local computer = require("computer") local ocelot = require("component").ocelot local resX, resY = gpu.getResolution() local textBuffer = gpu.allocateBuffer(resX, resY - 1) local args = {...} local file = args[1] if not file then print("\x1b[91mEnter a file name.") return end if fs.isDirectory(file) then print("\x1b[91mThe specified file is a directory.") return end if file:sub(1, 1) ~= "/" then file = fs.concat(shell.getWorkingDirectory(), file) end local data = "" if fs.exists(file) then local handle = fs.open(file) local tmpdata repeat tmpdata = handle:read(math.huge or math.maxinteger) data = data .. (tmpdata or "") until not tmpdata end local lines = {} for line in data:gmatch("[^\r\n]+") do table.insert(lines, line) end local function renderText(xOffset, yOffset) gpu.setActiveBuffer(textBuffer) gpu.setBackground(0x000000) gpu.setForeground(0xFFFFFF) gpu.fill(1, 1, resX, resY - 1, " ") for i = yOffset + 1, #lines do gpu.set(1, i - yOffset, lines[i]:sub(xOffset + 1)) end gpu.setActiveBuffer(0) gpu.bitblt(0, 1, 1, resX, resY - 1, textBuffer, 1, 1) end local function addLine(pos) if pos <= #lines then for i = #lines, pos, -1 do lines[i + 1] = lines[i] end lines[pos] = "" else table.insert(lines, "") end end local function removeLine(pos) length = #lines for i = pos + 1, length do lines[i - 1] = lines[i] end lines[length] = nil end local function save() gpu.setForeground(0xFFFFFF) gpu.setBackground(0x000000) gpu.set(1, resY, "Enter a location to save:") local savepath = file or "" local saveCursorX = 27 + #savepath local ready = false local eventArgs = {} gpu.fill(27, resY, resX - 27, 1, " ") gpu.set(27, resY, savepath) gpu.setForeground(0x000000) gpu.setBackground(0xFFFFFF) gpu.set(saveCursorX, resY, " ") gpu.setForeground(0xFFFFFF) gpu.setBackground(0x000000) repeat local shouldRender = false coroutine.yield() eventArgs = {event.pull("key_down", 0)} if keyboard.keys[eventArgs[4]] == "enter" then ready = true end if keyboard.keys[eventArgs[4]] == "back" then savepath = string.sub(savepath, 1, #savepath - 1) if saveCursorX > 27 then saveCursorX = saveCursorX - 1 end shouldRender = true end if not keyboard.keys.special[eventArgs[4]] and keyboard.keys[eventArgs[4]] then savepath = savepath .. (string.char(eventArgs[3]) or "") saveCursorX = saveCursorX + 1 shouldRender = true end if shouldRender then gpu.fill(27, resY, resX - 27, 1, " ") gpu.set(27, resY, savepath) gpu.setForeground(0x000000) gpu.setBackground(0xFFFFFF) gpu.set(saveCursorX, resY, " ") gpu.setForeground(0xFFFFFF) gpu.setBackground(0x000000) end until ready gpu.fill(1, resY, resX, 1, " ") gpu.setForeground(0x000000) gpu.setBackground(0xFFFFFF) gpu.set(1, resY, "^X") gpu.set(10, resY, "^S") gpu.setForeground(0xFFFFFF) gpu.setBackground(0x000000) gpu.set(4, resY, "Exit") gpu.set(13, resY, "Save") local text = table.concat(lines, "\n") local handle = fs.open(savepath, "w") handle:write(text) handle:close() file = savepath end -- Initialize screen renderText(0, 0) gpu.setForeground(0x000000) gpu.setBackground(0xFFFFFF) gpu.set(1, resY, "^X") gpu.set(10, resY, "^S") gpu.setForeground(0xFFFFFF) gpu.setBackground(0x000000) gpu.set(4, resY, "Exit") gpu.set(13, resY, "Save") local scrollX = 0 local scrollY = 0 local cursorX = 1 -- Absolute position, not accounting for scrolling local cursorY = 1 local cursorWhite = true local oldTime = computer.uptime() while true do local renderBufferFlag = false -- Flag to render the whole text buffer -- Handle events local previousCursorX -- Used for blackening the previous cursor location when the cursor is moved local previousCursorY coroutine.yield() local eventArgs = {} repeat eventArgs = {event.pull("key_down", 0)} -- The logical solution here for flashing the cursor would be to set the timeout to 0.5, and, if the timeout is reached, change the color. -- However, that makes scrolling freeze the screen up completely. -- Thus, for flashing the cursor, a timer is needed. if computer.uptime() >= oldTime + 0.5 then oldTime = computer.uptime() cursorWhite = not cursorWhite end if next(eventArgs) ~= nil then cursorWhite = true oldTime = computer.uptime() end if eventArgs[1] == "key_down" then -- Mouse events might be added later, that's why this if statement is here if keyboard.getCtrlDown() then -- Special commands if keyboard.keys[eventArgs[4]] == "x" then goto exit end if keyboard.keys[eventArgs[4]] == "s" then save() end end if keyboard.keys[eventArgs[4]] == "up" and cursorY > 1 then if cursorY - scrollY <= 1 then renderBufferFlag = true scrollY = scrollY - 1 else if not previousCursorX and not previousCursorY then previousCursorX = cursorX previousCursorY = cursorY end end cursorY = cursorY - 1 -- The cursor absolute position still has to be moved, even if on-screen it won't move if cursorX > string.len(lines[cursorY]) + 1 then -- cursor snapping, +1 to be 1 char in front of end of line cursorX = string.len(lines[cursorY]) + 1 end end if keyboard.keys[eventArgs[4]] == "down" then if cursorY - scrollY >= resY - 1 then renderBufferFlag = true scrollY = scrollY + 1 else if not previousCursorX and not previousCursorY then previousCursorX = cursorX previousCursorY = cursorY end end cursorY = cursorY + 1 if cursorX > string.len(lines[cursorY]) + 1 then cursorX = string.len(lines[cursorY]) + 1 end end if keyboard.keys[eventArgs[4]] == "left" and cursorX > 1 then if cursorX - scrollX <= 1 then renderBufferFlag = true scrollX = scrollX - 1 else if not previousCursorX and not previousCursorY then previousCursorX = cursorX previousCursorY = cursorY end end cursorX = cursorX - 1 end if keyboard.keys[eventArgs[4]] == "right" and cursorX < string.len(lines[cursorY]) + 1 then if cursorX - scrollX >= resX then renderBufferFlag = true scrollX = scrollX + 1 else if not previousCursorX and not previousCursorY then previousCursorX = cursorX previousCursorY = cursorY end end cursorX = cursorX + 1 end if not keyboard.keys.special[eventArgs[4]] then local line = lines[cursorY] or "" line = string.sub(line, 1, cursorX - 1) .. string.char(eventArgs[3]) .. string.sub(line, cursorX, -1) lines[cursorY] = line cursorX = cursorX + 1 renderBufferFlag = true end if keyboard.keys[eventArgs[4]] == "back" then local line = lines[cursorY] local prevline = lines[cursorY - 1] if cursorX > 1 then line = string.sub(line, 1, cursorX - 2) .. string.sub(line, cursorX, -1) -- remove 1 char elseif prevline then prevline = prevline .. line -- copy line up to the next before removing it ocelot.log(prevline) line = nil end if line then lines[cursorY] = line else removeLine(cursorY) end lines[cursorY - 1] = prevline if not line then cursorY = cursorY - 1 -- this can only trigger if the line is not the first end if cursorX > 1 then cursorX = cursorX - 1 end renderBufferFlag = true end if keyboard.keys[eventArgs[4]] == "enter" then addLine(cursorY + 1) cursorY = cursorY + 1 cursorX = 1 renderBufferFlag = true end end until next(eventArgs) == nil local displayedCursorX = cursorX - scrollX local displayedCursorY = cursorY - scrollY local previousDisplayedCursorX -- What a mouthful local previousDisplayedCursorY if previousCursorX and previousCursorY then previousDisplayedCursorX = previousCursorX - scrollX previousDisplayedCursorY = previousCursorY - scrollY end if renderBufferFlag then renderText(scrollX, scrollY) if cursorWhite then -- If the cursor is black, then there's no need to do anything because there is no cursor after calling renderText(). gpu.setForeground(0x000000) gpu.setBackground(0xFFFFFF) local letter = gpu.get(displayedCursorX, displayedCursorY) gpu.set(displayedCursorX, displayedCursorY, letter) -- TODO: Account for scrolling end else if cursorWhite then if previousCursorX or previousCursorY then -- Remove old cursor gpu.setForeground(0xFFFFFF) gpu.setBackground(0x000000) local letter = gpu.get(previousDisplayedCursorX, previousDisplayedCursorY) gpu.set(previousDisplayedCursorX, previousDisplayedCursorY, letter) end gpu.setForeground(0x000000) gpu.setBackground(0xFFFFFF) local letter = gpu.get(displayedCursorX, displayedCursorY) gpu.set(displayedCursorX, displayedCursorY, letter) else -- If renderText() hasn't been called, the cursor may still be white and need to be turned black. gpu.setForeground(0xFFFFFF) gpu.setBackground(0x000000) local letter = gpu.get(displayedCursorX, displayedCursorY) gpu.set(displayedCursorX, displayedCursorY, letter) end end end -- Cleanup ::exit:: gpu.freeBuffer(textBuffer) gpu.setActiveBuffer(0) terminal.clear()