summaryrefslogtreecommitdiff
path: root/love2dToAPK/tools/tools/zbstudio-win/src/editor/package.lua
diff options
context:
space:
mode:
Diffstat (limited to 'love2dToAPK/tools/tools/zbstudio-win/src/editor/package.lua')
-rw-r--r--love2dToAPK/tools/tools/zbstudio-win/src/editor/package.lua735
1 files changed, 735 insertions, 0 deletions
diff --git a/love2dToAPK/tools/tools/zbstudio-win/src/editor/package.lua b/love2dToAPK/tools/tools/zbstudio-win/src/editor/package.lua
new file mode 100644
index 0000000..004bcd0
--- /dev/null
+++ b/love2dToAPK/tools/tools/zbstudio-win/src/editor/package.lua
@@ -0,0 +1,735 @@
+-- Copyright 2013-15 Paul Kulchenko, ZeroBrane LLC
+---------------------------------------------------------
+
+local ide = ide
+local iscaseinsensitive = wx.wxFileName("A"):SameAs(wx.wxFileName("a"))
+local unpack = table.unpack or unpack
+local q = EscapeMagic
+
+local function eventHandle(handlers, event, ...)
+ local success
+ for package, handler in pairs(handlers) do
+ local ok, res = pcall(handler, package, ...)
+ if ok then
+ if res == false then success = false end
+ else
+ DisplayOutputLn(TR("%s event failed: %s"):format(event, res))
+ end
+ end
+ return success
+end
+
+local function getEventHandlers(packages, event)
+ local handlers = {}
+ for _, package in pairs(packages) do
+ if package[event] then handlers[package] = package[event] end
+ end
+ return handlers
+end
+
+function PackageEventHandle(event, ...)
+ return eventHandle(getEventHandlers(ide.packages, event), event, ...)
+end
+
+function PackageEventHandleOnce(event, ...)
+ -- copy packages as the event that is handled only once needs to be removed
+ local handlers = getEventHandlers(ide.packages, event)
+ -- remove all handlers as they need to be called only once
+ -- this allows them to be re-installed if needed
+ for _, package in pairs(ide.packages) do package[event] = nil end
+ return eventHandle(handlers, event, ...)
+end
+
+local function PackageEventHandleOne(file, event, ...)
+ local package = ide.packages[file]
+ if package and type(package[event]) == 'function' then
+ local ok, res = pcall(package[event], package, ...)
+ if ok then
+ if res == false then return false end
+ else
+ DisplayOutputLn(TR("%s event failed: %s"):format(event, res))
+ end
+ end
+end
+
+function PackageUnRegister(file, ...)
+ PackageEventHandleOne(file, "onUnRegister", ...)
+ -- remove from the list of installed packages
+ local package = ide.packages[file]
+ ide.packages[file] = nil
+ return package
+end
+
+function PackageRegister(file, ...)
+ if not ide.packages[file] then
+ local packages = {}
+ local package = MergeFullPath(
+ GetPathWithSep(ide.editorFilename), "packages/"..file..".lua")
+ LoadLuaFileExt(packages, package, ide.proto.Plugin)
+ packages[file].fname = file
+ ide.packages[file] = packages[file]
+ end
+ return PackageEventHandleOne(file, "onRegister", ...)
+end
+
+function ide:GetRootPath(path)
+ return MergeFullPath(GetPathWithSep(self.editorFilename), path or '')
+end
+function ide:GetPackagePath(packname)
+ return MergeFullPath(
+ self.oshome and MergeFullPath(self.oshome, '.'..self:GetAppName()..'/') or self:GetRootPath(),
+ MergeFullPath('packages', packname or '')
+ )
+end
+function ide:GetApp() return self.editorApp end
+function ide:GetAppName() return self.appname end
+function ide:GetEditor(index) return GetEditor(index) end
+function ide:GetEditorWithFocus(...) return GetEditorWithFocus(...) end
+function ide:GetEditorWithLastFocus()
+ -- make sure ide.infocus is still a valid component and not "some" userdata
+ return (self:IsValidCtrl(self.infocus)
+ and self.infocus:GetClassInfo():GetClassName() == "wxStyledTextCtrl"
+ and self.infocus:DynamicCast("wxStyledTextCtrl") or nil)
+end
+function ide:GetMenuBar() return self.frame.menuBar end
+function ide:GetStatusBar() return self.frame.statusBar end
+function ide:GetToolBar() return self.frame.toolBar end
+function ide:GetDebugger() return self.debugger end
+function ide:GetMainFrame() return self.frame end
+function ide:GetUIManager() return self.frame.uimgr end
+function ide:GetDocument(ed) return ed and self.openDocuments[ed:GetId()] end
+function ide:GetDocuments() return self.openDocuments end
+function ide:GetKnownExtensions(ext)
+ local knownexts, extmatch = {}, ext and ext:lower()
+ for _, spec in pairs(self.specs) do
+ for _, ext in ipairs(spec.exts or {}) do
+ if not extmatch or extmatch == ext:lower() then
+ table.insert(knownexts, ext)
+ end
+ end
+ end
+ table.sort(knownexts)
+ return knownexts
+end
+
+function ide:DoWhenIdle(func) table.insert(self.onidle, func) end
+
+function ide:FindTopMenu(item)
+ local index = self:GetMenuBar():FindMenu((TR)(item))
+ return self:GetMenuBar():GetMenu(index), index
+end
+function ide:FindMenuItem(itemid, menu)
+ local item, imenu = self:GetMenuBar():FindItem(itemid, menu)
+ if menu and not item then item = menu:FindItem(itemid) end
+ if not item then return end
+ menu = menu or imenu
+
+ for pos = 0, menu:GetMenuItemCount()-1 do
+ if menu:FindItemByPosition(pos):GetId() == itemid then
+ return item, menu, pos
+ end
+ end
+ return
+end
+function ide:AttachMenu(...)
+ -- AttachMenu([targetmenu,] id, submenu)
+ -- `targetmenu` is only needed for menus not attached to the main menubar
+ local menu, id, submenu = ...
+ if select('#', ...) == 2 then menu, id, submenu = nil, ... end
+ local item, menu, pos = self:FindMenuItem(id, menu)
+ if not item then return end
+
+ local menuitem = wx.wxMenuItem(menu, id, item:GetItemLabel(), item:GetHelp(), wx.wxITEM_NORMAL, submenu)
+ menu:Destroy(item)
+ return menu:Insert(pos, menuitem), pos
+end
+function ide:CloneMenu(menu)
+ if not menu then return end
+ local newmenu = wx.wxMenu()
+ local node = menu:GetMenuItems():GetFirst()
+ while node do
+ local item = node:GetData():DynamicCast("wxMenuItem")
+ newmenu:Append(item:GetId(), item:GetItemLabel(), item:GetHelp(), item:GetKind())
+ node = node:GetNext()
+ end
+ return newmenu
+end
+
+function ide:FindDocument(path)
+ local fileName = wx.wxFileName(path)
+ for _, doc in pairs(self:GetDocuments()) do
+ if doc.filePath and fileName:SameAs(wx.wxFileName(doc.filePath)) then
+ return doc
+ end
+ end
+ return
+end
+function ide:FindDocumentsByPartialPath(path)
+ local seps = "[\\/]"
+ -- add trailing path separator to make sure full directory match
+ if not path:find(seps.."$") then path = path .. GetPathSeparator() end
+ local pattern = "^"..q(path):gsub(seps, seps)
+ local lpattern = pattern:lower()
+
+ local docs = {}
+ for _, doc in pairs(self:GetDocuments()) do
+ if doc.filePath
+ and (doc.filePath:find(pattern)
+ or iscaseinsensitive and doc.filePath:lower():find(lpattern)) then
+ table.insert(docs, doc)
+ end
+ end
+ return docs
+end
+function ide:GetInterpreter() return self.interpreter end
+function ide:GetInterpreters() return self.interpreters end
+function ide:GetConfig() return self.config end
+function ide:GetOutput() return self.frame.bottomnotebook.errorlog end
+function ide:GetConsole() return self.frame.bottomnotebook.shellbox end
+function ide:GetEditorNotebook() return self.frame.notebook end
+function ide:GetOutputNotebook() return self.frame.bottomnotebook end
+function ide:GetOutline() return self.outline end
+function ide:GetProjectNotebook() return self.frame.projnotebook end
+function ide:GetProject() return FileTreeGetDir() end
+function ide:GetProjectStartFile()
+ local projectdir = FileTreeGetDir()
+ local startfile = self.filetree.settings.startfile[projectdir]
+ return MergeFullPath(projectdir, startfile), startfile
+end
+function ide:GetLaunchedProcess() return self.debugger and self.debugger.pid end
+function ide:GetProjectTree() return self.filetree.projtreeCtrl end
+function ide:GetOutlineTree() return self.outline.outlineCtrl end
+function ide:GetWatch() return self.debugger and self.debugger.watchCtrl end
+function ide:GetStack() return self.debugger and self.debugger.stackCtrl end
+
+local statusreset
+function ide:SetStatusFor(text, interval, field)
+ field = field or 0
+ interval = interval or 2
+ local statusbar = self:GetStatusBar()
+ if not ide.timers.status then
+ ide.timers.status = wx.wxTimer(statusbar)
+ statusbar:Connect(wx.wxEVT_TIMER, function(event) if statusreset then statusreset() end end)
+ end
+ statusreset = function()
+ if statusbar:GetStatusText(field) == text then statusbar:SetStatusText("", field) end
+ end
+ ide.timers.status:Start(interval*1000, wx.wxTIMER_ONE_SHOT)
+ statusbar:SetStatusText(text, field)
+end
+function ide:SetStatus(text, field) self:GetStatusBar():SetStatusText(text, field or 0) end
+function ide:GetStatus(field) return self:GetStatusBar():GetStatusText(field or 0) end
+function ide:PushStatus(text, field) self:GetStatusBar():PushStatusText(text, field or 0) end
+function ide:PopStatus(field) self:GetStatusBar():PopStatusText(field or 0) end
+function ide:Yield() wx.wxYield() end
+function ide:CreateBareEditor() return CreateEditor(true) end
+
+local rawMethods = {"AddTextDyn", "InsertTextDyn", "AppendTextDyn", "SetTextDyn",
+ "GetTextDyn", "GetLineDyn", "GetSelectedTextDyn", "GetTextRangeDyn"}
+local useraw = nil
+
+function ide:CreateStyledTextCtrl(...)
+ local editor = wxstc.wxStyledTextCtrl(...)
+ if not editor then return end
+
+ if useraw == nil then
+ useraw = true
+ for _, m in ipairs(rawMethods) do
+ if not pcall(function() return editor[m:gsub("Dyn", "Raw")] end) then useraw = false; break end
+ end
+ end
+
+ -- map all `GetTextDyn` to `GetText` or `GetTextRaw` if `*Raw` methods are present
+ editor.useraw = useraw
+ for _, m in ipairs(rawMethods) do
+ -- some `*Raw` methods return `nil` instead of `""` as their "normal" calls do
+ -- (for example, `GetLineRaw` and `GetTextRangeRaw` for parameters outside of text)
+ local def = m:find("^Get") and "" or nil
+ editor[m] = function(...) return editor[m:gsub("Dyn", useraw and "Raw" or "")](...) or def end
+ end
+
+ local suffix = "\1\0"
+ function editor:CopyDyn()
+ if not self.useraw then return self:Copy() end
+ -- check if selected fragment is a valid UTF-8 sequence
+ local text = self:GetSelectedTextRaw()
+ if text == "" or wx.wxString.FromUTF8(text) ~= "" then return self:Copy() end
+ local tdo = wx.wxTextDataObject()
+ -- append suffix as wxwidgets (3.1+ on Windows) truncate last char for odd-length strings
+ local workaround = ide.osname == "Windows" and (#text % 2 > 0) and suffix or ""
+ tdo:SetData(wx.wxDataFormat(wx.wxDF_TEXT), text..workaround)
+ local clip = wx.wxClipboard.Get()
+ clip:Open()
+ clip:SetData(tdo)
+ clip:Close()
+ end
+
+ function editor:PasteDyn()
+ if not self.useraw then return self:Paste() end
+ local tdo = wx.wxTextDataObject()
+ local clip = wx.wxClipboard.Get()
+ clip:Open()
+ clip:GetData(tdo)
+ clip:Close()
+ local ok, text = tdo:GetDataHere(wx.wxDataFormat(wx.wxDF_TEXT))
+ -- check if the fragment being pasted is a valid UTF-8 sequence
+ if not ok or text == "" or wx.wxString.FromUTF8(text) ~= "" then return self:Paste() end
+ if ide.osname == "Windows" then text = text:gsub(suffix.."+$","") end
+ self:AddTextRaw(text)
+ self:GotoPos(self:GetCurrentPos())
+ end
+
+ function editor:GotoPosEnforcePolicy(pos)
+ self:GotoPos(pos)
+ self:EnsureVisibleEnforcePolicy(self:LineFromPosition(pos))
+ end
+
+ function editor:CanFold()
+ local foldable = false
+ for m = 0, ide.MAXMARGIN do
+ if editor:GetMarginWidth(m) > 0
+ and editor:GetMarginMask(m) == wxstc.wxSTC_MASK_FOLDERS then
+ foldable = true
+ end
+ end
+ return foldable
+ end
+
+ -- circle through "fold all" => "hide base lines" => "unfold all"
+ function editor:FoldSome()
+ editor:Colourise(0, -1) -- update doc's folding info
+ local foldall = false -- at least on header unfolded => fold all
+ local hidebase = false -- at least one base is visible => hide all
+ local lines = editor:GetLineCount()
+
+ for ln = 0, lines-1 do
+ local foldRaw = editor:GetFoldLevel(ln)
+ local foldLvl = foldRaw % 4096
+ local foldHdr = (math.floor(foldRaw / 8192) % 2) == 1
+
+ -- at least one header is expanded
+ foldall = foldall or (foldHdr and editor:GetFoldExpanded(ln))
+
+ -- at least one base can be hidden
+ hidebase = hidebase or (
+ not foldHdr
+ and ln > 1 -- first line can't be hidden, so ignore it
+ and foldLvl == wxstc.wxSTC_FOLDLEVELBASE
+ and bit.band(foldRaw, wxstc.wxSTC_FOLDLEVELWHITEFLAG) == 0
+ and editor:GetLineVisible(ln))
+ end
+
+ -- shows lines; this doesn't change fold status for folded lines
+ if not foldall and not hidebase then editor:ShowLines(0, lines-1) end
+
+ for ln = 0, lines-1 do
+ local foldRaw = editor:GetFoldLevel(ln)
+ local foldLvl = foldRaw % 4096
+ local foldHdr = (math.floor(foldRaw / 8192) % 2) == 1
+
+ if foldall then
+ if foldHdr and editor:GetFoldExpanded(ln) then
+ editor:ToggleFold(ln)
+ end
+ elseif hidebase then
+ if not foldHdr and (foldLvl == wxstc.wxSTC_FOLDLEVELBASE) then
+ editor:HideLines(ln, ln)
+ end
+ else -- unfold all
+ if foldHdr and not editor:GetFoldExpanded(ln) then
+ editor:ToggleFold(ln)
+ end
+ end
+ end
+ editor:EnsureCaretVisible()
+ end
+
+ local function getMarginWidth(editor)
+ local width = 0
+ for m = 0, ide.MAXMARGIN do width = width + editor:GetMarginWidth(m) end
+ return width
+ end
+
+ function editor:ShowPosEnforcePolicy(pos)
+ local line = self:LineFromPosition(pos)
+ self:EnsureVisibleEnforcePolicy(line)
+ -- skip the rest if line wrapping is on
+ if editor:GetWrapMode() ~= wxstc.wxSTC_WRAP_NONE then return end
+ local xwidth = self:GetClientSize():GetWidth() - getMarginWidth(self)
+ local xoffset = self:GetTextExtent(self:GetLineDyn(line):sub(1, pos-self:PositionFromLine(line)+1))
+ self:SetXOffset(xoffset > xwidth and xoffset-xwidth or 0)
+ end
+
+ function editor:ClearAny()
+ local length = self:GetLength()
+ local selections = ide.wxver >= "2.9.5" and self:GetSelections() or 1
+ self:Clear() -- remove selected fragments
+
+ -- check if the modification has failed, which may happen
+ -- if there is "invisible" text in the selected fragment.
+ -- if there is only one selection, then delete manually.
+ if length == self:GetLength() and selections == 1 then
+ self:SetTargetStart(self:GetSelectionStart())
+ self:SetTargetEnd(self:GetSelectionEnd())
+ self:ReplaceTarget("")
+ end
+ end
+
+ function editor:MarkerGetAll(mask, from, to)
+ mask = mask or 2^24-1
+ local markers = {}
+ local line = editor:MarkerNext(from or 0, mask)
+ while line > -1 do
+ table.insert(markers, {line, editor:MarkerGet(line)})
+ if to and line > to then break end
+ line = editor:MarkerNext(line + 1, mask)
+ end
+ return markers
+ end
+
+ editor:Connect(wx.wxEVT_KEY_DOWN,
+ function (event)
+ local keycode = event:GetKeyCode()
+ local mod = event:GetModifiers()
+ if (keycode == wx.WXK_DELETE and mod == wx.wxMOD_SHIFT)
+ or (keycode == wx.WXK_INSERT and mod == wx.wxMOD_CONTROL)
+ or (keycode == wx.WXK_INSERT and mod == wx.wxMOD_SHIFT) then
+ local id = keycode == wx.WXK_DELETE and ID.CUT or mod == wx.wxMOD_SHIFT and ID.PASTE or ID.COPY
+ ide.frame:AddPendingEvent(wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, id))
+ elseif keycode == wx.WXK_CAPITAL and mod == wx.wxMOD_CONTROL then
+ -- ignore Ctrl+CapsLock
+ else
+ event:Skip()
+ end
+ end)
+ return editor
+end
+
+function ide:LoadFile(...) return LoadFile(...) end
+
+function ide:CopyToClipboard(text)
+ if wx.wxClipboard:Get():Open() then
+ wx.wxClipboard:Get():SetData(wx.wxTextDataObject(text))
+ wx.wxClipboard:Get():Close()
+ return true
+ end
+ return false
+end
+
+function ide:GetSetting(path, setting)
+ local settings = self.settings
+ local curpath = settings:GetPath()
+ settings:SetPath(path)
+ local ok, value = settings:Read(setting)
+ settings:SetPath(curpath)
+ return ok and value or nil
+end
+
+function ide:RemoveMenuItem(id, menu)
+ local _, menu, pos = self:FindMenuItem(id, menu)
+ if menu then
+ self:GetMainFrame():Disconnect(id, wx.wxID_ANY, wx.wxEVT_COMMAND_MENU_SELECTED)
+ self:GetMainFrame():Disconnect(id, wx.wxID_ANY, wx.wxEVT_UPDATE_UI)
+ menu:Disconnect(id, wx.wxID_ANY, wx.wxEVT_COMMAND_MENU_SELECTED)
+ menu:Disconnect(id, wx.wxID_ANY, wx.wxEVT_UPDATE_UI)
+ menu:Remove(id)
+
+ local positem = menu:FindItemByPosition(pos)
+ if (not positem or positem:GetKind() == wx.wxITEM_SEPARATOR)
+ and pos > 0
+ and (menu:FindItemByPosition(pos-1):GetKind() == wx.wxITEM_SEPARATOR) then
+ menu:Destroy(menu:FindItemByPosition(pos-1))
+ end
+ return true
+ end
+ return false
+end
+
+function ide:ExecuteCommand(cmd, wdir, callback, endcallback)
+ local proc = wx.wxProcess(self:GetOutput())
+ proc:Redirect()
+
+ local cwd
+ if (wdir and #wdir > 0) then -- ignore empty directory
+ cwd = wx.wxFileName.GetCwd()
+ cwd = wx.wxFileName.SetCwd(wdir) and cwd
+ end
+
+ local pid = wx.wxExecute(cmd, wx.wxEXEC_ASYNC, proc)
+ pid = pid ~= -1 and pid ~= 0 and pid or nil
+ if cwd then wx.wxFileName.SetCwd(cwd) end -- restore workdir
+ if not pid then return pid, wx.wxSysErrorMsg() end
+
+ OutputSetCallbacks(pid, proc, callback or function() end, endcallback)
+ return pid
+end
+
+function ide:CreateImageList(group, ...)
+ local _ = wx.wxLogNull() -- disable error reporting in popup
+ local size = wx.wxSize(16,16)
+ local imglist = wx.wxImageList(16,16)
+ for i = 1, select('#', ...) do
+ local icon, file = self:GetBitmap(select(i, ...), group, size)
+ if imglist:Add(icon) == -1 then
+ DisplayOutputLn(("Failed to add image '%s' to the image list.")
+ :format(file or select(i, ...)))
+ end
+ end
+ return imglist
+end
+
+local tintdef = 100
+local function iconFilter(bitmap, tint)
+ if type(tint) == 'function' then return tint(bitmap) end
+ if type(tint) ~= 'table' or #tint ~= 3 then return bitmap end
+
+ local tr, tg, tb = tint[1]/255, tint[2]/255, tint[3]/255
+ local pi = 0.299*tr + 0.587*tg + 0.114*tb -- pixel intensity
+ local perc = (tint[0] or tintdef)/tintdef
+ tr, tg, tb = tr*perc, tg*perc, tb*perc
+
+ local img = bitmap:ConvertToImage()
+ for x = 0, img:GetWidth()-1 do
+ for y = 0, img:GetHeight()-1 do
+ if not img:IsTransparent(x, y) then
+ local r, g, b = img:GetRed(x, y)/255, img:GetGreen(x, y)/255, img:GetBlue(x, y)/255
+ local gs = (r + g + b) / 3
+ local weight = 1-4*(gs-0.5)*(gs-0.5)
+ r = math.max(0, math.min(255, math.floor(255 * (gs + (tr-pi) * weight))))
+ g = math.max(0, math.min(255, math.floor(255 * (gs + (tg-pi) * weight))))
+ b = math.max(0, math.min(255, math.floor(255 * (gs + (tb-pi) * weight))))
+ img:SetRGB(x, y, r, g, b)
+ end
+ end
+ end
+ return wx.wxBitmap(img)
+end
+
+local icons = {} -- icon cache to avoid reloading the same icons
+function ide:GetBitmap(id, client, size)
+ local im = self.config.imagemap
+ local width = size:GetWidth()
+ local key = width.."/"..id
+ local keyclient = key.."-"..client
+ local mapped = im[keyclient] or im[id.."-"..client] or im[key] or im[id]
+ -- mapped may be a file name/path or wxImage object; take that into account
+ if type(im[id.."-"..client]) == 'string' then keyclient = width.."/"..im[id.."-"..client]
+ elseif type(im[keyclient]) == 'string' then keyclient = im[keyclient]
+ elseif type(im[id]) == 'string' then
+ id = im[id]
+ key = width.."/"..id
+ keyclient = key.."-"..client
+ end
+
+ local fileClient = self:GetAppName() .. "/res/" .. keyclient .. ".png"
+ local fileKey = self:GetAppName() .. "/res/" .. key .. ".png"
+ local isImage = type(mapped) == 'userdata' and mapped:GetClassInfo():GetClassName() == 'wxImage'
+ local file
+ if mapped and (isImage or wx.wxFileName(mapped):FileExists()) then file = mapped
+ elseif wx.wxFileName(fileClient):FileExists() then file = fileClient
+ elseif wx.wxFileName(fileKey):FileExists() then file = fileKey
+ else return wx.wxArtProvider.GetBitmap(id, client, size) end
+ local icon = icons[file] or iconFilter(wx.wxBitmap(file), self.config.imagetint)
+ icons[file] = icon
+ return icon, file
+end
+
+function ide:AddPackage(name, package)
+ self.packages[name] = setmetatable(package, self.proto.Plugin)
+ self.packages[name].fname = name
+ return self.packages[name]
+end
+function ide:RemovePackage(name) self.packages[name] = nil end
+
+function ide:AddWatch(watch, value)
+ local mgr = self.frame.uimgr
+ local pane = mgr:GetPane("watchpanel")
+ if (pane:IsOk() and not pane:IsShown()) then
+ pane:Show()
+ mgr:Update()
+ end
+
+ local watchCtrl = self.debugger.watchCtrl
+ if not watchCtrl then return end
+
+ local root = watchCtrl:GetRootItem()
+ if not root or not root:IsOk() then return end
+
+ local item = watchCtrl:GetFirstChild(root)
+ while true do
+ if not item:IsOk() then break end
+ if watchCtrl:GetItemExpression(item) == watch then
+ if value then watchCtrl:SetItemText(item, watch .. ' = ' .. tostring(value)) end
+ return item
+ end
+ item = watchCtrl:GetNextSibling(item)
+ end
+
+ item = watchCtrl:AppendItem(root, watch, 1)
+ watchCtrl:SetItemExpression(item, watch, value)
+ return item
+end
+
+function ide:AddInterpreter(name, interpreter)
+ self.interpreters[name] = setmetatable(interpreter, self.proto.Interpreter)
+ ProjectUpdateInterpreters()
+end
+function ide:RemoveInterpreter(name)
+ self.interpreters[name] = nil
+ ProjectUpdateInterpreters()
+end
+
+function ide:AddSpec(name, spec)
+ self.specs[name] = spec
+ UpdateSpecs()
+end
+function ide:RemoveSpec(name) self.specs[name] = nil end
+
+function ide:AddAPI(type, name, api)
+ self.apis[type] = self.apis[type] or {}
+ self.apis[type][name] = api
+end
+function ide:RemoveAPI(type, name) self.apis[type][name] = nil end
+
+function ide:AddConsoleAlias(alias, table) return ShellSetAlias(alias, table) end
+function ide:RemoveConsoleAlias(alias) return ShellSetAlias(alias, nil) end
+
+function ide:AddMarker(...) return StylesAddMarker(...) end
+function ide:GetMarker(marker) return StylesGetMarker(marker) end
+function ide:RemoveMarker(marker) StylesRemoveMarker(marker) end
+
+local indicators = {}
+function ide:AddIndicator(indic, num)
+ num = num or indicators[indic]
+ if not num then -- new indicator; find the smallest available number
+ local nums = {}
+ for _, indicator in pairs(indicators) do
+ if indicator >= wxstc.wxSTC_INDIC_CONTAINER then
+ nums[indicator-wxstc.wxSTC_INDIC_CONTAINER+1] = true
+ end
+ end
+ num = #nums + wxstc.wxSTC_INDIC_CONTAINER
+ if num > wxstc.wxSTC_INDIC_MAX then return end
+ end
+ indicators[indic] = num
+ return num
+end
+function ide:GetIndicator(indic) return indicators[indic] end
+function ide:GetIndicators() return indicators end
+function ide:RemoveIndicator(indic) indicators[indic] = nil end
+
+-- this provides a simple stack for saving/restoring current configuration
+local configcache = {}
+function ide:AddConfig(name, files)
+ if not name or configcache[name] then return end -- don't overwrite existing slots
+ if type(files) ~= "table" then files = {files} end -- allow to pass one value
+ configcache[name] = {
+ config = require('mobdebug').dump(self.config, {nocode = true}),
+ configmeta = getmetatable(self.config),
+ packages = {},
+ }
+ -- build a list of existing packages
+ local packages = {}
+ for package in pairs(self.packages) do packages[package] = true end
+ -- load config file(s)
+ for _, file in pairs(files) do LoadLuaConfig(MergeFullPath(name, file)) end
+ -- register newly added packages (if any)
+ for package in pairs(self.packages) do
+ if not packages[package] then -- this is a newly added package
+ PackageEventHandleOne(package, "onRegister")
+ configcache[name].packages[package] = true
+ end
+ end
+ ReApplySpecAndStyles() -- apply current config to the UI
+end
+function ide:RemoveConfig(name)
+ if not name or not configcache[name] then return end
+ -- unregister cached packages
+ for package in pairs(configcache[name].packages) do PackageUnRegister(package) end
+ -- load original config
+ local ok, res = LoadSafe(configcache[name].config)
+ if ok then
+ self.config = res
+ if configcache[name].configmeta then setmetatable(self.config, configcache[name].configmeta) end
+ else
+ DisplayOutputLn(("Error while restoring configuration: '%s'."):format(res))
+ end
+ configcache[name] = nil -- clear the slot after use
+ ReApplySpecAndStyles() -- apply current config to the UI
+end
+
+local panels = {}
+function ide:AddPanel(ctrl, panel, name, conf)
+ local width, height = 360, 200
+ local notebook = wxaui.wxAuiNotebook(self.frame, wx.wxID_ANY,
+ wx.wxDefaultPosition, wx.wxDefaultSize,
+ wxaui.wxAUI_NB_DEFAULT_STYLE + wxaui.wxAUI_NB_TAB_EXTERNAL_MOVE
+ - wxaui.wxAUI_NB_CLOSE_ON_ACTIVE_TAB + wx.wxNO_BORDER)
+ notebook:AddPage(ctrl, name, true)
+ notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK,
+ function() PaneFloatToggle(notebook) end)
+ notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE,
+ function(event) event:Veto() end)
+
+ local mgr = self.frame.uimgr
+ mgr:AddPane(notebook, wxaui.wxAuiPaneInfo():
+ Name(panel):Float():CaptionVisible(false):PaneBorder(false):
+ MinSize(width/2,height/2):
+ BestSize(width,height):FloatingSize(width,height):
+ PinButton(true):Hide())
+ if type(conf) == "function" then conf(mgr:GetPane(panel)) end
+ mgr.defaultPerspective = mgr:SavePerspective() -- resave default perspective
+
+ panels[name] = {ctrl, panel, name, conf}
+ return mgr:GetPane(panel), notebook
+end
+
+function ide:RemovePanel(panel)
+ local mgr = self.frame.uimgr
+ local pane = mgr:GetPane(panel)
+ if pane:IsOk() then
+ local win = pane.window
+ mgr:DetachPane(win)
+ win:Destroy()
+ mgr:Update()
+ end
+end
+
+function ide:AddPanelDocked(notebook, ctrl, panel, name, conf, activate)
+ notebook:AddPage(ctrl, name, activate ~= false)
+ panels[name] = {ctrl, panel, name, conf}
+ return notebook
+end
+function ide:IsPanelDocked(panel)
+ local layout = self:GetSetting("/view", "uimgrlayout")
+ return layout and not layout:find(panel)
+end
+
+function ide:IsValidCtrl(ctrl)
+ return ctrl and pcall(function() ctrl:GetId() end)
+end
+
+function ide:IsValidProperty(ctrl, prop)
+ return ide:IsValidCtrl(ctrl) and pcall(function() return ctrl[prop] end)
+end
+
+function ide:IsWindowShown(win)
+ while win do
+ if not win:IsShown() then return false end
+ win = win:GetParent()
+ end
+ return true
+end
+
+function ide:RestorePanelByLabel(name)
+ if not panels[name] then return end
+ return self:AddPanel(unpack(panels[name]))
+end
+
+function ide:AddTool(name, command, updateui)
+ return ToolsAddTool(name, command, updateui)
+end
+
+function ide:RemoveTool(name)
+ return ToolsRemoveTool(name)
+end