opus/sys/init/7.multishell.lua

378 lines
8.4 KiB
Lua
Raw Permalink Normal View History

local Blit = require('opus.ui.blit')
local Config = require('opus.config')
local Util = require('opus.util')
2016-12-11 19:24:52 +00:00
2017-10-08 21:45:01 +00:00
local colors = _G.colors
local fs = _G.fs
local kernel = _G.kernel
2017-10-08 21:45:01 +00:00
local keys = _G.keys
local os = _G.os
local printError = _G.printError
local window = _G.window
2017-10-05 17:07:48 +00:00
2018-01-21 10:44:13 +00:00
local parentTerm = _G.device.terminal
2016-12-11 19:24:52 +00:00
local w,h = parentTerm.getSize()
2017-10-16 20:54:50 +00:00
local overviewId
2016-12-11 19:24:52 +00:00
local tabsDirty = false
2018-01-12 01:53:32 +00:00
local closeInd = Util.getVersion() >= 1.76 and '\215' or '*'
2018-01-14 23:28:23 +00:00
local multishell = { }
_ENV.multishell = multishell
2017-10-08 21:45:01 +00:00
2020-05-10 20:04:20 +00:00
kernel.window.reposition(1, 2, w, h - 1)
2016-12-11 19:24:52 +00:00
local config = {
2018-01-24 22:39:38 +00:00
standard = {
textColor = colors.lightGray,
tabBarTextColor = colors.lightGray,
focusTextColor = colors.white,
backgroundColor = colors.gray,
tabBarBackgroundColor = colors.gray,
focusBackgroundColor = colors.gray,
2018-10-31 04:05:29 +00:00
errorColor = colors.black,
2018-01-24 22:39:38 +00:00
},
color = {
textColor = colors.lightGray,
tabBarTextColor = colors.lightGray,
focusTextColor = colors.white,
backgroundColor = colors.gray,
tabBarBackgroundColor = colors.gray,
focusBackgroundColor = colors.gray,
2018-10-31 04:05:29 +00:00
errorColor = colors.red,
2018-01-24 22:39:38 +00:00
},
2016-12-11 19:24:52 +00:00
}
Config.load('multishell', config)
2018-01-12 01:53:32 +00:00
local _colors = parentTerm.isColor() and config.color or config.standard
local palette = parentTerm.isColor() and Blit.colorPalette or Blit.grayscalePalette
2016-12-11 19:24:52 +00:00
local function redrawMenu()
2018-01-24 22:39:38 +00:00
if not tabsDirty then
os.queueEvent('multishell_redraw')
tabsDirty = true
end
2016-12-11 19:24:52 +00:00
end
function multishell.getFocus()
2018-01-24 22:39:38 +00:00
local currentTab = kernel.getFocused()
return currentTab.uid
2016-12-11 19:24:52 +00:00
end
function multishell.setFocus(tabId)
2018-01-24 22:39:38 +00:00
return kernel.raise(tabId)
2016-12-11 19:24:52 +00:00
end
function multishell.getTitle(tabId)
2018-01-24 22:39:38 +00:00
local tab = kernel.find(tabId)
return tab and tab.title
2016-12-11 19:24:52 +00:00
end
2017-10-15 23:55:05 +00:00
function multishell.setTitle(tabId, title)
2018-01-24 22:39:38 +00:00
local tab = kernel.find(tabId)
if tab then
tab.title = title
redrawMenu()
end
2016-12-11 19:24:52 +00:00
end
function multishell.getCurrent()
2018-01-24 22:39:38 +00:00
local runningTab = kernel.getCurrent()
return runningTab and runningTab.uid
2016-12-11 19:24:52 +00:00
end
function multishell.getTab(tabId)
2018-01-24 22:39:38 +00:00
return kernel.find(tabId)
2016-12-11 19:24:52 +00:00
end
function multishell.terminate(tabId)
2018-01-24 22:39:38 +00:00
os.queueEvent('multishell_terminate', tabId)
2016-12-11 19:24:52 +00:00
end
function multishell.getTabs()
2018-01-24 22:39:38 +00:00
return kernel.routines
2016-12-11 19:24:52 +00:00
end
function multishell.launch(env, path, ...)
2018-01-24 22:39:38 +00:00
-- backwards compatibility
return multishell.openTab(env, {
path = path,
2018-01-24 22:39:38 +00:00
args = { ... },
})
2016-12-11 19:24:52 +00:00
end
2020-05-31 02:05:53 +00:00
local function chain(orig, fn)
if not orig then
return fn
end
if type(orig) == 'table' then
table.insert(orig, fn)
return orig
end
return setmetatable({ orig, fn }, {
__call = function(self, ...)
for _,v in pairs(self) do
v(...)
end
end
})
end
function multishell.openTab(env, tab)
2018-01-24 22:39:38 +00:00
if not tab.title and tab.path then
tab.title = fs.getName(tab.path):match('([^%.]+)')
end
tab.title = tab.title or 'untitled'
tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false)
2020-05-31 02:05:53 +00:00
-- require('opus.terminal').window(parentTerm, 1, 2, w, h - 1, false)
tab.onExit = chain(tab.onExit, function(self, result, err, stack)
2020-05-13 03:25:37 +00:00
if not result and err and err ~= 'Terminated' then
self.terminal.setTextColor(colors.white)
self.terminal.setCursorBlink(false)
print('\nThe program terminated with an error.\n')
2019-04-07 14:09:47 +00:00
if tonumber(err) then
2020-05-13 03:25:37 +00:00
printError('Process exited with error code: ' .. err)
2019-04-07 14:09:47 +00:00
elseif err then
2018-01-24 22:39:38 +00:00
printError(tostring(err))
end
2020-05-13 03:25:37 +00:00
if type(stack) == 'table' and #stack > 0 then
local _, cy = self.terminal.getCursorPos()
local _, th = self.terminal.getSize()
self.terminal.setTextColor(colors.white)
if cy < th - 4 then
print('\nstack traceback:')
for _, v in ipairs(stack or { }) do
_, cy = self.terminal.getCursorPos()
if cy > th - 3 then
print(' ...')
break
end
print(v)
end
end
end
self.terminal.setTextColor(parentTerm.isColor() and colors.yellow or colors.white)
_G.write('\nPress enter to close')
2020-05-09 04:32:44 +00:00
self.isDead = true
self.hidden = false
redrawMenu()
2018-01-24 22:39:38 +00:00
while true do
local e, code = os.pullEventRaw('key')
if e == 'terminate' or e == 'key' and code == keys.enter then
break
end
end
end
2020-05-31 02:05:53 +00:00
end)
2018-01-24 22:39:38 +00:00
local routine, message = kernel.run(env, tab)
2018-01-24 22:39:38 +00:00
if routine then
if tab.focused then
multishell.setFocus(routine.uid)
else
redrawMenu()
end
2018-01-24 22:39:38 +00:00
end
return routine and routine.uid, message
2016-12-11 19:24:52 +00:00
end
function multishell.hideTab(tabId)
2018-01-24 22:39:38 +00:00
local tab = kernel.find(tabId)
if tab then
tab.hidden = true
kernel.lower(tab.uid)
redrawMenu()
end
2016-12-11 19:24:52 +00:00
end
function multishell.unhideTab(tabId)
2018-01-24 22:39:38 +00:00
local tab = kernel.find(tabId)
if tab then
tab.hidden = false
redrawMenu()
end
2016-12-11 19:24:52 +00:00
end
function multishell.getCount()
2018-01-24 22:39:38 +00:00
return #kernel.routines
2016-12-11 19:24:52 +00:00
end
2019-03-27 19:21:31 +00:00
kernel.hook('kernel_focus', function()
2018-01-24 22:39:38 +00:00
redrawMenu()
2018-01-12 01:53:32 +00:00
end)
kernel.hook('multishell_terminate', function(_, eventData)
2018-01-24 22:39:38 +00:00
local tab = kernel.find(eventData[1])
if tab and not tab.isOverview then
if coroutine.status(tab.co) ~= 'dead' then
tab:resume("terminate")
end
end
return true
end)
kernel.hook('terminate', function()
return kernel.getFocused().isOverview
2016-12-11 19:24:52 +00:00
end)
kernel.hook('multishell_redraw', function()
2018-01-24 22:39:38 +00:00
tabsDirty = false
local blit = Blit(w, {
bg = _colors.tabBarBackgroundColor,
fg = _colors.textColor,
palette = palette,
})
2018-01-24 22:39:38 +00:00
local currentTab = kernel.getFocused()
for _,tab in pairs(kernel.routines) 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(kernel.routines, function(t) tw = tw + t.width end)
return tw
end
while width() > w - 3 do
local tab = select(2,
Util.spairs(kernel.routines, function(a, b) return a.width > b.width end)())
tab.width = tab.width - 1
end
local function compareTab(a, b)
if a.hidden then return false end
return b.hidden or a.uid < b.uid
end
local tabX = 0
for _,tab in Util.spairs(kernel.routines, compareTab) do
if tab.width > 0 then
tab.sx = tabX + 1
tab.ex = tabX + tab.width
tabX = tabX + tab.width
if tab ~= currentTab then
local textColor = tab.isDead and _colors.errorColor or _colors.textColor
blit:write(tab.sx, tab.title:sub(1, tab.width - 1),
_colors.backgroundColor, textColor)
2018-01-24 22:39:38 +00:00
end
end
end
if currentTab then
if currentTab.sx then
2020-05-09 04:32:44 +00:00
local textColor = currentTab.isDead and _colors.errorColor or _colors.focusTextColor
blit:write(currentTab.sx - 1,
' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ',
2020-05-09 04:32:44 +00:00
_colors.focusBackgroundColor, textColor)
end
2019-11-01 22:35:58 +00:00
if not currentTab.noTerminate then
blit:write(w, closeInd, nil, _colors.focusTextColor)
2018-01-24 22:39:38 +00:00
end
end
parentTerm.setCursorPos(1, 1)
parentTerm.blit(blit.text, blit.fg, blit.bg)
2018-01-24 22:39:38 +00:00
if currentTab and currentTab.window then
currentTab.window.restoreCursor()
end
return true
2017-10-15 06:36:54 +00:00
end)
kernel.hook('term_resize', function(_, eventData)
2018-01-24 22:39:38 +00:00
if not eventData[1] then --- TEST
w,h = parentTerm.getSize()
local windowHeight = h-1
for _,key in pairs(Util.keys(kernel.routines)) do
local tab = kernel.routines[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
2017-10-15 06:36:54 +00:00
end)
kernel.hook('mouse_click', function(_, eventData)
if not eventData[4] then
local x, y = eventData[2], eventData[3]
if y == 1 then
if x == 1 then
multishell.setFocus(overviewId)
elseif x == w then
local currentTab = kernel.getFocused()
if currentTab then
multishell.terminate(currentTab.uid)
end
else
for _,tab in pairs(kernel.routines) do
if not tab.hidden and tab.sx then
if x >= tab.sx and x <= tab.ex then
multishell.setFocus(tab.uid)
break
end
2018-01-24 22:39:38 +00:00
end
end
end
return true
2018-01-24 22:39:38 +00:00
end
eventData[3] = eventData[3] - 1
2018-01-24 22:39:38 +00:00
end
2017-10-15 06:36:54 +00:00
end)
2018-01-12 01:53:32 +00:00
kernel.hook({ 'mouse_up', 'mouse_drag' }, function(_, eventData)
if not eventData[4] then
eventData[3] = eventData[3] - 1
end
2017-10-15 06:36:54 +00:00
end)
kernel.hook('mouse_scroll', function(_, eventData)
if not eventData[4] then
if eventData[3] == 1 then
return true
end
eventData[3] = eventData[3] - 1
2018-01-24 22:39:38 +00:00
end
2017-10-15 06:36:54 +00:00
end)
2018-01-15 21:28:10 +00:00
kernel.hook('kernel_ready', function()
overviewId = multishell.openTab(_ENV, {
path = 'sys/apps/shell.lua',
args = { config.launcher or 'sys/apps/Overview.lua' },
2018-01-24 22:39:38 +00:00
isOverview = true,
2019-11-01 22:35:58 +00:00
noTerminate = true,
2018-01-24 22:39:38 +00:00
focused = true,
title = '+',
onExit = function(_, s, m)
if not s then
kernel.halt(s, m)
end
end,
2018-01-24 22:39:38 +00:00
})
multishell.setTitle(overviewId, '+')
2018-01-24 22:39:38 +00:00
multishell.openTab(_ENV, {
2019-03-27 19:21:31 +00:00
path = 'sys/apps/shell.lua',
args = { 'sys/apps/autorun.lua' },
2018-01-24 22:39:38 +00:00
title = 'Autorun',
})
2018-01-15 21:28:10 +00:00
end)