Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local function split(self, sep)
- if sep == nil then
- sep = "%s"
- end
- local t={} ; i=1
- for str in string.gmatch(self, "([^"..sep.."]+)") do
- t[i] = str
- i = i + 1
- end
- return t
- end
- local letters = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" }
- local lettermap = {}
- for i=1,#letters do
- lettermap[letters[i]] = i
- end
- local function isFunc(input)
- return input:sub(1,1) == "="
- end
- local function letterToNumber(letter)
- return lettermap[letter:lower()]
- end
- local function numberToLetter(number)
- return (letters[number]):upper()
- end
- local function parseFunc(input) --converts "=Funcname lettercodnumbercode" in {"A1", "B2"}, funcname format
- local cells = {}
- input = input:sub(2,#input)
- local tokens = split(input)
- local fname = tokens[1]
- for i=2, #tokens do
- local token = tokens[i]
- local letter = {""}
- local number = {""}
- local range = false
- local comma = false
- for c in token:gmatch(".") do
- --iterate over characters
- if c == "-" then
- range = true
- elseif c == "," then
- comma = true
- elseif tonumber(c) then
- if range or comma then
- number[#number+1] = range and "-" or comma and ","
- number[#number+1] = c
- range = false
- comma = false
- else
- number[#number] = number[#number] .. c
- end
- elseif lettermap[c:lower()] then
- if range or comma then
- letter[#letter+1] = range and "-" or comma and ","
- letter[#letter+1] = c
- range = false
- comma = false
- else
- letter[#letter] = letter[#letter] .. c
- end
- end
- end
- local range, l = false
- local nrange = false
- local function doNumbers()
- for j=1,#number do
- local n = number[j]
- if n == "," then
- --ignore
- nrange = false
- elseif n == "-" then
- nrange = true
- elseif nrange then
- for num = number[j-2] + 1, n do
- cells[#cells+1] = l .. num
- end
- nrange = false
- else
- cells[#cells+1] = l .. n
- nrange = false
- end
- end
- end
- for k=1,#letter do
- l = letter[k]
- if l == "," then
- --ignore
- range = false
- elseif l == "-" then
- range = true
- elseif range then
- local constl = l
- for m = letterToNumber(letter[k-2]) + 1, letterToNumber(constl) do
- l = numberToLetter(m)
- doNumbers()
- end
- range = false
- else
- range = false
- doNumbers()
- end
- end
- end
- return cells, fname
- end
- local function convertParsedToIndexes(parsed) --converts the output of parseFunc to {{1,1},{2,2}} format
- local token, letter, number
- local output = {}
- for i=1,#parsed do
- token = parsed[i]
- letter, number = "",""
- for c in token:gmatch(".") do
- if tonumber(c) then
- number = number .. c
- else
- letter = letter .. c
- end
- end
- output[i] = {letterToNumber(letter), tonumber(number)}
- end
- return output
- end
- local function myRead(oldtxt)
- oldtxt = oldtxt or ""
- term.setCursorBlink(true)
- local pos = #oldtxt+1
- local x,y = term.getCursorPos()
- local event, key
- local keep = true
- local handler = {
- key = {
- [keys.left] = function()
- pos = pos > 1 and pos - 1 or pos
- end;
- [keys.right] = function()
- pos = pos <= #oldtxt and pos + 1 or pos
- end;
- [keys.backspace] = function()
- if pos > 1 then
- oldtxt = oldtxt:sub(1,pos-2) .. oldtxt:sub(pos, #oldtxt)
- pos = pos - 1
- end
- end;
- [keys.delete] = function()
- if pos <= #oldtxt then
- oldtxt = oldtxt:sub(1,pos-1) .. oldtxt:sub(pos+1, #oldtxt)
- end
- end;
- [keys.enter] = function()
- keep = false
- end;
- };
- char = function()
- oldtxt = oldtxt:sub(1,pos-1) .. key .. oldtxt:sub(pos, #oldtxt)
- pos = pos + #key
- end;
- }
- handler.paste = handler.char
- while keep do
- term.setCursorPos(x,y)
- term.clearLine()
- term.write(oldtxt)
- term.setCursorPos(x - 1 + pos, y)
- event, key = os.pullEvent()
- if handler[event] then
- if type(handler[event]) == "table" and handler[event][key] then
- handler[event][key]()
- elseif type(handler[event]) == "function" then
- handler[event]()
- end
- end
- end
- term.setCursorBlink(false)
- return oldtxt
- end
- --Setup vars:
- --general vars
- local filepath = ""
- --display vars
- local scroll = {0,0}
- local showncolumncount = 10
- local w,h = term.getSize()
- local gridw, gridh = w-2, h-2 --why not put this here :P
- local gridx, gridy = 4,2
- local defaultColumnWidth = 10
- --selection vars
- local selected = {1,1} --selected cell position
- local endselection = {}
- local multiselect = false
- --file content vars
- local grid = {}
- local columnWidths = {}
- --copy/paste vars
- local clipboard = {}
- --function vars
- local precalculated = {}
- --theme vars
- local theme = term.isColor() and {
- default = {colors.white, colors.black},
- selected = {colors.green, colors.white},
- multiselected = {colors.lime, colors.black},
- menu = {colors.green, colors.white},
- border = {colors.lightGray, colors.black}
- } or {
- default = {colors.black, colors.white},
- selected = {colors.white, colors.black},
- multiselected = {colors.gray, colors.black},
- menu = {colors.white, colors.black},
- border = {colors.lightGray, colors.black}
- }
- --Handle args:
- local tArgs = {...}
- if #tArgs < 1 then --not enough args
- print[[
- Usage:
- speadersheet <filename>
- ]]
- error("",0)
- end
- filepath = tArgs[1]
- --Local functions:
- --[[
- Speadersheet function functions + formatting
- ]]
- local function mytonumber(input)
- return tonumber(input) or 0
- end
- local spfunctions = {
- ADD = function(currently, nextnum)
- return mytonumber(currently) + mytonumber(nextnum)
- end;
- COPY = function(currently) return currently end;
- SUB = function(currently, nextnum)
- return mytonumber(currently) - mytonumber(nextnum)
- end;
- MUL = function(currently, nextnum)
- return mytonumber(currently) * mytonumber(nextnum)
- end;
- DIV = function(currently, nextnum)
- return mytonumber(currently) / mytonumber(nextnum)
- end;
- POW = function(currently, nextnum)
- return mytonumber(currently) ^ mytonumber(nextnum)
- end;
- COUNT = function(currently, nextnum)
- return mytonumber(currently) + 1
- end;
- }
- local spfuncinit = {
- COUNT = function() return 1 end;
- }
- local function doFunction(name, indexes)
- if spfunctions[name] then
- local func = spfunctions[name]
- local current, output
- for i=1, #indexes do
- local cx,cy = indexes[i][1], indexes[i][2]
- if precalculated[cx] and precalculated[cx][cy] then
- current = precalculated[cx][cy]
- else
- current = grid[cx] and grid[cx][cy] and grid[cx][cy] or ""
- end
- if i > 1 then
- output = func(output, current)
- elseif spfuncinit[name] then
- output = spfuncinit[name](current)
- else
- output = current
- end
- end
- return output
- else
- return "ERR:" .. name
- end
- end
- local function formatText(text, cwidth, x, y)
- if precalculated[x] and precalculated[x][y] then
- text = precalculated[x][y]
- end
- while isFunc(text) or text:sub(1,1) == "{" do
- local parsed, func
- if isFunc(text) then
- parsed, func = parseFunc(text)
- parsed = convertParsedToIndexes(parsed)
- else
- local tbl = textutils.unserialize(text)
- func = tbl[1]
- parsed = tbl[2]
- end
- text = tostring(doFunction(func, parsed))
- if not isFunc(text) and x and y then
- if not precalculated[x] then
- precalculated[x] = {}
- end
- precalculated[x][y] = text
- end
- end
- if #text >= cwidth then
- return text:sub(1,cwidth)
- elseif tonumber(text) then
- return string.rep(" ",(cwidth) - #text) .. text
- else
- return text .. string.rep(" ",(cwidth) - #text)
- end
- end
- --[[
- Other functions
- ]]
- local function selectColors(name) --selects colors, args: name of theme
- local cols = theme[name]
- term.setBackgroundColor(cols[1])
- term.setTextColor(cols[2])
- end
- local function redrawGrid() --redraws grid and bottom line
- --this is the function that will need lots of optimization probably
- local drawing = {scroll[1], scroll[2]} --which cell are we drawing (to get the content)
- for y=gridy, gridh + gridy - 1 do
- drawing[2] = drawing[2] + 1
- drawing[1] = scroll[1]
- term.setCursorPos(gridx, y)
- for i=1,showncolumncount do
- drawing[1] = drawing[1] + 1
- local cwidth = columnWidths[drawing[1]] or defaultColumnWidth
- selectColors((selected[1] == drawing[1] and selected[2] == drawing[2]) and "selected" or (multiselect and (selected[1] >= drawing[1] and selected[2] >= drawing[2]) and (endselection[1] <= drawing[1] and endselection[2] <= drawing[2])) and "multiselected" or "default")
- local realtext = grid[drawing[1]] and grid[drawing[1]][drawing[2]] and grid[drawing[1]][drawing[2]] or ""
- local text = formatText(realtext, cwidth - 1, drawing[1], drawing[2])
- term.write(text)
- if selected[1] == drawing[1] and selected[2] == drawing[2] then
- local oldx,oldy = term.getCursorPos()
- term.setCursorPos(1,h)
- selectColors("menu")
- --This is repeated because of functions later
- term.write(realtext .. (#realtext < w and string.rep(" ",w - #realtext) or ""))
- term.setCursorPos(oldx,oldy)
- end
- selectColors("border")
- term.write(" ")
- end
- end
- end
- local function redrawMenu() --redraws column and row numbers
- --redraw top column numbers
- do
- local drawing = scroll[1]
- term.setCursorPos(gridx, 1)
- selectColors("menu")
- term.clearLine()
- term.setCursorPos(gridx, 1)
- for i=1,showncolumncount do
- drawing = drawing + 1
- local cwidth = columnWidths[drawing] or defaultColumnWidth
- term.write(numberToLetter(drawing))
- term.write(string.rep(" ",cwidth - #numberToLetter(drawing)))
- end
- end
- --redraw line numbers
- local drawing = scroll[2]
- for y=gridy, gridh + gridy - 1 do
- drawing = drawing + 1
- term.setCursorPos(1, y)
- term.write(tostring(drawing) .. string.rep(" ",3 - #tostring(drawing)))
- end
- end
- local function checkScroll() --scrolls if needed
- local scrolled = false
- --horizontal:
- local cx = gridx
- local count = 0
- local drawing = scroll[1]
- for i=1,w do -- count how many columns are on screen
- drawing = drawing + 1
- local cwidth = columnWidths[drawing] or defaultColumnWidth
- cx = cx + cwidth
- count = count + 1
- if cx >= w then break end
- end
- showncolumncount = count
- while selected[1] - scroll[1] < 1 do
- scroll[1] = scroll[1] - 1
- scrolled = true
- end
- while selected[1] > scroll[1] + count do
- scroll[1] = scroll[1] + 1
- scrolled = true
- end
- --vertical:
- while selected[2] - scroll[2] < 1 do
- scroll[2] = scroll[2] - 1
- scrolled = true
- end
- while selected[2] > scroll[2] + gridh do
- scroll[2] = scroll[2] + 1
- scrolled = true
- end
- if scrolled then
- redrawGrid()
- redrawMenu()
- end
- end
- local function save() --saves persistant data
- local h = fs.open(filepath,"w")
- local cont = textutils.serialize{
- grid,
- selected,
- endselection,
- multiselect,
- columnWidths,
- "placeholder",
- scroll
- }
- h.write(cont)
- h.close()
- end
- local function reload() --loads persistant data
- if fs.exists(filepath) then
- local h = fs.open(filepath,"r")
- local cont = textutils.unserialize(h.readAll())
- h.close()
- grid = cont[1] or {}
- selected = cont[2] or {1,1}
- endselection = cont[3] or {1,1}
- multiselect = cont[4]
- columnWidths = cont[5] or {}
- scroll = cont[7] or {0,0}
- end
- end
- local function cancel() --cancels multiselection
- if multiselect then
- multiselect = false
- end
- endselection = {selected[1], selected[2]}
- end
- local function initMultiselect() --inits multiselection if needed
- if not multiselect then
- multiselect = true
- endselection = {selected[1], selected[2]}
- end
- end
- local function convertSelection() -- converts multi/single selection to a smaller sub grid and returns it
- local subx, suby = 1,1
- local subgrid = {}
- for x = endselection[1], selected[1] do
- subgrid[subx] = {}
- for y = endselection[2], selected[2] do
- subgrid[subx][suby] = grid[x] and grid[x][y] and grid[x][y] or ""
- suby = suby + 1
- end
- suby = 1
- subx = subx + 1
- end
- return subgrid
- end
- local function pasteSubgrid(subgrid, x, y) --pastes subgrid in main grid
- for i=#subgrid, 1, -1 do
- local line = subgrid[i]
- for j = #line, 1, -1 do
- local cell = line[j]
- if not grid[x + i - 1] then
- grid[x + i - 1] = {}
- end
- grid[x + i - 1][y + j - 1] = cell
- end
- end
- precalculated = {}
- end
- local function deleteSelection()
- for x = endselection[1], selected[1] do
- for y = endselection[2], selected[2] do
- if grid[x] then
- grid[x][y] = ""
- end
- end
- end
- precalculated = {}
- end
- local function copyCell(bx,by,ex,ey)
- if not grid[ex] then
- grid[ex] = {}
- end
- local txt
- if grid[bx] and grid[bx][by] and isFunc(grid[bx][by]) or grid[bx][by]:sub(1,1) == "{" then
- local result, func
- if isFunc(grid[bx][by]) then
- result, func = parseFunc(grid[bx][by])
- result = convertParsedToIndexes(result)
- else
- func, result = unpack(textutils.unserialize(grid[bx][by]))
- end
- local t
- for i=1, #result do
- t = result[i]
- t[1] = t[1] + ex - bx
- t[2] = t[2] + ey - by
- end
- txt = textutils.serialize {func, result}
- else
- txt = grid[bx] and grid[bx][by] or ""
- end
- grid[ex][ey] = txt
- precalculated = {}
- end
- reload()
- --Key-event table:
- local handler = {
- --[[
- movement:
- arrow keys = move
- p/l = multiselect (move right bottom corner right/down)
- enter = edit
- home = first column
- end = first row
- page up = page up
- page down = page down
- general:
- s = save
- q = exit
- copy/paste:
- c = copy
- v = paste
- x = cut
- n = make column less wide
- m = make column more wide
- u/h/j/k = copy one cell ^/</v/>
- ]]
- key = {
- [keys.up] = function() cancel() selected[2] = selected[2] > 1 and selected[2] - 1 or 1 redrawGrid() end;
- [keys.down] = function() cancel() selected[2] = selected[2] + 1 redrawGrid() end;
- [keys.left] = function() cancel() selected[1] = selected[1] > 1 and selected[1] - 1 or 1 redrawGrid() end;
- [keys.right] = function() cancel() selected[1] = selected[1] + 1 redrawGrid() end;
- [keys.home] = function() cancel() selected[1] = 1 redrawGrid() end;
- [keys["end"]] = function() cancel() selected[2] = 1 redrawGrid() end;
- [keys.pageUp] = function() cancel() selected[2] = selected[2] > gridh-1 and selected[2] - (gridh-1) or 1 redrawGrid() end;
- [keys.pageDown] = function() cancel() selected[2] = selected[2] + gridh - 1 redrawGrid() end;
- [keys.p] = function() initMultiselect() selected[1] = selected[1] + 1 redrawGrid() end;
- [keys.l] = function() initMultiselect() selected[2] = selected[2] + 1 redrawGrid() end;
- [keys.enter] = function()
- term.setCursorPos(1,h)
- selectColors("menu")
- term.clearLine()
- term.setCursorPos(1,h)
- if not grid[selected[1]] then
- grid[selected[1]] = {}
- end
- grid[selected[1]][selected[2]] = myRead(grid[selected[1]][selected[2]] and grid[selected[1]][selected[2]] or "")
- precalculated = {}
- redrawGrid()
- redrawMenu()
- end;
- [keys.s] = function()
- save()
- end;
- [keys.q] = function()
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- term.clear()
- term.setCursorPos(1,1)
- sleep(0)
- error("",0)
- end;
- [keys.c] = function()
- clipboard = convertSelection()
- end;
- [keys.v] = function()
- pasteSubgrid(clipboard, selected[1], selected[2])
- redrawGrid()
- end;
- [keys.x] = function()
- clipboard = convertSelection()
- deleteSelection()
- redrawGrid()
- end;
- [keys.n] = function()
- local neww = (columnWidths[selected[1]] or defaultColumnWidth) - 1
- columnWidths[selected[1]] = neww > 0 and neww or 1
- redrawGrid()
- redrawMenu()
- end;
- [keys.m] = function()
- local neww = (columnWidths[selected[1]] or defaultColumnWidth) + 1
- columnWidths[selected[1]] = neww < gridw and neww or gridw
- redrawGrid()
- redrawMenu()
- end;
- }
- }
- handler.key[keys.u] = function()
- if selected[2] == 1 then return end
- copyCell(selected[1], selected[2], selected[1], selected[2] - 1)
- handler["key"][keys.up]()
- end;
- handler.key[keys.h] = function()
- if selected[1] == 1 then return end
- copyCell(selected[1], selected[2], selected[1] - 1, selected[2])
- handler["key"][keys.left]()
- end;
- handler.key[keys.j] = function()
- copyCell(selected[1], selected[2], selected[1], selected[2] + 1)
- handler["key"][keys.down]()
- end;
- handler.key[keys.k] = function()
- copyCell(selected[1], selected[2], selected[1] + 1, selected[2])
- handler["key"][keys.right]()
- end;
- --Main loop:
- term.clear()
- redrawGrid()
- redrawMenu()
- while true do
- local event,param = os.pullEvent()
- if handler[event] and handler[event][param] then
- handler[event][param]()
- checkScroll()
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement