mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-14 09:46:53 -04:00
Merge branch 'next-term' of https://github.com/payonel/OpenComputers into master-MC1.7.10
This commit is contained in:
commit
c7931a76ab
@ -1,7 +1,7 @@
|
||||
local shell = require("shell")
|
||||
local args, options = shell.parse(...)
|
||||
|
||||
local ec = 0
|
||||
local ec, error_prefix = 0, "alias:"
|
||||
|
||||
if options.help then
|
||||
print(string.format("Usage: alias: [name[=value] ... ]", cmd_name))
|
||||
|
@ -1,16 +1,12 @@
|
||||
local event = require "event"
|
||||
local term = require "term"
|
||||
local keyboard = require "keyboard"
|
||||
|
||||
local args = {...}
|
||||
|
||||
local gpu = term.gpu()
|
||||
local interactive = io.output().tty
|
||||
local function gpu()
|
||||
return select(2, term.getGPU())
|
||||
end
|
||||
local color, isPal, evt
|
||||
if interactive then
|
||||
color, isPal = gpu().getForeground()
|
||||
color, isPal = gpu.getForeground()
|
||||
end
|
||||
io.write("Press 'Ctrl-C' to exit\n")
|
||||
pcall(function()
|
||||
@ -20,13 +16,13 @@ pcall(function()
|
||||
else
|
||||
evt = table.pack(event.pull())
|
||||
end
|
||||
if interactive then gpu().setForeground(0xCC2200) end
|
||||
if interactive then gpu.setForeground(0xCC2200) end
|
||||
io.write("[" .. os.date("%T") .. "] ")
|
||||
if interactive then gpu().setForeground(0x44CC00) end
|
||||
if interactive then gpu.setForeground(0x44CC00) end
|
||||
io.write(tostring(evt[1]) .. string.rep(" ", math.max(10 - #tostring(evt[1]), 0) + 1))
|
||||
if interactive then gpu().setForeground(0xB0B00F) end
|
||||
if interactive then gpu.setForeground(0xB0B00F) end
|
||||
io.write(tostring(evt[2]) .. string.rep(" ", 37 - #tostring(evt[2])))
|
||||
if interactive then gpu().setForeground(0xFFFFFF) end
|
||||
if interactive then gpu.setForeground(0xFFFFFF) end
|
||||
if evt.n > 2 then
|
||||
for i = 3, evt.n do
|
||||
io.write(" " .. tostring(evt[i]))
|
||||
@ -37,6 +33,6 @@ pcall(function()
|
||||
until evt[1] == "interrupted"
|
||||
end)
|
||||
if interactive then
|
||||
gpu().setForeground(color, isPal)
|
||||
gpu.setForeground(color, isPal)
|
||||
end
|
||||
|
||||
|
@ -9,12 +9,7 @@ local unicode = require("unicode")
|
||||
if not term.isAvailable() then
|
||||
return
|
||||
end
|
||||
|
||||
local function gpu()
|
||||
return select(2, term.getGPU())
|
||||
end
|
||||
|
||||
|
||||
local gpu = term.gpu()
|
||||
local args, options = shell.parse(...)
|
||||
if #args == 0 then
|
||||
io.write("Usage: edit <filename>")
|
||||
@ -121,7 +116,7 @@ local function setStatus(value)
|
||||
local x, y, w, h = term.getGlobalArea()
|
||||
value = unicode.wlen(value) > w - 10 and unicode.wtrunc(value, w - 9) or value
|
||||
value = text.padRight(value, w - 10)
|
||||
gpu().set(x, y + h - 1, value)
|
||||
gpu.set(x, y + h - 1, value)
|
||||
end
|
||||
|
||||
local function getArea()
|
||||
@ -170,7 +165,7 @@ local function drawLine(x, y, w, h, lineNr)
|
||||
local str = removePrefix(buffer[lineNr] or "", scrollX)
|
||||
str = unicode.wlen(str) > w and unicode.wtrunc(str, w + 1) or str
|
||||
str = text.padRight(str, w)
|
||||
gpu().set(x, y - 1 + lineNr - scrollY, str)
|
||||
gpu.set(x, y - 1 + lineNr - scrollY, str)
|
||||
end
|
||||
end
|
||||
|
||||
@ -204,7 +199,7 @@ local function setCursor(nbx, nby)
|
||||
local dy = math.abs(scrollY - sy)
|
||||
scrollY = sy
|
||||
if h > dy then
|
||||
gpu().copy(x, y + dy, w, h - dy, 0, -dy)
|
||||
gpu.copy(x, y + dy, w, h - dy, 0, -dy)
|
||||
end
|
||||
for lineNr = nby - (math.min(dy, h) - 1), nby do
|
||||
drawLine(x, y, w, h, lineNr)
|
||||
@ -215,7 +210,7 @@ local function setCursor(nbx, nby)
|
||||
local dy = math.abs(scrollY - sy)
|
||||
scrollY = sy
|
||||
if h > dy then
|
||||
gpu().copy(x, y, w, h - dy, 0, dy)
|
||||
gpu.copy(x, y, w, h - dy, 0, dy)
|
||||
end
|
||||
for lineNr = nby, nby + (math.min(dy, h) - 1) do
|
||||
drawLine(x, y, w, h, lineNr)
|
||||
@ -242,7 +237,7 @@ local function setCursor(nbx, nby)
|
||||
term.setCursor(nbx - scrollX, nby - scrollY)
|
||||
--update with term lib
|
||||
nbx, nby = getCursor()
|
||||
gpu().set(x + w - 10, y + h, text.padLeft(string.format("%d,%d", nby, nbx), 10))
|
||||
gpu.set(x + w - 10, y + h, text.padLeft(string.format("%d,%d", nby, nbx), 10))
|
||||
end
|
||||
|
||||
local function highlight(bx, by, length, enabled)
|
||||
@ -252,21 +247,21 @@ local function highlight(bx, by, length, enabled)
|
||||
cy = math.max(1, math.min(h, cy))
|
||||
length = math.max(1, math.min(w - cx, length))
|
||||
|
||||
local fg, fgp = gpu().getForeground()
|
||||
local bg, bgp = gpu().getBackground()
|
||||
local fg, fgp = gpu.getForeground()
|
||||
local bg, bgp = gpu.getBackground()
|
||||
if enabled then
|
||||
gpu().setForeground(bg, bgp)
|
||||
gpu().setBackground(fg, fgp)
|
||||
gpu.setForeground(bg, bgp)
|
||||
gpu.setBackground(fg, fgp)
|
||||
end
|
||||
local indexFrom = lengthToChars(buffer[by], bx)
|
||||
local value = unicode.sub(buffer[by], indexFrom)
|
||||
if unicode.wlen(value) > length then
|
||||
value = unicode.wtrunc(value, length + 1)
|
||||
end
|
||||
gpu().set(x - 1 + cx, y - 1 + cy, value)
|
||||
gpu.set(x - 1 + cx, y - 1 + cy, value)
|
||||
if enabled then
|
||||
gpu().setForeground(fg, fgp)
|
||||
gpu().setBackground(bg, bgp)
|
||||
gpu.setForeground(fg, fgp)
|
||||
gpu.setBackground(bg, bgp)
|
||||
end
|
||||
end
|
||||
|
||||
@ -336,7 +331,7 @@ local function delete(fullRow)
|
||||
local content = table.remove(buffer, row)
|
||||
local rcy = cy + (row - cby)
|
||||
if rcy <= h then
|
||||
gpu().copy(x, y + rcy, w, h - rcy, 0, -1)
|
||||
gpu.copy(x, y + rcy, w, h - rcy, 0, -1)
|
||||
drawLine(x, y, w, h, row + (h - rcy))
|
||||
end
|
||||
return content
|
||||
@ -347,7 +342,7 @@ local function delete(fullRow)
|
||||
deleteRow(cby)
|
||||
else
|
||||
buffer[cby] = ""
|
||||
gpu().fill(x, y - 1 + cy, w, 1, " ")
|
||||
gpu.fill(x, y - 1 + cy, w, 1, " ")
|
||||
end
|
||||
setCursor(1, cby)
|
||||
elseif cbx <= unicode.wlen(line()) then
|
||||
@ -395,7 +390,7 @@ local function enter()
|
||||
drawLine(x, y, w, h, cby)
|
||||
if cy < h then
|
||||
if cy < h - 1 then
|
||||
gpu().copy(x, y + cy, w, h - (cy + 1), 0, 1)
|
||||
gpu.copy(x, y + cy, w, h - (cy + 1), 0, 1)
|
||||
end
|
||||
drawLine(x, y, w, h, cby + 1)
|
||||
end
|
||||
@ -434,10 +429,9 @@ local function find()
|
||||
term.setCursor(7 + unicode.wlen(findText), h + 1)
|
||||
setStatus("Find: " .. findText)
|
||||
|
||||
local _, address, char, code = event.pull("key_down")
|
||||
local inFocus, screenAddress, keyboardAddress = term.hasFocus()
|
||||
if inFocus and address == keyboardAddress then
|
||||
local handler, name = getKeyBindHandler(code, keyboardAddress)
|
||||
local _, address, char, code = term.pull("key_down")
|
||||
if address == term.keyboard().address then
|
||||
local handler, name = getKeyBindHandler(code)
|
||||
highlight(cbx, cby, unicode.wlen(findText), false)
|
||||
if name == "newline" then
|
||||
break
|
||||
@ -547,7 +541,7 @@ local keyBindHandlers = {
|
||||
findnext = find
|
||||
}
|
||||
|
||||
getKeyBindHandler = function(code, keyboardAddress)
|
||||
getKeyBindHandler = function(code)
|
||||
if type(config.keybinds) ~= "table" then return end
|
||||
-- Look for matches, prefer more 'precise' keybinds, e.g. prefer
|
||||
-- ctrl+del over del.
|
||||
@ -563,6 +557,7 @@ getKeyBindHandler = function(code, keyboardAddress)
|
||||
elseif value == "shift" then shift = true
|
||||
else key = value end
|
||||
end
|
||||
local keyboardAddress = term.keyboard().address
|
||||
if (not alt or keyboard.isAltDown(keyboardAddress)) and
|
||||
(not control or keyboard.isControlDown(keyboardAddress)) and
|
||||
(not shift or keyboard.isShiftDown(keyboardAddress)) and
|
||||
@ -582,8 +577,8 @@ end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local function onKeyDown(char, code, keyboardAddress)
|
||||
local handler = getKeyBindHandler(code, keyboardAddress)
|
||||
local function onKeyDown(char, code)
|
||||
local handler = getKeyBindHandler(code)
|
||||
if handler then
|
||||
handler()
|
||||
elseif readonly and code == keyboard.keys.q then
|
||||
@ -660,12 +655,11 @@ do
|
||||
end
|
||||
|
||||
while running do
|
||||
local event, address, arg1, arg2, arg3 = event.pull()
|
||||
local inFocus, screenAddress, keyboardAddress = term.hasFocus()
|
||||
if inFocus and (address == screenAddress or address == keyboardAddress) then
|
||||
local event, address, arg1, arg2, arg3 = term.pull()
|
||||
if address == term.keyboard().address or address == term.screen().address then
|
||||
local blink = true
|
||||
if event == "key_down" then
|
||||
onKeyDown(arg1, arg2, keyboardAddress)
|
||||
onKeyDown(arg1, arg2)
|
||||
elseif event == "clipboard" and not readonly then
|
||||
onClipboard(arg1)
|
||||
elseif event == "touch" or event == "drag" then
|
||||
@ -682,7 +676,6 @@ while running do
|
||||
end
|
||||
if blink then
|
||||
term.setCursorBlink(true)
|
||||
term.setCursorBlink(true) -- force toggle to caret
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -14,9 +14,7 @@ local term = require("term")
|
||||
|
||||
local args, options = shell.parse(...)
|
||||
|
||||
local function gpu()
|
||||
return select(2, term.getGPU())
|
||||
end
|
||||
local gpu = term.gpu()
|
||||
|
||||
local function printUsage(ostream, msg)
|
||||
local s = ostream or io.stdout
|
||||
@ -111,8 +109,8 @@ local print_count = pop('c','count')
|
||||
local colorize = pop('color','colour') and io.output().tty and term.isAvailable()
|
||||
|
||||
local noop = function(...)return ...;end
|
||||
local setc = colorize and gpu().setForeground or noop
|
||||
local getc = colorize and gpu().getForeground or noop
|
||||
local setc = colorize and gpu.setForeground or noop
|
||||
local getc = colorize and gpu.getForeground or noop
|
||||
|
||||
local trim = pop('t','trim')
|
||||
local trim_front = trim and function(s)return s:gsub('^%s+','')end or noop
|
||||
|
@ -29,7 +29,7 @@ For more info run: man ls]])
|
||||
end
|
||||
|
||||
local ec = 0
|
||||
local gpu = select(2, term.getGPU())
|
||||
local gpu = term.gpu()
|
||||
local fOut = term.isAvailable() and io.output().tty
|
||||
local function perr(msg) io.stderr:write(msg,"\n") ec = 2 end
|
||||
local function _path(n,i) return n[i]:sub(1, 1) == '/' and "" or n.path end
|
||||
@ -53,7 +53,7 @@ if fOut and not ops["no-color"] then
|
||||
LSC = require("serialization").unserialize(LSC)
|
||||
end
|
||||
if not LSC then
|
||||
perr("ls: unparsable value for LSC environment variable")
|
||||
perr("ls: unparsable value for LS_COLORS environment variable")
|
||||
else
|
||||
prev_color = gpu.getForeground()
|
||||
restore_color = function() gpu.setForeground(prev_color) end
|
||||
@ -215,12 +215,12 @@ local function display(n)
|
||||
end
|
||||
return max
|
||||
end
|
||||
local function measure()
|
||||
local function measure(_cols)
|
||||
local t=0
|
||||
for c=1,cols do t=t+max_name(c)+(c>1 and 2 or 0) end
|
||||
for c=1,_cols do t=t+max_name(c)+(c>1 and 2 or 0) end
|
||||
return t
|
||||
end
|
||||
repeat d=d+1 cols=math.ceil(#n/d) until d>=#n or measure()<w
|
||||
while d<#n do d=d+1 cols=math.ceil(#n/d) if measure(cols)<w then break end end
|
||||
lines.n=d
|
||||
mt.__index=function(tbl,di)return setmetatable({},{
|
||||
__len=function()return cols end,
|
||||
|
@ -1,12 +1,8 @@
|
||||
local package = require("package")
|
||||
local term = require("term")
|
||||
local serialization = require("serialization")
|
||||
local shell = require("shell")
|
||||
|
||||
local function gpu()
|
||||
return select(2, term.getGPU())
|
||||
end
|
||||
|
||||
local gpu = term.gpu()
|
||||
local args, options = shell.parse(...)
|
||||
local env = setmetatable({}, {__index = _ENV})
|
||||
|
||||
@ -105,25 +101,22 @@ if #args == 0 or options.i then
|
||||
return r2
|
||||
end
|
||||
|
||||
gpu().setForeground(0xFFFFFF)
|
||||
gpu.setForeground(0xFFFFFF)
|
||||
term.write(_VERSION .. " Copyright (C) 1994-2015 Lua.org, PUC-Rio\n")
|
||||
gpu().setForeground(0xFFFF00)
|
||||
gpu.setForeground(0xFFFF00)
|
||||
term.write("Enter a statement and hit enter to evaluate it.\n")
|
||||
term.write("Prefix an expression with '=' to show its value.\n")
|
||||
term.write("Press Ctrl+C to exit the interpreter.\n")
|
||||
gpu().setForeground(0xFFFFFF)
|
||||
term.write("Press Ctrl+D to exit the interpreter.\n")
|
||||
gpu.setForeground(0xFFFFFF)
|
||||
|
||||
while term.isAvailable() do
|
||||
local foreground = gpu().setForeground(0x00FF00)
|
||||
local foreground = gpu.setForeground(0x00FF00)
|
||||
term.write(tostring(env._PROMPT or "lua> "))
|
||||
gpu().setForeground(foreground)
|
||||
gpu.setForeground(foreground)
|
||||
local command = term.read(history, nil, hint)
|
||||
if command == nil then -- eof
|
||||
if command == nil or command == "" then -- eof
|
||||
return
|
||||
end
|
||||
while #history > 10 do
|
||||
table.remove(history, 1)
|
||||
end
|
||||
local code, reason
|
||||
if string.sub(command, 1, 1) == "=" then
|
||||
code, reason = load("return " .. string.sub(command, 2), "=stdin", "t", env)
|
||||
@ -139,7 +132,7 @@ if #args == 0 or options.i then
|
||||
io.stderr:write(tostring(result[2]) .. "\n")
|
||||
else
|
||||
for i = 2, result.n do
|
||||
term.write(serialization.serialize(result[i], true) .. "\t", true)
|
||||
term.write(require("serialization").serialize(result[i], true) .. "\t", true)
|
||||
end
|
||||
if term.getCursor() > 1 then
|
||||
term.write("\n")
|
||||
|
@ -1,4 +1,3 @@
|
||||
local event = require("event")
|
||||
local keyboard = require("keyboard")
|
||||
local shell = require("shell")
|
||||
local term = require("term")
|
||||
@ -27,7 +26,6 @@ local line = nil
|
||||
local function readlines(num)
|
||||
local x, y, w, h = term.getGlobalArea()
|
||||
num = num or (h - 1)
|
||||
term.setCursorBlink(false)
|
||||
for _ = 1, num do
|
||||
if not line then
|
||||
line = file:read("*l")
|
||||
@ -41,7 +39,6 @@ local function readlines(num)
|
||||
end
|
||||
term.setCursor(1, h)
|
||||
term.write(":")
|
||||
term.setCursorBlink(true)
|
||||
return true
|
||||
end
|
||||
|
||||
@ -51,11 +48,8 @@ while true do
|
||||
return
|
||||
end
|
||||
while true do
|
||||
local event, address, char, code = event.pull("key_down")
|
||||
local hasFocus, screenAddress, keyboardAddress = term.hasFocus()
|
||||
if hasFocus and address == keyboardAddress then
|
||||
local event, address, char, code = term.pull("key_down")
|
||||
if code == keyboard.keys.q then
|
||||
term.setCursorBlink(false)
|
||||
term.clearLine()
|
||||
return
|
||||
elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then
|
||||
@ -68,4 +62,3 @@ while true do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,13 +2,10 @@ local shell = require("shell")
|
||||
local term = require("term")
|
||||
|
||||
local args = shell.parse(...)
|
||||
|
||||
local function gpu()
|
||||
return select(2, term.getGPU())
|
||||
end
|
||||
local gpu = term.gpu()
|
||||
|
||||
if #args == 0 then
|
||||
local w, h = gpu().getViewport()
|
||||
local w, h = gpu.getViewport()
|
||||
io.write(w," ",h,"\n")
|
||||
return
|
||||
end
|
||||
@ -25,7 +22,7 @@ if not w or not h then
|
||||
return 1
|
||||
end
|
||||
|
||||
local result, reason = gpu().setResolution(w, h)
|
||||
local result, reason = gpu.setResolution(w, h)
|
||||
if not result then
|
||||
if reason then -- otherwise we didn't change anything
|
||||
io.stderr:write(reason..'\n')
|
||||
|
@ -13,13 +13,10 @@ end
|
||||
local history = {}
|
||||
shell.prime()
|
||||
|
||||
local function gpu()
|
||||
return select(2, term.getGPU())
|
||||
end
|
||||
|
||||
if #args == 0 and (io.stdin.tty or options.i) and not options.c then
|
||||
-- interactive shell.
|
||||
-- source profile
|
||||
if not term.isAvailable() then event.pull("term_available") end
|
||||
loadfile(shell.resolve("source","lua"))("/etc/profile")
|
||||
while true do
|
||||
if not term.isAvailable() then -- don't clear unless we lost the term
|
||||
@ -28,18 +25,16 @@ if #args == 0 and (io.stdin.tty or options.i) and not options.c then
|
||||
end
|
||||
term.clear()
|
||||
end
|
||||
local gpu = term.gpu()
|
||||
while term.isAvailable() do
|
||||
local foreground = gpu().setForeground(0xFF0000)
|
||||
local foreground = gpu.setForeground(0xFF0000)
|
||||
term.write(sh.expand(os.getenv("PS1") or "$ "))
|
||||
gpu().setForeground(foreground)
|
||||
gpu.setForeground(foreground)
|
||||
local command = term.read(history, nil, sh.hintHandler)
|
||||
if not command then
|
||||
io.write("exit\n")
|
||||
return -- eof
|
||||
end
|
||||
while #history > (tonumber(os.getenv("HISTSIZE")) or 10) do
|
||||
table.remove(history, 1)
|
||||
end
|
||||
command = text.trim(command)
|
||||
if command == "exit" then
|
||||
return
|
||||
|
@ -17,15 +17,15 @@ if not file then
|
||||
end
|
||||
return 1
|
||||
else
|
||||
local status, reason = pcall(function()
|
||||
local status, reason = xpcall(function()
|
||||
repeat
|
||||
local line = file:read("*L")
|
||||
if line then
|
||||
sh.execute(nil, line)
|
||||
end
|
||||
until not line
|
||||
end)
|
||||
end, function(msg) return {msg, debug.traceback()} end)
|
||||
|
||||
file:close()
|
||||
assert(status, reason)
|
||||
if not status and reason then assert(false, tostring(reason[1]) .."\n".. tostring(reason[2])) end
|
||||
end
|
||||
|
@ -73,19 +73,9 @@ function os.tmpname()
|
||||
end
|
||||
end
|
||||
|
||||
os.setenv("EDITOR", "/bin/edit")
|
||||
os.setenv("HISTSIZE", "10")
|
||||
os.setenv("HOME", "/home")
|
||||
os.setenv("IFS", " ")
|
||||
os.setenv("MANPATH", "/usr/man:.")
|
||||
os.setenv("PAGER", "/bin/more")
|
||||
os.setenv("PATH", "/bin:/usr/bin:/home/bin:.")
|
||||
os.setenv("PS1", "$PWD# ")
|
||||
os.setenv("PWD", "/")
|
||||
os.setenv("SHELL", "/bin/sh")
|
||||
os.setenv("TMP", "/tmp") -- Deprecated
|
||||
os.setenv("TMPDIR", "/tmp")
|
||||
os.setenv("LS_COLORS",[[{FILE=0xFFFFFF,DIR=0x66CCFF,LINK=0xFFAA00,["*.lua"]=0x00FF00}]])
|
||||
|
||||
if computer.tmpAddress() then
|
||||
fs.mount(computer.tmpAddress(), os.getenv("TMPDIR") or "/tmp")
|
||||
|
@ -22,10 +22,8 @@ stdoutStream.close = stdinStream.close
|
||||
stderrStream.close = stdinStream.close
|
||||
|
||||
function stdinStream:read(n, dobreak)
|
||||
local result = term.read(stdinHistory, dobreak)
|
||||
while #stdinHistory > 10 do
|
||||
table.remove(stdinHistory, 1)
|
||||
end
|
||||
stdinHistory.dobreak = dobreak
|
||||
local result = term.readKeyboard(stdinHistory)
|
||||
return result
|
||||
end
|
||||
|
||||
|
@ -8,6 +8,7 @@ local function onComponentAvailable(_, componentType)
|
||||
component.gpu.bind(component.screen.address)
|
||||
local depth = 2^(component.gpu.getDepth())
|
||||
os.setenv("TERM", "term-"..depth.."color")
|
||||
require("computer").pushSignal("gpu_bound", component.gpu.address, component.screen.address)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,104 +1,29 @@
|
||||
local component = require("component")
|
||||
local computer = require("computer")
|
||||
local event = require("event")
|
||||
local keyboard = require("keyboard")
|
||||
local term = require("term")
|
||||
local process = require("process")
|
||||
|
||||
local gpuAvailable, screenAvailable = false, false
|
||||
-- this should be the init level process
|
||||
process.info().data.window = term.internal.open()
|
||||
|
||||
local function isAvailable()
|
||||
return gpuAvailable and screenAvailable
|
||||
end
|
||||
|
||||
local function onComponentAvailable(_, componentType)
|
||||
local wasAvailable = isAvailable()
|
||||
if componentType == "gpu" then
|
||||
gpuAvailable = true
|
||||
elseif componentType == "screen" then
|
||||
screenAvailable = true
|
||||
end
|
||||
if not wasAvailable and isAvailable() then
|
||||
event.listen("gpu_bound", function(ename, gpu, screen)
|
||||
gpu=component.proxy(gpu)
|
||||
screen=component.proxy(screen)
|
||||
term.bind(gpu, screen)
|
||||
computer.pushSignal("term_available")
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local function onComponentUnavailable(_, componentType)
|
||||
local wasAvailable = isAvailable()
|
||||
if componentType == "gpu" then
|
||||
gpuAvailable = false
|
||||
elseif componentType == "screen" then
|
||||
screenAvailable = false
|
||||
event.listen("component_unavailable", function(_,type)
|
||||
if type == "screen" or type == "gpu" then
|
||||
if term.isAvailable() then
|
||||
local window = term.internal.window()
|
||||
if window[type] and not component.proxy(window[type].address) then
|
||||
window[type] = nil
|
||||
end
|
||||
if wasAvailable and not isAvailable() then
|
||||
end
|
||||
if not term.isAvailable() then
|
||||
computer.pushSignal("term_unavailable")
|
||||
end
|
||||
end
|
||||
|
||||
local function onTermFocus(event, screenAddress)
|
||||
term.__focus[screenAddress] = term.__nextFocus[screenAddress]
|
||||
end
|
||||
|
||||
local function updateKeyboards(event, _, typ)
|
||||
if event == nil or typ == "screen" or typ == "keyboard" then
|
||||
term.__keyboardToScreen = {}
|
||||
term.__screenToKeyboard = {}
|
||||
for screenAddress in component.list("screen", true) do
|
||||
local keyboardAddress = component.invoke(screenAddress, "getKeyboards")[1]
|
||||
if keyboardAddress then
|
||||
term.__keyboardToScreen[keyboardAddress] = screenAddress
|
||||
term.__screenToKeyboard[screenAddress] = keyboardAddress
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function onKeyDown(event, keyboardAddress, char, code)
|
||||
local screenAddress = term.__keyboardToScreen[keyboardAddress]
|
||||
if screenAddress then
|
||||
local self = term.__focus[screenAddress]
|
||||
if self then
|
||||
if code == keyboard.keys.tab then
|
||||
if keyboard.isControlDown(keyboardAddress) then
|
||||
if keyboard.isShiftDown(keyboardAddress) then
|
||||
self:focusPrevious()
|
||||
else
|
||||
self:focusNext()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function onTouch(event, screenAddress, x, y)
|
||||
local list = term.__knownWindows[screenAddress]
|
||||
if list[1] then
|
||||
local _, gpu = list[1]:getGPU()
|
||||
if gpu and gpu.getScreen() == screenAddress then
|
||||
local w, h = gpu.getViewport()
|
||||
if w then
|
||||
local nextWindow
|
||||
for _, window in ipairs(list) do
|
||||
local xWin, yWin, wWin, hWin = window:getGlobalArea(w, h)
|
||||
local xOffset, yOffset = x - xWin, y - yWin
|
||||
if xOffset >= 0 and xOffset < wWin and yOffset >= 0 and yOffset < hWin then
|
||||
nextWindow = window
|
||||
end
|
||||
end
|
||||
if nextWindow then
|
||||
nextWindow:focus()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
event.listen("component_available", onComponentAvailable)
|
||||
event.listen("component_unavailable", onComponentUnavailable)
|
||||
event.listen("component_added", updateKeyboards)
|
||||
event.listen("component_removed", updateKeyboards)
|
||||
event.listen("term_focus", onTermFocus)
|
||||
event.listen("key_down", onKeyDown)
|
||||
event.listen("touch", onTouch)
|
||||
|
||||
|
||||
end)
|
||||
|
@ -4,6 +4,7 @@ local component = require("component")
|
||||
local computer = require("computer")
|
||||
local text = require("text")
|
||||
local unicode = require("unicode")
|
||||
local term = require("term")
|
||||
|
||||
if not component.isAvailable("gpu") then
|
||||
return
|
||||
@ -19,7 +20,7 @@ if f then
|
||||
f:close()
|
||||
local greeting = greetings[math.random(1, #greetings)]
|
||||
if greeting then
|
||||
local width = math.max(10, component.gpu.getViewport())
|
||||
local width = math.max(10, term.getViewport())
|
||||
for line in text.wrappedLines(greeting, width - 4, width - 4) do
|
||||
table.insert(lines, line)
|
||||
maxWidth = math.max(maxWidth, unicode.len(line))
|
||||
|
@ -12,6 +12,17 @@ alias view=edit\ -r
|
||||
alias help=man
|
||||
alias cp=cp\ -i
|
||||
|
||||
set EDITOR=/bin/edit
|
||||
set HISTSIZE=10
|
||||
set HOME=/home
|
||||
set IFS=\
|
||||
set MANPATH=/usr/man:.
|
||||
set PAGER=/bin/more
|
||||
set PS1='$PWD# '
|
||||
set PWD=/
|
||||
set SHELL=/bin/sh
|
||||
set LS_COLORS="{FILE=0xFFFFFF,DIR=0x66CCFF,LINK=0xFFAA00,['*.lua']=0x00FF00}"
|
||||
|
||||
cd $HOME
|
||||
clear
|
||||
/etc/motd
|
||||
|
@ -123,6 +123,7 @@ do
|
||||
package.delayed["text"] = true
|
||||
package.delayed["sh"] = true
|
||||
package.delayed["transforms"] = true
|
||||
package.delayed["term"] = true
|
||||
end
|
||||
|
||||
status("Initializing file system...")
|
||||
@ -169,7 +170,9 @@ do
|
||||
end
|
||||
|
||||
while true do
|
||||
local result, reason = require("shell").execute()
|
||||
local result, reason = xpcall(require("shell").getShell(), function(msg)
|
||||
return tostring(msg).."\n"..debug.traceback()
|
||||
end)
|
||||
if not result then
|
||||
io.stderr:write((reason ~= nil and tostring(reason) or "unknown error") .. "\n")
|
||||
io.write("Press any key to continue.\n")
|
||||
|
@ -24,63 +24,6 @@ local local_env = {event=event,fs=fs,process=process,shell=shell,term=term,text=
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
|
||||
local memoryStream = {}
|
||||
|
||||
function memoryStream:close()
|
||||
self.closed = true
|
||||
end
|
||||
|
||||
function memoryStream:seek()
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
|
||||
function memoryStream:read(n)
|
||||
if self.closed then
|
||||
return nil -- eof
|
||||
end
|
||||
if self.redirect.read then
|
||||
-- popen could be using this code path
|
||||
-- if that is the case, it is important to leave stream.buffer alone
|
||||
return self.redirect.read:read(n)
|
||||
elseif self.buffer == "" then
|
||||
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
|
||||
end
|
||||
local result = string.sub(self.buffer, 1, n)
|
||||
self.buffer = string.sub(self.buffer, n + 1)
|
||||
return result
|
||||
end
|
||||
|
||||
function memoryStream:write(value)
|
||||
if not self.redirect.write and self.closed then
|
||||
-- if next is dead, ignore all writes
|
||||
if coroutine.status(self.next) ~= "dead" then
|
||||
error("attempt to use a closed stream")
|
||||
end
|
||||
elseif self.redirect.write then
|
||||
return self.redirect.write:write(value)
|
||||
elseif not self.closed then
|
||||
self.buffer = self.buffer .. value
|
||||
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
|
||||
if coroutine.status(self.next) == "dead" then
|
||||
self:close()
|
||||
end
|
||||
if not self.result[1] then
|
||||
error(self.result[2], 0)
|
||||
end
|
||||
table.remove(self.result, 1)
|
||||
return self
|
||||
end
|
||||
return nil, 'stream closed'
|
||||
end
|
||||
|
||||
local stream = {closed = false, buffer = "",
|
||||
redirect = {}, result = {}, args = {}}
|
||||
local metatable = {__index = memoryStream,
|
||||
__metatable = "memorystream"}
|
||||
return setmetatable(stream, metatable)
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
--SH API
|
||||
|
||||
sh.internal.globbers = {{"*",".*"},{"?","."}}
|
||||
@ -109,107 +52,6 @@ function sh.internal.command_result_as_code(ec)
|
||||
end
|
||||
end
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.boolean_executor(chains, predicator)
|
||||
local function not_gate(result)
|
||||
return sh.internal.command_passed(result) and 1 or 0
|
||||
end
|
||||
|
||||
local last = true
|
||||
local boolean_stage = 1
|
||||
local negation_stage = 2
|
||||
local command_stage = 0
|
||||
local stage = negation_stage
|
||||
local skip = false
|
||||
|
||||
for ci=1,#chains do
|
||||
local next = chains[ci]
|
||||
local single = #next == 1 and #next[1] == 1 and not next[1][1].qr and next[1][1].txt
|
||||
|
||||
if single == "||" then
|
||||
if stage ~= command_stage or #chains == 0 then
|
||||
return nil, "syntax error near unexpected token '"..single.."'"
|
||||
end
|
||||
if sh.internal.command_passed(last) then
|
||||
skip = true
|
||||
end
|
||||
stage = boolean_stage
|
||||
elseif single == "&&" then
|
||||
if stage ~= command_stage or #chains == 0 then
|
||||
return nil, "syntax error near unexpected token '"..single.."'"
|
||||
end
|
||||
if not sh.internal.command_passed(last) then
|
||||
skip = true
|
||||
end
|
||||
stage = boolean_stage
|
||||
elseif not skip then
|
||||
local chomped = #next
|
||||
local negate = sh.internal.remove_negation(next)
|
||||
chomped = chomped ~= #next
|
||||
if negate then
|
||||
local prev = predicator
|
||||
predicator = function(n,i)
|
||||
local result = not_gate(prev(n,i))
|
||||
predicator = prev
|
||||
return result
|
||||
end
|
||||
end
|
||||
if chomped then
|
||||
stage = negation_stage
|
||||
end
|
||||
if #next > 0 then
|
||||
last = predicator(next,ci)
|
||||
stage = command_stage
|
||||
end
|
||||
else
|
||||
skip = false
|
||||
stage = command_stage
|
||||
end
|
||||
end
|
||||
|
||||
if stage == negation_stage then
|
||||
last = not_gate(last)
|
||||
end
|
||||
|
||||
return last
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.splitStatements(words, semicolon)
|
||||
checkArg(1, words, "table")
|
||||
checkArg(2, semicolon, "string", "nil")
|
||||
semicolon = semicolon or ";"
|
||||
|
||||
return tx.partition(words, function(g, i, t)
|
||||
if isWord(g,semicolon) then
|
||||
return i, i
|
||||
end
|
||||
end, true)
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.splitChains(s,pc)
|
||||
checkArg(1, s, "table")
|
||||
checkArg(2, pc, "string", "nil")
|
||||
pc = pc or "|"
|
||||
return tx.partition(s, function(w)
|
||||
-- each word has multiple parts due to quotes
|
||||
if isWord(w,pc) then
|
||||
return true
|
||||
end
|
||||
end, true) -- drop |s
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.groupChains(s)
|
||||
checkArg(1,s,"table")
|
||||
return tx.partition(s,function(w)return isWordOf(w,{"&&","||"})end)
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.remove_negation(chain)
|
||||
if isWord(chain[1],"!") then
|
||||
table.remove(chain, 1)
|
||||
return true and not sh.internal.remove_negation(chain)
|
||||
end
|
||||
return false
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function sh.internal.resolveActions(input, resolver, resolved)
|
||||
checkArg(1, input, "string")
|
||||
checkArg(2, resolver, "function", "nil")
|
||||
@ -281,46 +123,8 @@ function sh.internal.statements(input)
|
||||
return statements
|
||||
end
|
||||
|
||||
-- verifies that no pipes are doubled up nor at the start nor end of words
|
||||
function --[[@delayloaded-start@]] sh.internal.hasValidPiping(words, pipes)
|
||||
checkArg(1, words, "table")
|
||||
checkArg(2, pipes, "table", "nil")
|
||||
|
||||
if #words == 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
pipes = pipes or tx.sub(text.syntax, 2) -- first text syntax is ; which CAN be repeated
|
||||
|
||||
local pies = tx.select(words, function(parts, i, t)
|
||||
return (#parts == 1 and tx.first(pipes, {{parts[1].txt}}) and true or false), i
|
||||
end)
|
||||
|
||||
local bad_pipe
|
||||
local last = 0
|
||||
for k,v in ipairs(pies) do
|
||||
if v then
|
||||
if k-last == 1 then
|
||||
bad_pipe = words[k][1].txt
|
||||
break
|
||||
end
|
||||
last=k
|
||||
end
|
||||
end
|
||||
|
||||
if not bad_pipe and last == #pies then
|
||||
bad_pipe = words[last][1].txt
|
||||
end
|
||||
|
||||
if bad_pipe then
|
||||
return false, "parse error near " .. bad_pipe
|
||||
else
|
||||
return true
|
||||
end
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
-- returns true if key is a string that represents a valid command line identifier
|
||||
function sh.isIdentifier(key)
|
||||
function sh.internal.isIdentifier(key)
|
||||
if type(key) ~= "string" then
|
||||
return false
|
||||
end
|
||||
@ -328,78 +132,33 @@ function sh.isIdentifier(key)
|
||||
return key:match("^[%a_][%w_]*$") == key
|
||||
end
|
||||
|
||||
-- returns the environment stored value of key if it exists else an empty string
|
||||
function sh.expandKey(key)
|
||||
-- reserved alias $? is last error codes
|
||||
function sh.expand(value)
|
||||
return value
|
||||
:gsub("%$([_%w%?]+)", function(key)
|
||||
if key == "?" then
|
||||
return tostring(sh.getLastExitCode())
|
||||
end
|
||||
|
||||
return os.getenv(key) or ''
|
||||
return os.getenv(key) or '' end)
|
||||
:gsub("%${(.*)}", function(key)
|
||||
if sh.internal.isIdentifier(key) then
|
||||
return sh.internal.expandKey(key)
|
||||
end
|
||||
|
||||
function sh.keySubstitution(key)
|
||||
if sh.isIdentifier(key) then
|
||||
return sh.expandKey(key)
|
||||
end
|
||||
|
||||
error("${" .. key .. "}: bad substitution")
|
||||
end)
|
||||
end
|
||||
|
||||
function sh.expand(value)
|
||||
local result = value:gsub("%$([_%w%?]+)", sh.expandKey):gsub("%${(.*)}", sh.keySubstitution)
|
||||
return result -- we return just result because gsub returns multiple to the stack
|
||||
function sh.internal.expand(word)
|
||||
if #word == 0 then return {} end
|
||||
|
||||
local result = ''
|
||||
for i=1,#word do
|
||||
local part = word[i]
|
||||
result = result .. (not (part.qr and part.qr[3]) and sh.expand(part.txt) or part.txt)
|
||||
end
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.glob(glob_pattern)
|
||||
local segments = text.split(glob_pattern, {"/"}, true)
|
||||
local hiddens = tx.select(segments,function(e)return e:match("^%%%.")==nil end)
|
||||
local function is_visible(s,i)
|
||||
return not hiddens[i] or s:match("^%.") == nil
|
||||
return {result}
|
||||
end
|
||||
|
||||
local function magical(s)
|
||||
for _,glob_rule in ipairs(sh.internal.globbers) do
|
||||
if s:match("[^%%]-"..text.escapeMagic(glob_rule[2])) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local is_abs = glob_pattern:sub(1, 1) == "/"
|
||||
local root = is_abs and '' or shell.getWorkingDirectory()
|
||||
local paths = {is_abs and "/" or ''}
|
||||
local relative_separator = ''
|
||||
|
||||
for i,segment in ipairs(segments) do
|
||||
local enclosed_pattern = string.format("^(%s)/?$", segment)
|
||||
local next_paths = {}
|
||||
for _,path in ipairs(paths) do
|
||||
if fs.isDirectory(root..path) then
|
||||
if magical(segment) then
|
||||
for file in fs.list(root..path) do
|
||||
if file:match(enclosed_pattern) and is_visible(file, i) then
|
||||
table.insert(next_paths, path..relative_separator..file:gsub("/+$",''))
|
||||
end
|
||||
end
|
||||
else -- not a globbing segment, just use it raw
|
||||
local plain = text.removeEscapes(segment)
|
||||
local fpath = root..path..relative_separator..plain
|
||||
local hit = path..relative_separator..plain:gsub("/+$",'')
|
||||
if fs.exists(fpath) then
|
||||
table.insert(next_paths, hit)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
paths = next_paths
|
||||
if not next(paths) then break end
|
||||
relative_separator = "/"
|
||||
end
|
||||
-- if no next_paths were hit here, the ENTIRE glob value is not a path
|
||||
return paths
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
-- expand to files in path, or try key substitution
|
||||
-- word is a list of metadata-filled word parts
|
||||
-- note: text.internal.words(string) returns an array of these words
|
||||
@ -408,7 +167,7 @@ function sh.internal.evaluate(word)
|
||||
if #word == 0 then
|
||||
return {}
|
||||
elseif #word == 1 and word[1].qr then
|
||||
return {sh.expand(word[1].txt)}
|
||||
return sh.internal.expand(word)
|
||||
end
|
||||
local function make_pattern(seg)
|
||||
local result = seg
|
||||
@ -422,12 +181,10 @@ function sh.internal.evaluate(word)
|
||||
end
|
||||
return result
|
||||
end
|
||||
local normalized = ''
|
||||
local glob_pattern = ''
|
||||
local has_globits = false
|
||||
for i=1,#word do local part = word[i]
|
||||
local next = part.txt
|
||||
normalized = normalized .. next
|
||||
if not part.qr then
|
||||
local escaped = text.escapeMagic(next)
|
||||
next = make_pattern(escaped)
|
||||
@ -438,114 +195,12 @@ function sh.internal.evaluate(word)
|
||||
glob_pattern = glob_pattern .. next
|
||||
end
|
||||
if not has_globits then
|
||||
return {sh.expand(normalized)}
|
||||
return sh.internal.expand(word)
|
||||
end
|
||||
local globs = sh.internal.glob(glob_pattern)
|
||||
return #globs == 0 and {sh.expand(normalized)} or globs
|
||||
return #globs == 0 and sh.internal.expand(word) or globs
|
||||
end
|
||||
|
||||
function --[[@delayloaded-start@]] sh.getMatchingPrograms(baseName)
|
||||
local result = {}
|
||||
local result_keys = {} -- cache for fast value lookup
|
||||
-- TODO only matching files with .lua extension for now, might want to
|
||||
-- extend this to other extensions at some point? env var? file attrs?
|
||||
if not baseName or #baseName == 0 then
|
||||
baseName = "^(.*)%.lua$"
|
||||
else
|
||||
baseName = "^(" .. text.escapeMagic(baseName) .. ".*)%.lua$"
|
||||
end
|
||||
for basePath in string.gmatch(os.getenv("PATH"), "[^:]+") do
|
||||
for file in fs.list(basePath) do
|
||||
local match = file:match(baseName)
|
||||
if match and not result_keys[match] then
|
||||
table.insert(result, match)
|
||||
result_keys[match] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return result
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.getMatchingFiles(basePath, name)
|
||||
local resolvedPath = shell.resolve(basePath)
|
||||
local result, baseName = {}
|
||||
|
||||
-- note: we strip the trailing / to make it easier to navigate through
|
||||
-- directories using tab completion (since entering the / will then serve
|
||||
-- as the intention to go into the currently hinted one).
|
||||
-- if we have a directory but no trailing slash there may be alternatives
|
||||
-- on the same level, so don't look inside that directory... (cont.)
|
||||
if fs.isDirectory(resolvedPath) and name:len() == 0 then
|
||||
baseName = "^(.-)/?$"
|
||||
else
|
||||
baseName = "^(" .. text.escapeMagic(name) .. ".-)/?$"
|
||||
end
|
||||
|
||||
for file in fs.list(resolvedPath) do
|
||||
local match = file:match(baseName)
|
||||
if match then
|
||||
table.insert(result, basePath .. match)
|
||||
end
|
||||
end
|
||||
-- (cont.) but if there's only one match and it's a directory, *then* we
|
||||
-- do want to add the trailing slash here.
|
||||
if #result == 1 and fs.isDirectory(shell.resolve(result[1])) then
|
||||
result[1] = result[1] .. "/"
|
||||
end
|
||||
return result
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.hintHandlerSplit(line)
|
||||
if line:sub(-1):find("%s") then
|
||||
return '', line
|
||||
end
|
||||
local splits = text.internal.tokenize(line)
|
||||
if not splits then -- parse error, e.g. unclosed quotes
|
||||
return nil -- no split, no hints
|
||||
end
|
||||
local num_splits = #splits
|
||||
if num_splits == 1 or not isWordOf(splits[num_splits-1],{";","&&","||","|"}) then
|
||||
return '', line
|
||||
end
|
||||
local l = text.internal.normalize({splits[num_splits]})[1]
|
||||
return line:sub(1,-l:len()-1), l
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor)
|
||||
local line = unicode.sub(full_line, 1, cursor - 1)
|
||||
local suffix = unicode.sub(full_line, cursor)
|
||||
if not line or #line < 1 then
|
||||
return {}
|
||||
end
|
||||
local prev,line = sh.internal.hintHandlerSplit(line)
|
||||
if not prev then -- failed to parse, e.g. unclosed quote, no hints
|
||||
return {}
|
||||
end
|
||||
local result
|
||||
local prefix, partial = string.match(line, "^(.+%s+)(.*)$")
|
||||
local partialPrefix = (partial or line)
|
||||
local name = partialPrefix:gsub(".*/", "")
|
||||
partialPrefix = partialPrefix:sub(1, -name:len() - 1)
|
||||
local searchInPath = not prefix and not partialPrefix:find("/")
|
||||
if searchInPath then
|
||||
result = sh.getMatchingPrograms(line)
|
||||
else
|
||||
result = sh.getMatchingFiles(partialPrefix, name)
|
||||
end
|
||||
local resultSuffix = suffix
|
||||
if #result > 0 and unicode.sub(result[1], -1) ~= "/" and
|
||||
not suffix:sub(1,1):find('%s') and
|
||||
(#result == 1 or searchInPath or not prefix) then
|
||||
resultSuffix = " " .. resultSuffix
|
||||
end
|
||||
prefix = prev .. (prefix or "")
|
||||
table.sort(result)
|
||||
for i = 1, #result do
|
||||
result[i] = prefix .. result[i] .. resultSuffix
|
||||
end
|
||||
return result
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function sh.hintHandler(full_line, cursor)
|
||||
return sh.internal.hintHandlerImpl(full_line, cursor)
|
||||
end
|
||||
@ -764,6 +419,358 @@ function sh.execute(env, command, ...)
|
||||
return true
|
||||
end
|
||||
|
||||
return sh.internal.execute_complex(statements)
|
||||
end
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.glob(glob_pattern)
|
||||
local segments = text.split(glob_pattern, {"/"}, true)
|
||||
local hiddens = tx.select(segments,function(e)return e:match("^%%%.")==nil end)
|
||||
local function is_visible(s,i)
|
||||
return not hiddens[i] or s:match("^%.") == nil
|
||||
end
|
||||
|
||||
local function magical(s)
|
||||
for _,glob_rule in ipairs(sh.internal.globbers) do
|
||||
if s:match("[^%%]-"..text.escapeMagic(glob_rule[2])) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local is_abs = glob_pattern:sub(1, 1) == "/"
|
||||
local root = is_abs and '' or shell.getWorkingDirectory()
|
||||
local paths = {is_abs and "/" or ''}
|
||||
local relative_separator = ''
|
||||
|
||||
for i,segment in ipairs(segments) do
|
||||
local enclosed_pattern = string.format("^(%s)/?$", segment)
|
||||
local next_paths = {}
|
||||
for _,path in ipairs(paths) do
|
||||
if fs.isDirectory(root..path) then
|
||||
if magical(segment) then
|
||||
for file in fs.list(root..path) do
|
||||
if file:match(enclosed_pattern) and is_visible(file, i) then
|
||||
table.insert(next_paths, path..relative_separator..file:gsub("/+$",''))
|
||||
end
|
||||
end
|
||||
else -- not a globbing segment, just use it raw
|
||||
local plain = text.removeEscapes(segment)
|
||||
local fpath = root..path..relative_separator..plain
|
||||
local hit = path..relative_separator..plain:gsub("/+$",'')
|
||||
if fs.exists(fpath) then
|
||||
table.insert(next_paths, hit)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
paths = next_paths
|
||||
if not next(paths) then break end
|
||||
relative_separator = "/"
|
||||
end
|
||||
-- if no next_paths were hit here, the ENTIRE glob value is not a path
|
||||
return paths
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.getMatchingPrograms(baseName)
|
||||
local result = {}
|
||||
local result_keys = {} -- cache for fast value lookup
|
||||
-- TODO only matching files with .lua extension for now, might want to
|
||||
-- extend this to other extensions at some point? env var? file attrs?
|
||||
if not baseName or #baseName == 0 then
|
||||
baseName = "^(.*)%.lua$"
|
||||
else
|
||||
baseName = "^(" .. text.escapeMagic(baseName) .. ".*)%.lua$"
|
||||
end
|
||||
for basePath in string.gmatch(os.getenv("PATH"), "[^:]+") do
|
||||
for file in fs.list(basePath) do
|
||||
local match = file:match(baseName)
|
||||
if match and not result_keys[match] then
|
||||
table.insert(result, match)
|
||||
result_keys[match] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return result
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.getMatchingFiles(basePath, name)
|
||||
local resolvedPath = shell.resolve(basePath)
|
||||
local result, baseName = {}
|
||||
|
||||
-- note: we strip the trailing / to make it easier to navigate through
|
||||
-- directories using tab completion (since entering the / will then serve
|
||||
-- as the intention to go into the currently hinted one).
|
||||
-- if we have a directory but no trailing slash there may be alternatives
|
||||
-- on the same level, so don't look inside that directory... (cont.)
|
||||
if fs.isDirectory(resolvedPath) and name == "" then
|
||||
baseName = "^(.-)/?$"
|
||||
else
|
||||
baseName = "^(" .. text.escapeMagic(name) .. ".-)/?$"
|
||||
end
|
||||
|
||||
for file in fs.list(resolvedPath) do
|
||||
local match = file:match(baseName)
|
||||
if match then
|
||||
table.insert(result, basePath .. match)
|
||||
end
|
||||
end
|
||||
-- (cont.) but if there's only one match and it's a directory, *then* we
|
||||
-- do want to add the trailing slash here.
|
||||
if #result == 1 and fs.isDirectory(shell.resolve(result[1])) then
|
||||
result[1] = result[1] .. "/"
|
||||
end
|
||||
return result
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.hintHandlerSplit(line)
|
||||
if line:sub(-1):find("%s") then
|
||||
return '', line
|
||||
end
|
||||
local splits = text.internal.tokenize(line)
|
||||
if not splits then -- parse error, e.g. unclosed quotes
|
||||
return nil -- no split, no hints
|
||||
end
|
||||
local num_splits = #splits
|
||||
if num_splits == 1 or not isWordOf(splits[num_splits-1],{";","&&","||","|"}) then
|
||||
return '', line
|
||||
end
|
||||
local l = text.internal.normalize({splits[num_splits]})[1]
|
||||
return line:sub(1,-unicode.len(l)-1), l
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor)
|
||||
local line = unicode.sub(full_line, 1, cursor - 1)
|
||||
local suffix = unicode.sub(full_line, cursor)
|
||||
if not line or #line < 1 then
|
||||
return {}
|
||||
end
|
||||
local prev,line = sh.internal.hintHandlerSplit(line)
|
||||
if not prev then -- failed to parse, e.g. unclosed quote, no hints
|
||||
return {}
|
||||
end
|
||||
local result
|
||||
local prefix, partial = line:match("^(.*=)(.*)$")
|
||||
if not prefix then prefix, partial = line:match("^(.+%s+)(.*)$") end
|
||||
local partialPrefix = (partial or line)
|
||||
local name = partialPrefix:gsub(".*/", "")
|
||||
partialPrefix = partialPrefix:sub(1, -unicode.len(name) - 1)
|
||||
local searchInPath = not prefix and not partialPrefix:find("/")
|
||||
if searchInPath then
|
||||
result = sh.getMatchingPrograms(line)
|
||||
else
|
||||
result = sh.getMatchingFiles(partialPrefix, name)
|
||||
end
|
||||
local resultSuffix = suffix
|
||||
if #result > 0 and unicode.sub(result[1], -1) ~= "/" and
|
||||
not suffix:sub(1,1):find('%s') and
|
||||
(#result == 1 or searchInPath or not prefix) then
|
||||
resultSuffix = " " .. resultSuffix
|
||||
end
|
||||
prefix = prev .. (prefix or "")
|
||||
table.sort(result)
|
||||
for i = 1, #result do
|
||||
result[i] = prefix .. result[i] .. resultSuffix
|
||||
end
|
||||
return result
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
-- verifies that no pipes are doubled up nor at the start nor end of words
|
||||
function --[[@delayloaded-start@]] sh.internal.hasValidPiping(words, pipes)
|
||||
checkArg(1, words, "table")
|
||||
checkArg(2, pipes, "table", "nil")
|
||||
|
||||
if #words == 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
pipes = pipes or tx.sub(text.syntax, 2) -- first text syntax is ; which CAN be repeated
|
||||
|
||||
local pies = tx.select(words, function(parts, i, t)
|
||||
return (#parts == 1 and tx.first(pipes, {{parts[1].txt}}) and true or false), i
|
||||
end)
|
||||
|
||||
local bad_pipe
|
||||
local last = 0
|
||||
for k,v in ipairs(pies) do
|
||||
if v then
|
||||
if k-last == 1 then
|
||||
bad_pipe = words[k][1].txt
|
||||
break
|
||||
end
|
||||
last=k
|
||||
end
|
||||
end
|
||||
|
||||
if not bad_pipe and last == #pies then
|
||||
bad_pipe = words[last][1].txt
|
||||
end
|
||||
|
||||
if bad_pipe then
|
||||
return false, "parse error near " .. bad_pipe
|
||||
else
|
||||
return true
|
||||
end
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.boolean_executor(chains, predicator)
|
||||
local function not_gate(result)
|
||||
return sh.internal.command_passed(result) and 1 or 0
|
||||
end
|
||||
|
||||
local last = true
|
||||
local boolean_stage = 1
|
||||
local negation_stage = 2
|
||||
local command_stage = 0
|
||||
local stage = negation_stage
|
||||
local skip = false
|
||||
|
||||
for ci=1,#chains do
|
||||
local next = chains[ci]
|
||||
local single = #next == 1 and #next[1] == 1 and not next[1][1].qr and next[1][1].txt
|
||||
|
||||
if single == "||" then
|
||||
if stage ~= command_stage or #chains == 0 then
|
||||
return nil, "syntax error near unexpected token '"..single.."'"
|
||||
end
|
||||
if sh.internal.command_passed(last) then
|
||||
skip = true
|
||||
end
|
||||
stage = boolean_stage
|
||||
elseif single == "&&" then
|
||||
if stage ~= command_stage or #chains == 0 then
|
||||
return nil, "syntax error near unexpected token '"..single.."'"
|
||||
end
|
||||
if not sh.internal.command_passed(last) then
|
||||
skip = true
|
||||
end
|
||||
stage = boolean_stage
|
||||
elseif not skip then
|
||||
local chomped = #next
|
||||
local negate = sh.internal.remove_negation(next)
|
||||
chomped = chomped ~= #next
|
||||
if negate then
|
||||
local prev = predicator
|
||||
predicator = function(n,i)
|
||||
local result = not_gate(prev(n,i))
|
||||
predicator = prev
|
||||
return result
|
||||
end
|
||||
end
|
||||
if chomped then
|
||||
stage = negation_stage
|
||||
end
|
||||
if #next > 0 then
|
||||
last = predicator(next,ci)
|
||||
stage = command_stage
|
||||
end
|
||||
else
|
||||
skip = false
|
||||
stage = command_stage
|
||||
end
|
||||
end
|
||||
|
||||
if stage == negation_stage then
|
||||
last = not_gate(last)
|
||||
end
|
||||
|
||||
return last
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.splitStatements(words, semicolon)
|
||||
checkArg(1, words, "table")
|
||||
checkArg(2, semicolon, "string", "nil")
|
||||
semicolon = semicolon or ";"
|
||||
|
||||
return tx.partition(words, function(g, i, t)
|
||||
if isWord(g,semicolon) then
|
||||
return i, i
|
||||
end
|
||||
end, true)
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.splitChains(s,pc)
|
||||
checkArg(1, s, "table")
|
||||
checkArg(2, pc, "string", "nil")
|
||||
pc = pc or "|"
|
||||
return tx.partition(s, function(w)
|
||||
-- each word has multiple parts due to quotes
|
||||
if isWord(w,pc) then
|
||||
return true
|
||||
end
|
||||
end, true) -- drop |s
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.groupChains(s)
|
||||
checkArg(1,s,"table")
|
||||
return tx.partition(s,function(w)return isWordOf(w,{"&&","||"})end)
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.remove_negation(chain)
|
||||
if isWord(chain[1],"!") then
|
||||
table.remove(chain, 1)
|
||||
return true and not sh.internal.remove_negation(chain)
|
||||
end
|
||||
return false
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
|
||||
local memoryStream = {}
|
||||
|
||||
function memoryStream:close()
|
||||
self.closed = true
|
||||
end
|
||||
|
||||
function memoryStream:seek()
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
|
||||
function memoryStream:read(n)
|
||||
if self.closed then
|
||||
return nil -- eof
|
||||
end
|
||||
if self.redirect.read then
|
||||
-- popen could be using this code path
|
||||
-- if that is the case, it is important to leave stream.buffer alone
|
||||
return self.redirect.read:read(n)
|
||||
elseif self.buffer == "" then
|
||||
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
|
||||
end
|
||||
local result = string.sub(self.buffer, 1, n)
|
||||
self.buffer = string.sub(self.buffer, n + 1)
|
||||
return result
|
||||
end
|
||||
|
||||
function memoryStream:write(value)
|
||||
if not self.redirect.write and self.closed then
|
||||
-- if next is dead, ignore all writes
|
||||
if coroutine.status(self.next) ~= "dead" then
|
||||
error("attempt to use a closed stream")
|
||||
end
|
||||
elseif self.redirect.write then
|
||||
return self.redirect.write:write(value)
|
||||
elseif not self.closed then
|
||||
self.buffer = self.buffer .. value
|
||||
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
|
||||
if coroutine.status(self.next) == "dead" then
|
||||
self:close()
|
||||
end
|
||||
if not self.result[1] then
|
||||
error(self.result[2], 0)
|
||||
end
|
||||
table.remove(self.result, 1)
|
||||
return self
|
||||
end
|
||||
return nil, 'stream closed'
|
||||
end
|
||||
|
||||
local stream = {closed = false, buffer = "",
|
||||
redirect = {}, result = {}, args = {}}
|
||||
local metatable = {__index = memoryStream,
|
||||
__metatable = "memorystream"}
|
||||
return setmetatable(stream, metatable)
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.execute_complex(statements)
|
||||
for si=1,#statements do local s = statements[si]
|
||||
local chains = sh.internal.groupChains(s)
|
||||
local last_code,br = sh.internal.boolean_executor(chains, function(chain, chain_index)
|
||||
@ -777,6 +784,6 @@ function sh.execute(env, command, ...)
|
||||
sh.internal.ec.last = sh.internal.command_result_as_code(last_code)
|
||||
end
|
||||
return true, br
|
||||
end
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
return sh, local_env
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,7 @@ setmetatable(text.internal,
|
||||
|
||||
text.syntax = {";","&&","||","|",">>",">","<"}
|
||||
|
||||
function text.detab(value, tabWidth)
|
||||
function --[[@delayloaded-start@]] text.detab(value, tabWidth)
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, tabWidth, "number", "nil")
|
||||
tabWidth = tabWidth or 8
|
||||
@ -27,8 +27,9 @@ function text.detab(value, tabWidth)
|
||||
end
|
||||
local result = value:gsub("([^\n]-)\t", rep) -- truncate results
|
||||
return result
|
||||
end
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
-- used in motd
|
||||
function text.padRight(value, length)
|
||||
checkArg(1, value, "string", "nil")
|
||||
checkArg(2, length, "number")
|
||||
@ -39,7 +40,7 @@ function text.padRight(value, length)
|
||||
end
|
||||
end
|
||||
|
||||
function text.padLeft(value, length)
|
||||
function --[[@delayloaded-start@]] text.padLeft(value, length)
|
||||
checkArg(1, value, "string", "nil")
|
||||
checkArg(2, length, "number")
|
||||
if not value or unicode.wlen(value) == 0 then
|
||||
@ -47,7 +48,7 @@ function text.padLeft(value, length)
|
||||
else
|
||||
return string.rep(" ", length - unicode.wlen(value)) .. value
|
||||
end
|
||||
end
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function text.trim(value) -- from http://lua-users.org/wiki/StringTrim
|
||||
local from = string.match(value, "^%s*()")
|
||||
@ -176,13 +177,6 @@ function text.internal.tokenize(value, quotes, delimiters)
|
||||
return text.internal.splitWords(words, delimiters)
|
||||
end
|
||||
|
||||
function --[[@delayloaded-start@]] text.internal.table_view(str)
|
||||
checkArg(1, str, 'string')
|
||||
return setmetatable({s=str},
|
||||
{ __index = function(_,k) return unicode.sub(_.s,k,k) end,
|
||||
__len = function(_) return unicode.len(_.s) end})
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
-- tokenize input by quotes and whitespace
|
||||
function text.internal.words(input, quotes)
|
||||
checkArg(1, input, "string")
|
||||
|
@ -133,7 +133,9 @@ end
|
||||
-- calls callback(e,i,tbl) for each ith element e in table tbl from first
|
||||
function lib.foreach(tbl,c,f,l)
|
||||
checkArg(1,tbl,'table')
|
||||
checkArg(2,c,'function')
|
||||
checkArg(2,c,'function','string')
|
||||
local ck=c
|
||||
c=type(c)=="string" and function(e) return e[ck] end or c
|
||||
local s=#tbl
|
||||
f,l=adjust(f,l,s)
|
||||
tbl=view(tbl,f,l)
|
||||
|
Loading…
x
Reference in New Issue
Block a user