-- Implements Cuberite interpreter description and interface for ZBStudio. -- Cuberite executable can have a postfix depending on the compilation mode (debug / release). local function MakeCuberiteInterpreter(a_Self, a_InterpreterPostfix, a_ExePostfix) assert(a_Self) assert(type(a_InterpreterPostfix) == "string") assert(type(a_ExePostfix) == "string") return { name = "Cuberite" .. a_InterpreterPostfix, description = "Cuberite - the custom C++ minecraft server", api = { "baselib", "mcserver_api", -- Keep the old MCServer name for compatibility reasons "cuberite_api" }, frun = function(self, wfilename, withdebug) -- Cuberite plugins are always in a "Plugins/" subfolder located at the executable level -- Get to the executable by removing the last two dirs: local ExePath = wx.wxFileName(wfilename) ExePath:RemoveLastDir() ExePath:RemoveLastDir() ExePath:ClearExt() ExePath:SetName("") local ExeName = wx.wxFileName(ExePath) -- The executable name depends on the debug / non-debug build mode, it can have a postfix ExeName:SetName("Cuberite" .. a_ExePostfix) -- Executable has a .exe ext on Windows if (ide.osname == 'Windows') then ExeName:SetExt("exe") end -- Check if we're in a subfolder inside the plugin folder, try going up one level if executable not found: if not(ExeName:FileExists()) then ide:GetOutput():Write("The Cuberite executable cannot be found in \"" .. ExeName:GetFullPath() .. "\". Trying one folder up.\n") ExeName:RemoveLastDir() ExePath:RemoveLastDir() if not(ExeName:FileExists()) then ide:GetOutput():Write("The Cuberite executable cannot be found in \"" .. ExeName:GetFullPath() .. "\". Aborting the debugger.\n") return end end -- Start the debugger server: if withdebug then DebuggerAttachDefault({ runstart = (ide.config.debugger.runonstart == true), basedir = ExePath:GetFullPath(), }) end -- Add a "nooutbuf" cmdline param to the server, causing it to call setvbuf to disable output buffering: local Cmd = ExeName:GetFullPath() .. " --no-output-buffering" -- Force ZBS not to hide Cuberite window, save and restore previous state: local SavedUnhideConsoleWindow = ide.config.unhidewindow.ConsoleWindowClass ide.config.unhidewindow.ConsoleWindowClass = 1 -- show if hidden -- Create the !EnableMobDebug.lua file so that the Cuberite plugin starts the debugging session, when loaded: local EnablerPath = wx.wxFileName(wfilename) EnablerPath:SetName("!EnableMobDebug") EnablerPath:SetExt("lua") local f = io.open(EnablerPath:GetFullPath(), "w") if (f ~= nil) then f:write( [[ -- !EnableMobDebug.lua -- This file is created by the ZeroBrane Studio debugger, do NOT commit it to your repository! -- It is safe to delete this file once the debugger is stopped. -- When this file is loaded in the ZeroBrane Studio, the debugger will pause when Cuberite detects a problem in your plugin -- If you close this file, the debugger will no longer pause on problems local g_mobdebug = require("mobdebug") g_mobdebug.start() function BreakIntoDebugger(a_Message) g_mobdebug:pause() -- If your plugin breaks here, it means that Cuberite has run into a problem in your plugin -- Inspect the stack and the server console for the error report -- If you close this file while the debugger is paused here, Cuberite will be terminated! LOG("Broken into debugger: " .. a_Message) end ]] ) f:close() end -- Open the "!EnableMobDebug.lua" file in the editor, if not already open (so that breakpoints work): local enablerEditor local fullEnablerPath = EnablerPath:GetFullPath() if not(ide:FindDocument(fullEnablerPath)) then enablerEditor = LoadFile(fullEnablerPath) end -- When the enabler gets closed, invalidate our enablerEditor variable: a_Self.onEditorClose = function(self, a_Editor) if (a_Editor == enablerEditor) then enablerEditor = nil end end -- Create the closure to call upon debugging finish: local OnFinished = function() -- Close the "!EnableMobDebug.lua" file editor: if (enablerEditor) then ide:GetDocument(enablerEditor):Close() end -- Remove the editor-close watcher: a_Self.onEditorClose = nil -- Restore the Unhide status: ide.config.unhidewindow.ConsoleWindowClass = SavedUnhideConsoleWindow -- Remove the !EnableMobDebug.lua file: os.remove(EnablerPath:GetFullPath()) end -- Run the server: local pid = CommandLineRun( Cmd, -- Command to run ExePath:GetFullPath(), -- Working directory for the debuggee false, -- Redirect debuggee output to Output pane? (NOTE: This force-hides the Cuberite window, not desirable!) true, -- Add a no-hide flag to WX nil, -- StringCallback, whatever that is nil, -- UID to identify this running program; nil to auto-assign OnFinished -- Callback to call once the debuggee terminates ) end, hasdebugger = true, } end local function analyzeProject() local projectPath = ide:GetProject() if not(projectPath) then ide:GetOutput():Write("No project path has been defined.\n") return end -- Get a list of all the files in the order in which Cuberite loads them (Info.lua is always last): local files = {} for _, filePath in ipairs(ide:GetFileList(projectPath, false, "*.lua")) do table.insert(files, filePath) end table.sort(files, function (a_File1, a_File2) if (a_File1:match("[/\\]Info.lua")) then return false elseif (a_File2:match("[/\\]Info.lua")) then return true else return a_File1 < a_File2 end end ) -- List all files in the console: ide:GetOutput():Write("Files for analysis:\n") for _, file in ipairs(files) do ide:GetOutput():Write(file .. "\n") end ide:GetOutput():Write("Analyzing...\n") -- Concatenate all the files, remember their line begin positions: local lineBegin = {} -- array of {File = "filename", LineBegin = , LineEnd = } local whole = {} -- array of individual files' contents local curLineBegin = 1 for _, file in ipairs(files) do local curFile = { "do" } local lastLineNum = 0 for line in io.lines(file) do table.insert(curFile, line) lastLineNum = lastLineNum + 1 end table.insert(curFile, "end") table.insert(lineBegin, {File = file, LineBegin = curLineBegin + 1, LineEnd = curLineBegin + lastLineNum + 1}) curLineBegin = curLineBegin + lastLineNum + 2 table.insert(whole, table.concat(curFile, "\n")) end -- Analyze the concatenated files: local warn, err, line, pos = ide:AnalyzeString(table.concat(whole, "\n")) if (err) then ide:GetOutput():Write("Error: " .. err .. "\n") return end -- Function that translates concatenated-linenums back into source + linenum local function findSourceByLine(a_LineNum) for _, begin in ipairs(lineBegin) do if (a_LineNum < begin.LineEnd) then return begin.File, a_LineNum - begin.LineBegin + 1 end end end -- Parse the analysis results back to original files: for _, w in ipairs(warn) do local wtext = w:gsub("^:(%d*):(.*)", function (a_LineNum, a_Message) local srcFile, srcLineNum = findSourceByLine(tonumber(a_LineNum)) ide:GetOutput():Write(srcFile .. ":" .. srcLineNum .. ": " .. a_Message .. "\n") end ) end ide:GetOutput():Write("Analysis completed.\n") end local function runInfoDump() local projectPath = ide:GetProject() if not(projectPath) then ide:GetOutput():Write("No project path has been defined.\n") return end -- Get the path to InfoDump.lua file, that is one folder up from the current project: local dumpScriptPath = wx.wxFileName(projectPath) local pluginName = dumpScriptPath:GetDirs()[#dumpScriptPath:GetDirs()] dumpScriptPath:RemoveLastDir() local dumpScript = wx.wxFileName(dumpScriptPath) dumpScript:SetName("InfoDump") dumpScript:SetExt("lua") local fullPath = dumpScript:GetFullPath() if not(wx.wxFileExists(fullPath)) then ide:GetOutput():Write("The InfoDump.lua script was not found (tried " .. fullPath .. ")\n") return end -- Execute the script: local cmd = "lua " .. fullPath .. " " .. pluginName CommandLineRun( cmd, -- Command to run dumpScriptPath:GetFullPath(), -- Working directory for the debuggee true, -- Redirect debuggee output to Output pane? true -- Add a no-hide flag to WX ) ide:GetOutput():Write("The InfoDump.lua script was executed.\n") end return { name = "Cuberite integration", description = "Implements integration with Cuberite - the custom C++ minecraft server.", author = "Mattes D (https://github.com/madmaxoft)", version = 0.55, dependencies = "1.70", AnalysisMenuID = ID("analyze.cuberite_analyzeall"), InfoDumpMenuID = ID("project.cuberite_infodump"), onRegister = function(self) -- Add the interpreters self.InterpreterDebug = MakeCuberiteInterpreter(self, " - debug mode", "_debug") self.InterpreterRelease = MakeCuberiteInterpreter(self, " - release mode", "") ide:AddInterpreter("cuberite_debug", self.InterpreterDebug) ide:AddInterpreter("cuberite_release", self.InterpreterRelease) -- Add the analysis menu item: local _, menu, pos = ide:FindMenuItem(ID.ANALYZE) if pos then menu:Insert(pos + 1, self.AnalysisMenuID, TR("Analyze as Cuberite") .. KSC(id), TR("Analyze the project source code as Cuberite")) ide:GetMainFrame():Connect(self.AnalysisMenuID, wx.wxEVT_COMMAND_MENU_SELECTED, analyzeProject) end -- Add the InfoDump menu item: _, menu, pos = ide:FindMenuItem(ID.INTERPRETER) if (pos) then self.Separator1 = menu:AppendSeparator() menu:Append(self.InfoDumpMenuID, TR("Cuberite InfoDump") .. KSC(id), TR("Run the InfoDump script on the current plugin")) ide:GetMainFrame():Connect(self.InfoDumpMenuID, wx.wxEVT_COMMAND_MENU_SELECTED, runInfoDump) end end, onUnRegister = function(self) -- Remove the interpreters: ide:RemoveInterpreter("cuberite_debug") ide:RemoveInterpreter("cuberite_release") -- Remove the menu items: ide:RemoveMenuItem(self.AnalysisMenuID) ide:RemoveMenuItem(self.InfoDumpMenuID) self.Separator1:GetParent():Delete(self.Separator1) end, }