You'll have to do something called "buffering" the screen.
Basically, you'll have to redirect the terminal API to write to a "buffer". This buffer is most commonly a table of strings which act as the characters on the screen. The buffer also has tables for the text colors and background colors for each of the pixels on the screen.
Here is a buffer file which I've been using to handle windowing:
Pixel
Spoiler
--[[
Pixel Trystan Cannon
17 July 2013
Contains the Pixel class which couples with
the Screen Buffer class in order to simulate
a buffer for concurrently running programs
properly.
]]
-- Variables -------------------------------------------
local pixelMetatable = { __index = getfenv (1) }
-- Variables -------------------------------------------
function new (textColor, backColor, character)
return setmetatable ({
character = character or ' ',
textColor = textColor or colors.white,
backColor = backColor or colors.black
}, pixelMetatable)
end
Buffer
Spoiler
--[[
Screen Buffer Trystan Cannon
17 July 2013
Contains the Screen Buffer class which contains
the accurate behavior for a buffer when working
with Windows or multiple threads which require
their own buffer to read and write from/to when
running concurrently.
]]
-- Variables -------------------------------------------
local bufferMetatable = { __index = getfenv (1) }
-- Variables -------------------------------------------
-- Returns a new 1d table of pixels with the given width and colors.
-- The colors, by default, will be white and black.
local function newPixelLine (width, textColor, backColor)
local pixelLine = {}
for space = 1, width do
pixelLine[space] = Pixel.new (textColor, backColor)
end
return pixelLine
end
-- Returns a new 2d table of pixels with the given dimensions and colors.
-- The colors, by default, will be white and black.
local function newPixelTable (width, height, textColor, backColor)
local pixels = {}
for lineNumber = 1, height do
pixels[lineNumber] = newPixelLine (width, textColor, backColor)
end
return pixels
end
-- Returns a table which will allow us to redirect the terminal to use
-- the buffer as a redirection object.
function getRedirectTable (buffer)
local redirect = {}
redirect.getSize = function()
return buffer.width, buffer.height
end
redirect.getCursorPos = function()
return buffer.cursorX, buffer.cursorY
end
redirect.setCursorPos = function (x, y)
buffer.cursorX = math.floor (tostring (x) ~= "nan" and tonumber (x) or buffer.cursorX)
buffer.cursorY = math.floor (tostring (y) ~= "nan" and tonumber (y) or buffer.cursorY)
-- Before setting the position of the cursor, make sure that the buffer is visible and the cursor
-- is within bounds of the buffer's borders.
if buffer.isVisible then
term.native.setCursorPos (buffer.cursorX + buffer.xPos - 1, buffer.cursorY + buffer.yPos - 1)
end
end
redirect.setCursorBlink = function (shouldCursorBlink)
buffer.shouldCursorBlink = shouldCursorBlink
if buffer.isVisible then
term.native.setCursorBlink (shouldCursorBlink)
end
end
redirect.isColor = function()
return buffer.isColor
end
redirect.setTextColor = function (color)
buffer.textColor = color
if buffer.isVisible then
return term.native.setTextColor (color)
end
end
redirect.setBackgroundColor = function (color)
buffer.backColor = color
if buffer.isVisible then
return term.native.setBackgroundColor (color)
end
end
redirect.isColour = redirect.isColor
redirect.setTextColour = redirect.setTextColor
redirect.setBackgroundColour = redirect.setBackgroundColor
redirect.clearLine = function (lineNumber)
lineNumber = lineNumber or buffer.cursorY
buffer.pixels[buffer.cursorY] = newPixelLine (buffer.width, buffer.textColor, buffer.backColor)
-- Set the background color to that of the buffer so the colors match when cleared.
if buffer.isVisible then
term.native.setBackgroundColor (buffer.backColor)
for space = 1, buffer.width do
-- Only clear the space that the buffer is specified to take up on the screen.
term.native.setCursorPos (space + buffer.xPos - 1, lineNumber + buffer.yPos - 1)
term.native.write (string.rep (' ', buffer.width))
end
-- Reset the native cursor after clearing the line.
term.native.setCursorPos (buffer.cursorX + buffer.xPos - 1, buffer.cursorY + buffer.yPos - 1)
end
end
redirect.clear = function()
buffer.pixels = newPixelTable (buffer.width, buffer.height, buffer.textColor, buffer.backColor)
local currentCursorY = buffer.cursorY
for lineNumber = 1, buffer.height do
redirect.clearLine (lineNumber)
end
end
redirect.scroll = function (timesToScroll)
if timesToScroll > 0 then
for timesScrolled = 1, timesToScroll do
for lineNumber = 1, buffer.height - 1 do
buffer.pixels[lineNumber] = buffer.pixels[lineNumber + 1]
buffer.pixels[lineNumber + 1] = newPixelLine (buffer.width, buffer.textColor, buffer.backColor)
end
end
elseif timesToScroll < 0 then
for timesScrolled = 1, math.abs (timesToScroll) do
for lineNumber = buffer.height, 2, -1 do
buffer.pixels[lineNumber] = buffer.pixels[lineNumber - 1]
buffer.pixels[lineNumber - 1] = newPixelLine (buffer.width, buffer.textColor, buffer.backColor)
end
end
end
-- If the buffer has a height of one, then the previous code won't have affected the buffer.
-- Therefore, we should just set the only line in the buffer to a blank line of pixels.
if buffer.height == 1 then
buffer.pixels[buffer.height] = newPixelLine (buffer.width, buffer.textColor, buffer.backColor)
end
-- Render the buffer now that the buffer has been scrolled.
-- This will scroll the buffer on the screen without scrolling the entire terminal.
if buffer.isVisible then
buffer:render()
end
end
redirect.write = function (text)
-- Remove escape sequences (control characers) from the string
-- just as term.write would.
text = tostring (text):gsub ("\t", " ")
text = text:gsub ("%c", "?")
-- Make sure that the cursor is in bounds.
-- Truncate the string as necessary.
if buffer.cursorX > buffer.width or buffer.cursorY < 1 or buffer.cursorY > buffer.height then
buffer.cursorX = buffer.cursorX + text:len()
return
end
if buffer.cursorX < 1 then
local textLength = text:len()
text = text:sub (math.abs (buffer.cursorX) + 2)
-- If the cursor still won't reach the inside of the buffer even after truncation,
-- just update the buffer's cursor and don't attempt to write anything.
if text:len() == 0 then
buffer.cursorX = buffer.cursorX + textLength
return
end
buffer.cursorX = 1
end
if buffer.cursorX + text:len() - 1 > buffer.width then
text = text:sub (1, buffer.width - buffer.cursorX + 1)
end
-- Write the text to the native terminal.
if buffer.isVisible then
term.native.setTextColor (buffer.textColor)
term.native.setBackgroundColor (buffer.backColor)
term.native.setCursorPos (buffer.cursorX + buffer.xPos - 1, buffer.cursorY + buffer.yPos - 1)
term.native.write (text)
end
-- Write the text to the buffer character for character into pixels.
for character in text:gmatch ('.') do
buffer.pixels[buffer.cursorY][buffer.cursorX] = Pixel.new (buffer.textColor, buffer.backColor, character)
buffer.cursorX = buffer.cursorX + 1
end
end
return redirect
end
-- Renders the given buffer and returns the terminal's colors and cursor
-- back to their previous states (before rendering).
function render (buffer)
term.native.setCursorBlink (false)
-- Define the current colors for drawing pixels without disturbing
-- the buffers current colors. This way, we can check if we need to
-- change the colors already set in order to draw the pixel accurately.
local currentTextColor = buffer.textColor
local currentBackColor = buffer.backColor
-- Set the native terminal colors to the current colors that we defined above.
term.native.setTextColor (currentTextColor)
term.native.setBackgroundColor (currentBackColor)
-- Iterate over the 2d pixel table of the buffer and draw all of the pixels
-- in it.
for lineNumber = 1, buffer.height do
for space = 1, buffer.width do
local pixel = buffer.pixels[lineNumber][space]
-- Try to avoid setting the text and background colors if
-- the current text and or background color is already that of
-- the pixel.
if currentTextColor ~= pixel.textColor then
currentTextColor = pixel.textColor
term.native.setTextColor (currentTextColor)
end
if currentBackColor ~= pixel.backColor then
currentBackColor = pixel.backColor
term.native.setBackgroundColor (currentBackColor)
end
-- Position the cursor properly for the buffer and draw the pixel for the iteration.
term.native.setCursorPos (space + buffer.xPos - 1, lineNumber + buffer.yPos - 1)
term.native.write (pixel.character)
end
end
-- Return the state of the terminal to what it was before we rendered
-- the buffer.
term.native.setTextColor (buffer.textColor)
term.native.setBackgroundColor (buffer.backColor)
-- Make sure that the cursor is in bounds of the buffer and that the buffer is visible before setting the position and blink state to that
-- of the the cursor to that of the buffer.
if buffer.isVisible and buffer.cursorX + buffer.xPos - 1 >= buffer.xPos and buffer.cursorX + buffer.xPos - 1 <= buffer.xPos + buffer.width and
buffer.cursorY + buffer.yPos - 1 >= buffer.yPos and buffer.cursorY + buffer.yPos - 1 <= buffer.yPos + buffer.height then
term.native.setCursorPos (buffer.cursorX + buffer.xPos - 1, buffer.cursorY + buffer.yPos - 1)
term.native.setCursorBlink (buffer.shouldCursorBlink)
-- Since the cursor will end up at the lower right hand corner of the window on the screen, we should turn off the cursor
-- blink so that it doesn't look like the cursor is actually in the lower right corner of the window.
else
term.native.setCursorBlink (false)
end
end
-- Constructs and returns a new Buffer object.
function new (width, height, xPos, yPos, isVisible, isColor)
if isVisible == nil then
isVisible = true
end
if isColor == nil then
isColor = true
end
local this = {
width = width,
height = height,
textColor = colors.white,
backColor = colors.black,
xPos = xPos or 1,
yPos = yPos or 1,
cursorX = 1,
cursorY = 1,
shouldCursorBlink = true,
isVisible = isVisible,
isColor = isColor,
pixels = newPixelTable (width, height)
}
setmetatable (this, bufferMetatable)
return this
end
If you're a stickler for memory management (which you should be :P/>), you could use strings for all of the lines, text color, and background colors. However, you'll need to serialize the colors and write similar functions for the terminal API to write to your buffer and still behave properly.