-- configs local LogColors = { [LogInfo] = 'white', [LogWarning] = 'yellow', [LogError] = 'red' } local MaxLogLines = 80 local LabelHeight = 16 local MaxHistory = 1000 -- private variables local terminalWindow local terminalButton local logLocked = false local commandEnv = {} setmetatable(commandEnv, { __index = getfenv() } ) local commandTextEdit local terminalBuffer local commandHistory = { } local currentHistoryIndex = 0 -- private functions local function navigateCommand(step) local numCommands = #commandHistory if numCommands > 0 then currentHistoryIndex = math.min(math.max(currentHistoryIndex + step, 0), numCommands) if currentHistoryIndex > 0 then local command = commandHistory[numCommands - currentHistoryIndex + 1] commandTextEdit:setText(command) commandTextEdit:setCursorPos(-1) else commandTextEdit:clearText() end end end local function completeCommand() local cursorPos = commandTextEdit:getCursorPos() if cursorPos == 0 then return end local commandBegin = commandTextEdit:getText():sub(1, cursorPos) local possibleCommands = {} -- create a list containing all globals local allVars = table.copy(_G) table.merge(allVars, commandEnv) -- match commands for k,v in pairs(allVars) do if k:sub(1, cursorPos) == commandBegin then table.insert(possibleCommands, k) end end -- complete command with one match if #possibleCommands == 1 then commandTextEdit:setText(possibleCommands[1]) commandTextEdit:setCursorPos(-1) -- show command matches elseif #possibleCommands > 0 then print('>> ' .. commandBegin) -- expand command local expandedComplete = commandBegin local done = false while not done do cursorPos = #commandBegin+1 if #possibleCommands[1] < cursorPos then break end expandedComplete = commandBegin .. possibleCommands[1]:sub(cursorPos, cursorPos) for i,v in ipairs(possibleCommands) do if v:sub(1, #expandedComplete) ~= expandedComplete then done = true end end if not done then commandBegin = expandedComplete end end commandTextEdit:setText(commandBegin) commandTextEdit:setCursorPos(-1) for i,v in ipairs(possibleCommands) do print(v) end end end local function doCommand() local currentCommand = commandTextEdit:getText() executeCommand(currentCommand) if commandTextEdit then commandTextEdit:clearText() end return true end local function onLog(level, message, time) -- debug messages are ignored if level == LogDebug then return end -- avoid logging while reporting logs (would cause a infinite loop) if logLocked then return end logLocked = true addLine(message, LogColors[level]) logLocked = false end -- public functions function init() terminalWindow = g_ui.displayUI('terminal') terminalWindow:setVisible(false) local poped = false terminalWindow.onDoubleClick = function(self) if poped then self:fill('parent') self:getChildById('bottomResizeBorder'):disable() self:getChildById('rightResizeBorder'):disable() poped = false else self:breakAnchors() self:resize(g_window.getWidth()/2, g_window.getHeight()/2) self:move(g_window.getWidth()/2, g_window.getHeight()/2) self:getChildById('bottomResizeBorder'):enable() self:getChildById('rightResizeBorder'):enable() poped = true end end terminalButton = modules.client_topmenu.addLeftButton('terminalButton', tr('Terminal') .. ' (Ctrl + T)', '/images/topbuttons/terminal', toggle) g_keyboard.bindKeyDown('Ctrl+T', toggle) commandHistory = g_settings.getList('terminal-history') commandTextEdit = terminalWindow:getChildById('commandTextEdit') g_keyboard.bindKeyPress('Up', function() navigateCommand(1) end, commandTextEdit) g_keyboard.bindKeyPress('Down', function() navigateCommand(-1) end, commandTextEdit) g_keyboard.bindKeyDown('Tab', completeCommand, commandTextEdit) g_keyboard.bindKeyDown('Enter', doCommand, commandTextEdit) g_keyboard.bindKeyDown('Escape', hide, terminalWindow) terminalBuffer = terminalWindow:getChildById('terminalBuffer') g_logger.setOnLog(onLog) g_logger.fireOldMessages() end function terminate() g_settings.setList('terminal-history', commandHistory) g_keyboard.unbindKeyDown('Ctrl+T') g_logger.setOnLog(nil) commandTextEdit = nil terminalBuffer = nil terminalWindow:destroy() terminalWindow = nil commandEnv = nil end function hideButton() terminalButton:hide() end function toggle() if terminalWindow:isVisible() then hide() else show() end end function show() terminalWindow:show() terminalWindow:raise() terminalWindow:focus() end function hide() terminalWindow:hide() end function disable() terminalButton:hide() g_keyboard.unbindKeyDown('Ctrl+T') end function addLine(text, color) -- delete old lines if needed local numLines = terminalBuffer:getChildCount() + 1 if numLines > MaxLogLines then terminalBuffer:getChildByIndex(1):destroy() end -- create new line label local label = g_ui.createWidget('TerminalLabel', terminalBuffer) label:setId('terminalLabel' .. numLines) label:setText(text) label:setColor(color) end function executeCommand(command) if command == nil or #command == 0 then return end logLocked = true g_logger.log(LogInfo, '> ' .. command) logLocked = false -- detect and convert commands with simple syntax local realCommand if string.sub(command, 1, 1) == '=' then realCommand = 'print(' .. string.sub(command,2) .. ')' else realCommand = command end -- reset current history index currentHistoryIndex = 0 -- add new command to history if #commandHistory == 0 or commandHistory[#commandHistory] ~= command then table.insert(commandHistory, command) if #commandHistory > MaxHistory then table.remove(commandHistory, 1) end end -- add command line --addLine(">> " .. command, "#ffffff") -- load command buffer local func, err = loadstring(realCommand, "@") -- check for syntax errors if not func then g_logger.log(LogError, 'incorrect lua syntax: ' .. err:sub(5)) return end -- setup func env to commandEnv setfenv(func, commandEnv) -- execute the command local ok, ret = pcall(func) if ok then -- if the command returned a value, print it if ret then print(ret) end else g_logger.log(LogError, 'command failed: ' .. ret) end end