mirror of
https://github.com/kepler155c/opus
synced 2024-11-15 21:24:49 +00:00
617 lines
13 KiB
Plaintext
617 lines
13 KiB
Plaintext
local sandboxEnv = { }
|
|
for k,v in pairs(_ENV) do
|
|
sandboxEnv[k] = v
|
|
end
|
|
|
|
_G.requireInjector()
|
|
|
|
local Config = require('config')
|
|
local Input = require('input')
|
|
local Util = require('util')
|
|
|
|
local colors = _G.colors
|
|
local fs = _G.fs
|
|
local keys = _G.keys
|
|
local multishell = _ENV.multishell
|
|
local os = _G.os
|
|
local printError = _G.printError
|
|
local shell = _ENV.shell
|
|
local term = _G.term
|
|
local window = _G.window
|
|
|
|
local parentTerm = term.current()
|
|
local w,h = parentTerm.getSize()
|
|
local tabs = { }
|
|
local currentTab
|
|
local _tabId = 0
|
|
local overviewId
|
|
local runningTab
|
|
local tabsDirty = false
|
|
local closeInd = '*'
|
|
local hooks = { }
|
|
local hotkeys = { }
|
|
local downState = { }
|
|
|
|
multishell.term = term.current()
|
|
|
|
-- Default label
|
|
if not os.getComputerLabel() then
|
|
local id = os.getComputerID()
|
|
if _G.turtle then
|
|
os.setComputerLabel('turtle_' .. id)
|
|
elseif _G.pocket then
|
|
os.setComputerLabel('pocket_' .. id)
|
|
elseif _G.commands then
|
|
os.setComputerLabel('command_' .. id)
|
|
else
|
|
os.setComputerLabel('computer_' .. id)
|
|
end
|
|
end
|
|
|
|
if Util.getVersion() >= 1.76 then
|
|
closeInd = '\215'
|
|
end
|
|
|
|
local config = {
|
|
standard = {
|
|
textColor = colors.lightGray,
|
|
tabBarTextColor = colors.lightGray,
|
|
focusTextColor = colors.white,
|
|
backgroundColor = colors.gray,
|
|
tabBarBackgroundColor = colors.gray,
|
|
focusBackgroundColor = colors.gray,
|
|
},
|
|
color = {
|
|
textColor = colors.lightGray,
|
|
tabBarTextColor = colors.lightGray,
|
|
focusTextColor = colors.white,
|
|
backgroundColor = colors.gray,
|
|
tabBarBackgroundColor = colors.gray,
|
|
focusBackgroundColor = colors.gray,
|
|
},
|
|
}
|
|
Config.load('multishell', config)
|
|
|
|
local _colors = config.standard
|
|
if parentTerm.isColor() then
|
|
_colors = config.color
|
|
end
|
|
|
|
local function redrawMenu()
|
|
if not tabsDirty then
|
|
os.queueEvent('multishell_redraw')
|
|
tabsDirty = true
|
|
end
|
|
end
|
|
|
|
local function resumeTab(tab, event, eventData)
|
|
if not tab or coroutine.status(tab.co) == 'dead' then
|
|
return
|
|
end
|
|
|
|
if not tab.filter or tab.filter == event or event == "terminate" then
|
|
eventData = eventData or { }
|
|
term.redirect(tab.terminal)
|
|
local previousTab = runningTab
|
|
runningTab = tab
|
|
local ok, result = coroutine.resume(tab.co, event, unpack(eventData))
|
|
tab.terminal = term.current()
|
|
if ok then
|
|
tab.filter = result
|
|
else
|
|
printError(result)
|
|
end
|
|
|
|
runningTab = previousTab
|
|
|
|
return ok, result
|
|
end
|
|
end
|
|
|
|
local function selectTab(tab)
|
|
if not tab then
|
|
for _,ftab in pairs(tabs) do
|
|
if not ftab.hidden then
|
|
tab = ftab
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if not tab then
|
|
tab = tabs[overviewId]
|
|
end
|
|
|
|
if currentTab and currentTab ~= tab then
|
|
currentTab.window.setVisible(false)
|
|
if coroutine.status(currentTab.co) == 'suspended' then
|
|
-- the process that opens a new tab won't get the lose focus event
|
|
-- os.queueEvent('multishell_notifyfocus', currentTab.tabId)
|
|
--resumeTab(currentTab, 'multishell_losefocus')
|
|
end
|
|
if tab and not currentTab.hidden then
|
|
tab.previousTabId = currentTab.tabId
|
|
end
|
|
end
|
|
|
|
if tab ~= currentTab then
|
|
currentTab = tab
|
|
tab.window.setVisible(true)
|
|
resumeTab(tab, 'multishell_focus')
|
|
end
|
|
end
|
|
|
|
local function nextTabId()
|
|
_tabId = _tabId + 1
|
|
return _tabId
|
|
end
|
|
|
|
local function launchProcess(tab)
|
|
tab.tabId = nextTabId()
|
|
tab.timestamp = os.clock()
|
|
tab.window = window.create(parentTerm, 1, 2, w, h - 1, false)
|
|
tab.terminal = tab.window
|
|
tab.env = Util.shallowCopy(tab.env or sandboxEnv)
|
|
|
|
tab.co = coroutine.create(function()
|
|
|
|
local result, err
|
|
|
|
if tab.fn then
|
|
result, err = Util.runFunction(tab.env, tab.fn, table.unpack(tab.args or { } ))
|
|
elseif tab.path then
|
|
result, err = Util.run(tab.env, tab.path, table.unpack(tab.args or { } ))
|
|
else
|
|
err = 'multishell: invalid tab'
|
|
end
|
|
|
|
if not result and err and err ~= 'Terminated' then
|
|
if err then
|
|
printError(tostring(err))
|
|
end
|
|
printError('Press enter to close')
|
|
tab.isDead = true
|
|
while true do
|
|
local e, code = os.pullEventRaw('key')
|
|
if e == 'terminate' or e == 'key' and code == keys.enter then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
tabs[tab.tabId] = nil
|
|
if tab == currentTab then
|
|
local previousTab
|
|
if tab.previousTabId then
|
|
previousTab = tabs[tab.previousTabId]
|
|
if previousTab and previousTab.hidden then
|
|
previousTab = nil
|
|
end
|
|
end
|
|
selectTab(previousTab)
|
|
end
|
|
redrawMenu()
|
|
end)
|
|
|
|
tabs[tab.tabId] = tab
|
|
resumeTab(tab)
|
|
return tab
|
|
end
|
|
|
|
function multishell.addHotkey(code, fn)
|
|
hotkeys[code] = fn
|
|
end
|
|
|
|
function multishell.removeHotkey(code)
|
|
hotkeys[code] = nil
|
|
end
|
|
|
|
function multishell.getFocus()
|
|
return currentTab.tabId
|
|
end
|
|
|
|
function multishell.setFocus(tabId)
|
|
local tab = tabs[tabId]
|
|
if tab then
|
|
selectTab(tab)
|
|
redrawMenu()
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function multishell.getTitle(tabId)
|
|
local tab = tabs[tabId]
|
|
if tab then
|
|
return tab.title
|
|
end
|
|
end
|
|
|
|
function multishell.setTitle(tabId, title)
|
|
local tab = tabs[tabId]
|
|
if tab then
|
|
if not tab.isOverview then
|
|
tab.title = title or ''
|
|
end
|
|
redrawMenu()
|
|
end
|
|
end
|
|
|
|
function multishell.getCurrent()
|
|
if runningTab then
|
|
return runningTab.tabId
|
|
end
|
|
end
|
|
|
|
function multishell.getTab(tabId)
|
|
return tabs[tabId]
|
|
end
|
|
|
|
function multishell.terminate(tabId)
|
|
os.queueEvent('multishell_terminate', tabId)
|
|
end
|
|
|
|
function multishell.getTabs()
|
|
return tabs
|
|
end
|
|
|
|
function multishell.launch( tProgramEnv, sProgramPath, ... )
|
|
-- backwards compatibility
|
|
return multishell.openTab({
|
|
env = tProgramEnv,
|
|
path = sProgramPath,
|
|
args = { ... },
|
|
})
|
|
end
|
|
|
|
function multishell.openTab(tab)
|
|
if not tab.title and tab.path then
|
|
tab.title = fs.getName(tab.path)
|
|
end
|
|
tab.title = tab.title or 'untitled'
|
|
|
|
local previousTerm = term.current()
|
|
launchProcess(tab)
|
|
term.redirect(previousTerm)
|
|
|
|
if tab.hidden then
|
|
if coroutine.status(tab.co) == 'dead' or tab.isDead then
|
|
tab.hidden = false
|
|
end
|
|
elseif tab.focused then
|
|
multishell.setFocus(tab.tabId)
|
|
else
|
|
redrawMenu()
|
|
end
|
|
|
|
return tab.tabId
|
|
end
|
|
|
|
function multishell.hideTab(tabId)
|
|
local tab = tabs[tabId]
|
|
if tab then
|
|
tab.hidden = true
|
|
if currentTab.tabId == tabId then
|
|
selectTab(tabs[currentTab.previousTabId])
|
|
end
|
|
redrawMenu()
|
|
end
|
|
end
|
|
|
|
function multishell.unhideTab(tabId)
|
|
local tab = tabs[tabId]
|
|
if tab then
|
|
tab.hidden = false
|
|
redrawMenu()
|
|
end
|
|
end
|
|
|
|
function multishell.getCount()
|
|
return Util.size(tabs)
|
|
end
|
|
|
|
function multishell.hook(event, fn)
|
|
if type(event) == 'table' then
|
|
for _,v in pairs(event) do
|
|
multishell.hook(v, fn)
|
|
end
|
|
else
|
|
if not hooks[event] then
|
|
hooks[event] = { }
|
|
end
|
|
table.insert(hooks[event], fn)
|
|
end
|
|
end
|
|
|
|
-- you can only unhook from within the function that hooked
|
|
function multishell.unhook(event, fn)
|
|
local eventHooks = hooks[event]
|
|
if eventHooks then
|
|
Util.removeByValue(eventHooks, fn)
|
|
if #eventHooks == 0 then
|
|
hooks[event] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
multishell.hook('multishell_terminate', function(_, eventData)
|
|
local tabId = eventData[1] or -1
|
|
local tab = tabs[tabId]
|
|
|
|
if tab and not tab.isOverview then
|
|
if coroutine.status(tab.co) ~= 'dead' then
|
|
resumeTab(tab, "terminate")
|
|
end
|
|
end
|
|
return true
|
|
end)
|
|
|
|
multishell.hook('multishell_redraw', function()
|
|
tabsDirty = false
|
|
|
|
local function write(x, text, bg, fg)
|
|
parentTerm.setBackgroundColor(bg)
|
|
parentTerm.setTextColor(fg)
|
|
parentTerm.setCursorPos(x, 1)
|
|
parentTerm.write(text)
|
|
end
|
|
|
|
local bg = _colors.tabBarBackgroundColor
|
|
parentTerm.setBackgroundColor(bg)
|
|
parentTerm.setCursorPos(1, 1)
|
|
parentTerm.clearLine()
|
|
|
|
local function compareTab(a, b)
|
|
return a.tabId < b.tabId
|
|
end
|
|
|
|
for _,tab in pairs(tabs) do
|
|
if tab.hidden and tab ~= currentTab then
|
|
tab.width = 0
|
|
else
|
|
tab.width = #tab.title + 1
|
|
end
|
|
end
|
|
|
|
local function width()
|
|
local tw = 0
|
|
Util.each(tabs, function(t) tw = tw + t.width end)
|
|
return tw
|
|
end
|
|
|
|
while width() > w - 3 do
|
|
local tab = select(2,
|
|
Util.spairs(tabs, function(a, b) return a.width > b.width end)())
|
|
tab.width = tab.width - 1
|
|
end
|
|
|
|
local tabX = 0
|
|
for _,tab in Util.spairs(tabs, compareTab) do
|
|
if tab.width > 0 then
|
|
tab.sx = tabX + 1
|
|
tab.ex = tabX + tab.width
|
|
tabX = tabX + tab.width
|
|
if tab ~= currentTab then
|
|
write(tab.sx, tab.title:sub(1, tab.width - 1),
|
|
_colors.backgroundColor, _colors.textColor)
|
|
end
|
|
end
|
|
end
|
|
|
|
if currentTab then
|
|
write(currentTab.sx - 1,
|
|
' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ',
|
|
_colors.focusBackgroundColor, _colors.focusTextColor)
|
|
if not currentTab.isOverview then
|
|
write(w, closeInd, _colors.backgroundColor, _colors.focusTextColor)
|
|
end
|
|
end
|
|
|
|
if currentTab then
|
|
currentTab.window.restoreCursor()
|
|
end
|
|
|
|
return true
|
|
end)
|
|
|
|
multishell.hook('term_resize', function(_, eventData)
|
|
if not eventData[1] then --- TEST
|
|
w,h = parentTerm.getSize()
|
|
|
|
local windowHeight = h-1
|
|
|
|
for _,key in pairs(Util.keys(tabs)) do
|
|
local tab = tabs[key]
|
|
local x,y = tab.window.getCursorPos()
|
|
if y > windowHeight then
|
|
tab.window.scroll(y - windowHeight)
|
|
tab.window.setCursorPos(x, windowHeight)
|
|
end
|
|
tab.window.reposition(1, 2, w, windowHeight)
|
|
end
|
|
|
|
redrawMenu()
|
|
end
|
|
end)
|
|
|
|
-- downstate should be stored in the tab (maybe)
|
|
multishell.hook('key_up', function(_, eventData)
|
|
local code = eventData[1]
|
|
|
|
if downState[code] ~= currentTab then
|
|
downState[code] = nil
|
|
return true
|
|
end
|
|
downState[code] = nil
|
|
end)
|
|
|
|
multishell.hook('key', function(_, eventData)
|
|
local code = eventData[1]
|
|
local firstPress = not eventData[2]
|
|
|
|
if firstPress then
|
|
downState[code] = currentTab
|
|
else
|
|
--key was pressed initially in a previous window
|
|
if downState[code] ~= currentTab then
|
|
return true
|
|
end
|
|
end
|
|
end)
|
|
|
|
multishell.hook({ 'key', 'key_up', 'char', 'paste' }, function(event, eventData)
|
|
local code = Input:translate(event, eventData[1], eventData[2])
|
|
|
|
if code and hotkeys[code] then
|
|
hotkeys[code](event, eventData)
|
|
end
|
|
end)
|
|
|
|
multishell.hook('mouse_click', function(_, eventData)
|
|
local x, y = eventData[2], eventData[3]
|
|
if y == 1 then
|
|
if x == 1 then
|
|
multishell.setFocus(overviewId)
|
|
elseif x == w then
|
|
if currentTab then
|
|
multishell.terminate(currentTab.tabId)
|
|
end
|
|
else
|
|
for _,tab in pairs(tabs) do
|
|
if not tab.hidden and tab.sx then
|
|
if x >= tab.sx and x <= tab.ex then
|
|
multishell.setFocus(tab.tabId)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
downState.mouse = nil
|
|
return true
|
|
end
|
|
downState.mouse = currentTab
|
|
eventData[3] = eventData[3] - 1
|
|
end)
|
|
|
|
multishell.hook({ 'mouse_up', 'mouse_drag' }, function(event, eventData)
|
|
if downState.mouse ~= currentTab then
|
|
-- don't send mouse up as the mouse click event was on another window
|
|
if event == 'mouse_up' then
|
|
downState.mouse = nil
|
|
end
|
|
|
|
return true -- stop propagation
|
|
end
|
|
eventData[3] = eventData[3] - 1
|
|
end)
|
|
|
|
multishell.hook('mouse_scroll', function(_, eventData)
|
|
local dir, y = eventData[1], eventData[3]
|
|
|
|
if y == 1 then
|
|
return true
|
|
end
|
|
|
|
if currentTab.terminal.scrollUp then
|
|
if dir == -1 then
|
|
currentTab.terminal.scrollUp()
|
|
else
|
|
currentTab.terminal.scrollDown()
|
|
end
|
|
end
|
|
|
|
eventData[3] = y - 1
|
|
end)
|
|
|
|
local function startup()
|
|
local success = true
|
|
|
|
local function runDir(directory, open)
|
|
if not fs.exists(directory) then
|
|
return true
|
|
end
|
|
|
|
local files = fs.list(directory)
|
|
table.sort(files)
|
|
|
|
for _,file in ipairs(files) do
|
|
os.sleep(0)
|
|
local result, err = open(directory .. '/' .. file)
|
|
if result then
|
|
if term.isColor() then
|
|
term.setTextColor(colors.green)
|
|
end
|
|
term.write('[PASS] ')
|
|
term.setTextColor(colors.white)
|
|
term.write(fs.combine(directory, file))
|
|
else
|
|
if term.isColor() then
|
|
term.setTextColor(colors.red)
|
|
end
|
|
term.write('[FAIL] ')
|
|
term.setTextColor(colors.white)
|
|
term.write(fs.combine(directory, file))
|
|
if err then
|
|
_G.printError(err)
|
|
end
|
|
success = false
|
|
end
|
|
print()
|
|
end
|
|
end
|
|
|
|
runDir('sys/services', shell.openHiddenTab)
|
|
runDir('sys/autorun', shell.run)
|
|
runDir('usr/autorun', shell.run)
|
|
|
|
if not success then
|
|
print()
|
|
error('An autorun program has errored')
|
|
end
|
|
end
|
|
|
|
overviewId = multishell.openTab({
|
|
path = 'sys/apps/Overview.lua',
|
|
isOverview = true,
|
|
})
|
|
tabs[overviewId].title = '+'
|
|
|
|
multishell.openTab({
|
|
focused = true,
|
|
fn = startup,
|
|
title = 'Autorun',
|
|
})
|
|
|
|
local currentTabEvents = Util.transpose {
|
|
'char', 'key', 'key_up',
|
|
'mouse_click', 'mouse_drag', 'mouse_scroll', 'mouse_up',
|
|
'paste', 'terminate',
|
|
}
|
|
|
|
while true do
|
|
local tEventData = { os.pullEventRaw() }
|
|
local sEvent = table.remove(tEventData, 1)
|
|
local stopPropagation
|
|
|
|
local eventHooks = hooks[sEvent]
|
|
if eventHooks then
|
|
for i = #eventHooks, 1, -1 do
|
|
stopPropagation = eventHooks[i](sEvent, tEventData)
|
|
if stopPropagation then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if not stopPropagation then
|
|
if currentTabEvents[sEvent] then
|
|
resumeTab(currentTab, sEvent, tEventData)
|
|
|
|
else
|
|
-- Passthrough to all processes
|
|
for _,key in pairs(Util.keys(tabs)) do
|
|
resumeTab(tabs[key], sEvent, tEventData)
|
|
end
|
|
end
|
|
end
|
|
end
|