Files
Halyde/halyde/apps/bedit.lua
T

335 lines
10 KiB
Lua

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
else
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
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()