| local cursorX, cursorY = 1, 1 |
| local cursorBlink = nil |
| |
| local function toggleBlink() |
| cursorBlink.state = not cursorBlink.state |
| if term.isAvailable() then |
| local char = cursorBlink.state and cursorBlink.solid or cursorBlink.alt |
| gpu.set(cursorX, cursorY, char) |
| end |
| end |
| |
| ------------------------------------------------------------------------------- |
| |
| term = {} |
| |
| function term.isAvailable() |
| return component.isAvailable("gpu") and component.isAvailable("screen") |
| end |
| |
| function term.clear() |
| if term.isAvailable() then |
| local w, h = gpu.resolution() |
| gpu.fill(1, 1, w, h, " ") |
| end |
| cursorX, cursorY = 1, 1 |
| end |
| |
| function term.clearLine() |
| if term.isAvailable() then |
| local w = gpu.resolution() |
| gpu.fill(1, cursorY, w, 1, " ") |
| end |
| cursorX = 1 |
| end |
| |
| function term.cursor(col, row) |
| if col and row then |
| local w, h = gpu.resolution() |
| cursorX = math.min(math.max(col, 1), w) |
| cursorY = math.min(math.max(row, 1), h) |
| end |
| return cursorX, cursorY |
| end |
| |
| function term.cursorBlink(enabled) |
| local function start(alt) |
| if not cursorBlink then |
| cursorBlink = event.interval(0.5, toggleBlink) |
| cursorBlink.state = false |
| cursorBlink.solid = string.char(0x2588) -- 0x2588 is a solid block. |
| elseif cursorBlink.state then |
| toggleBlink() |
| end |
| cursorBlink.alt = alt |
| end |
| local function stop() |
| event.cancel(cursorBlink.id) |
| if cursorBlink.state then |
| toggleBlink() |
| end |
| cursorBlink = nil |
| end |
| if type(enabled) == "boolean" and enabled ~= (cursorBlink ~= nil) then |
| if enabled then |
| start(" ") |
| else |
| stop() |
| end |
| elseif type(enabled) == "string" and |
| (not cursorBlink or enabled:sub(1, 1) ~= cursorBlink.alt) |
| then |
| if enabled:len() > 0 then |
| start(enabled:sub(1, 1)) |
| else |
| stop() |
| end |
| end |
| return cursorBlink ~= nil |
| end |
| |
| ------------------------------------------------------------------------------- |
| |
| function term.read(history) |
| history = history or {} |
| table.insert(history, "") |
| local current = #history |
| local keys = driver.keyboard.keys |
| local start, y = term.cursor() |
| local cursor, scroll = 1, 0 |
| local keyRepeat = nil |
| local result = nil |
| local function remove() |
| local x = start - 1 + cursor - scroll |
| local w = gpu.resolution() |
| gpu.copy(x + 1, y, w - x, 1, -1, 0) |
| local cursor = cursor + (w - x) |
| local char = history[current]:sub(cursor, cursor) |
| if char:len() == 0 then |
| char = " " |
| end |
| gpu.set(w, y, char) |
| end |
| local function render() |
| local w = gpu.resolution() |
| local str = history[current]:sub(1 + scroll, 1 + scroll + w - (start - 1)) |
| str = str .. string.rep(" ", (w - (start - 1)) - str:len()) |
| gpu.set(start, y, str) |
| end |
| local function scrollEnd() |
| local w = gpu.resolution() |
| cursor = history[current]:len() + 1 |
| scroll = math.max(0, cursor - (w - (start - 1))) |
| render() |
| end |
| local function scrollLeft() |
| scroll = scroll - 1 |
| local w = gpu.resolution() |
| gpu.copy(start, y, w - start - 1, 1, 1, 0) |
| local cursor = w - (start - 1) + scroll |
| local char = history[current]:sub(cursor, cursor) |
| if char:len() == 0 then |
| char = " " |
| end |
| gpu.set(1, y, char) |
| end |
| local function scrollRight() |
| scroll = scroll + 1 |
| local w = gpu.resolution() |
| gpu.copy(start + 1, y, w - start, 1, -1, 0) |
| local cursor = w - (start - 1) + scroll |
| local char = history[current]:sub(cursor, cursor) |
| if char:len() == 0 then |
| char = " " |
| end |
| gpu.set(w, y, char) |
| end |
| local function update() |
| local w = gpu.resolution() |
| local cursor = cursor - 1 |
| local x = start - 1 + cursor - scroll |
| if cursor < history[current]:len() then |
| gpu.copy(x, y, w - x, 1, 1, 0) |
| end |
| gpu.set(x, y, history[current]:sub(cursor, cursor)) |
| end |
| local function copyIfNecessary() |
| if current ~= #history then |
| history[#history] = history[current] |
| current = #history |
| end |
| end |
| local function updateCursor() |
| term.cursor(start - 1 + cursor - scroll, y) |
| term.cursorBlink(cursor <= history[current]:len() and |
| history[current]:sub(cursor, cursor) or " ") |
| end |
| local function handleKeyPress(char, code) |
| if not term.isAvailable() then return end |
| local w, h = gpu.resolution() |
| local cancel = false |
| term.cursorBlink(false) |
| if code == keys.back then |
| if cursor > 1 then |
| copyIfNecessary() |
| history[current] = history[current]:sub(1, cursor - 2) .. |
| history[current]:sub(cursor) |
| cursor = cursor - 1 |
| if cursor - scroll < 1 then |
| scrollLeft() |
| end |
| remove() |
| end |
| cancel = cursor == 1 |
| elseif code == keys.delete then |
| if cursor <= history[current]:len() then |
| copyIfNecessary() |
| history[current] = history[current]:sub(1, cursor - 1) .. |
| history[current]:sub(cursor + 1) |
| remove() |
| end |
| cancel = cursor == history[current]:len() + 1 |
| elseif code == keys.left then |
| if cursor > 1 then |
| cursor = cursor - 1 |
| if cursor - scroll < 1 then |
| scrollLeft() |
| end |
| end |
| cancel = cursor == 1 |
| elseif code == keys.right then |
| if cursor < history[current]:len() + 1 then |
| cursor = cursor + 1 |
| if cursor - scroll > w - (start - 1) then |
| scrollRight() |
| end |
| end |
| cancel = cursor == history[current]:len() + 1 |
| elseif code == keys.home then |
| if cursor > 1 then |
| cursor, scroll = 1, 0 |
| render() |
| end |
| cancel = true |
| elseif code == keys["end"] then |
| if cursor < history[current]:len() + 1 then |
| scrollEnd() |
| end |
| cancel = true |
| elseif code == keys.up then |
| if current > 1 then |
| current = current - 1 |
| scrollEnd() |
| end |
| cancel = current == 1 |
| elseif code == keys.down then |
| if current < #history then |
| current = current + 1 |
| scrollEnd() |
| end |
| cancel = current == #history |
| elseif code == keys.enter then |
| if current ~= #history then -- bring entry to front |
| history[#history] = history[current] |
| table.remove(history, current) |
| current = #history |
| end |
| result = history[current] .. "\n" |
| if history[current]:len() == 0 then |
| table.remove(history, current) |
| end |
| return true |
| elseif not keys.isControl(char) then |
| copyIfNecessary() |
| history[current] = history[current]:sub(1, cursor - 1) .. |
| string.char(char) .. |
| history[current]:sub(cursor) |
| cursor = cursor + 1 |
| update() |
| if cursor - scroll > w - (start - 1) then |
| scrollRight() |
| end |
| end |
| updateCursor() |
| return cancel |
| end |
| local function onKeyDown(_, address, char, code) |
| if not component.isAvailable("keyboard") or |
| address ~= component.primary("keyboard") |
| then |
| return |
| end |
| if keyRepeat then |
| keyRepeat = event.cancel(keyRepeat) |
| end |
| if not handleKeyPress(char, code) then |
| local function onRepeatTimer() |
| if not handleKeyPress(char, code) then |
| keyRepeat = event.timer(0, onRepeatTimer) |
| end |
| end |
| keyRepeat = event.timer(0.4, onRepeatTimer) |
| end |
| end |
| local function onKeyUp(_, address, char, code) |
| if not component.isAvailable("keyboard") or |
| address ~= component.primary("keyboard") |
| then |
| return |
| end |
| if keyRepeat then |
| keyRepeat = event.cancel(keyRepeat) |
| end |
| end |
| local function onClipboard(_, address, value) |
| if not component.isAvailable("keyboard") or |
| address ~= component.primary("keyboard") |
| then |
| return |
| end |
| copyIfNecessary() |
| term.cursorBlink(false) |
| local l = value:find("\n", 1, true) |
| if l then |
| history[current] = history[current] .. value:sub(1, l - 1) |
| result = history[current] .. "\n" |
| else |
| history[current] = history[current] .. value |
| scrollEnd() |
| updateCursor() |
| end |
| end |
| event.listen("key_down", onKeyDown) |
| event.listen("key_up", onKeyUp) |
| event.listen("clipboard", onClipboard) |
| term.cursorBlink(true) |
| while term.isAvailable() and not result do |
| event.wait() |
| end |
| if history[#history] == "" then |
| table.remove(history) |
| end |
| if keyRepeat then |
| event.cancel(keyRepeat) |
| end |
| event.ignore("key_down", onKeyDown) |
| event.ignore("key_up", onKeyUp) |
| event.ignore("clipboard", onClipboard) |
| term.cursorBlink(false) |
| print() |
| return result |
| end |
| |
| function term.write(value, wrap) |
| value = tostring(value) |
| if value:len() == 0 or not term.isAvailable() then |
| return |
| end |
| value = value:gsub("\t", " ") |
| local w, h = gpu.resolution() |
| local function checkCursor() |
| if cursorX > w then |
| cursorX = 1 |
| cursorY = cursorY + 1 |
| end |
| if cursorY > h then |
| gpu.copy(1, 1, w, h, 0, -1) |
| gpu.fill(1, h, w, 1, " ") |
| cursorY = h |
| end |
| end |
| for line, nl in value:gmatch("([^\r\n]*)([\r\n]?)") do |
| while wrap and line:len() > w - cursorX + 1 do |
| local partial = line:sub(1, w - cursorX + 1) |
| line = line:sub(partial:len() + 1) |
| gpu.set(cursorX, cursorY, partial) |
| cursorX = cursorX + partial:len() |
| checkCursor() |
| end |
| if line:len() > 0 then |
| gpu.set(cursorX, cursorY, line) |
| cursorX = cursorX + line:len() |
| end |
| if nl:len() == 1 then |
| cursorX = 1 |
| cursorY = cursorY + 1 |
| checkCursor() |
| end |
| end |
| end |
| |
| ------------------------------------------------------------------------------- |
| |
| local function onComponentAvailable(_, componentType) |
| if (componentType == "gpu" and component.isAvailable("screen")) or |
| (componentType == "screen" and component.isAvailable("gpu")) |
| then |
| event.fire("term_available") |
| end |
| end |
| |
| local function onComponentUnavailable(_, componentType) |
| if (componentType == "gpu" and component.isAvailable("screen")) or |
| (componentType == "screen" and component.isAvailable("gpu")) |
| then |
| event.fire("term_unavailable") |
| end |
| end |
| |
| function term.install() |
| event.listen("component_available", onComponentAvailable) |
| event.listen("component_unavailable", onComponentUnavailable) |
| end |
| |
| function term.uninstall() |
| event.ignore("component_available", onComponentAvailable) |
| event.ignore("component_unavailable", onComponentUnavailable) |
| end |