├── saveonappswitch.lua ├── autostartdebug.lua ├── noblinkcursor.lua ├── blockcursor.lua ├── editorautofocusbymouse.lua ├── saveonfocuslost.lua ├── escapetoquit.lua ├── striptrailingwhitespace.lua ├── edgemark.lua ├── shebangtype.lua ├── openwithdefault.lua ├── hidemousewhentyping.lua ├── tildemenu.lua ├── filetreeoneclick.lua ├── savealleveryxrunning.lua ├── hidemenu.lua ├── localhelpmenu.lua ├── autoindent.lua ├── autodelimiter.lua ├── overtype.lua ├── wordwrapmenu.lua ├── LICENSE ├── closetabsleftright.lua ├── markchar.lua ├── projectsettings.lua ├── launchtime.lua ├── verbosesaving.lua ├── outputtofile.lua ├── colourpicker.lua ├── realtimewatch.lua ├── xml.lua ├── showluareference.lua ├── moveline.lua ├── refreshproject.lua ├── syntaxcheckontype.lua ├── extregister.lua ├── showreference.lua ├── uniquetabname.lua ├── openimagefile.lua ├── analyzeall.lua ├── autodelimitersurroundselection.lua ├── outputclone.lua ├── maketoolbar.lua ├── referencepanel.lua ├── screenshot.lua ├── cloneview.lua ├── highlightselected.lua ├── remoteedit.lua ├── clippy.lua ├── moonscript.lua ├── todo.lua ├── wordcount.lua ├── eris.lua ├── redbean.lua ├── tools └── makedescriptions.lua ├── todoall.lua ├── README.md ├── torch7.lua ├── moonscriptlove.lua ├── documentmap.lua ├── luadist.lua ├── cuberite.lua └── tasks.lua /saveonappswitch.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Save all files on app switch", 3 | description = "Saves all modified files when app focus is lost.", 4 | author = "Paul Kulchenko", 5 | version = 0.11, 6 | 7 | onAppFocusLost = function() SaveAll(true) end, 8 | } 9 | -------------------------------------------------------------------------------- /autostartdebug.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Auto-start debugger server", 3 | description = "Auto-starts debugger server.", 4 | author = "Paul Kulchenko", 5 | version = 0.21, 6 | dependencies = "1.4", 7 | 8 | onRegister = function() ide:GetDebugger():Listen(true) end, 9 | } 10 | -------------------------------------------------------------------------------- /noblinkcursor.lua: -------------------------------------------------------------------------------- 1 | local function setCaretPeriod(self, editor) editor:SetCaretPeriod(0) end 2 | 3 | return { 4 | name = "No-blink cursor", 5 | description = "Disables cursor blinking.", 6 | author = "Paul Kulchenko", 7 | version = 0.21, 8 | 9 | onEditorLoad = setCaretPeriod, 10 | onEditorNew = setCaretPeriod, 11 | } 12 | -------------------------------------------------------------------------------- /blockcursor.lua: -------------------------------------------------------------------------------- 1 | local function setCaretStyle(self, editor) editor:SetCaretStyle(wxstc.wxSTC_CARETSTYLE_BLOCK) end 2 | 3 | return { 4 | name = "Block cursor", 5 | description = "Switches cursor to a block cursor.", 6 | author = "Paul Kulchenko", 7 | version = 0.21, 8 | 9 | onEditorLoad = setCaretStyle, 10 | onEditorNew = setCaretStyle, 11 | } 12 | -------------------------------------------------------------------------------- /editorautofocusbymouse.lua: -------------------------------------------------------------------------------- 1 | local function focusOnEnterWindow(self, editor) 2 | editor:Connect(wx.wxEVT_ENTER_WINDOW, function() editor:SetFocus() end) 3 | end 4 | 5 | return { 6 | name = "Editor auto-focus by mouse", 7 | description = "Moves focus to an editor tab the mouse is over.", 8 | author = "Paul Kulchenko", 9 | version = 0.11, 10 | 11 | onEditorLoad = focusOnEnterWindow, 12 | onEditorNew = focusOnEnterWindow, 13 | } 14 | -------------------------------------------------------------------------------- /saveonfocuslost.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Save a file on editor losing focus", 3 | description = "Saves a file when editor focus is lost.", 4 | author = "Paul Kulchenko", 5 | version = 0.11, 6 | 7 | onEditorFocusLost = function(plugin, editor) 8 | local fpath = ide:GetDocument(editor):GetFilePath() 9 | -- don't save those files that don't have a file name yet 10 | if fpath then SaveFile(editor, fpath) end 11 | end 12 | } 13 | -------------------------------------------------------------------------------- /escapetoquit.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Quit on Escape", 3 | description = "Exits application on Escape.", 4 | author = "Paul Kulchenko", 5 | version = 0.11, 6 | 7 | onEditorKeyDown = function(self, editor, event) 8 | if (event:GetKeyCode() == wx.WXK_ESCAPE 9 | and event:GetModifiers() == wx.wxMOD_NONE) then 10 | ide:GetMainFrame():AddPendingEvent(wx.wxCommandEvent( 11 | wx.wxEVT_COMMAND_MENU_SELECTED, ID.EXIT)) 12 | end 13 | end, 14 | } 15 | -------------------------------------------------------------------------------- /striptrailingwhitespace.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Strip trailing whitespaces on save", 3 | description = "Strips trailing whitespaces before saving a file.", 4 | author = "Paul Kulchenko", 5 | version = 0.1, 6 | 7 | onEditorPreSave = function(self, editor) 8 | for line = editor:GetLineCount()-1, 0, -1 do 9 | local spos, _, spaces = editor:GetLine(line):find("([ \t]+)([\r\n]*)$") 10 | if spos then 11 | editor:DeleteRange(editor:PositionFromLine(line)+spos-1, #spaces) 12 | end 13 | end 14 | end, 15 | } 16 | -------------------------------------------------------------------------------- /edgemark.lua: -------------------------------------------------------------------------------- 1 | local function markEdge(self, editor) 2 | local config = self.GetConfig and self:GetConfig() 3 | editor:SetEdgeMode(wxstc.wxSTC_EDGE_LINE) 4 | editor:SetEdgeColumn(config and config.column or 80) 5 | if config and config.color then 6 | editor:SetEdgeColour(wx.wxColour((table.unpack or unpack)(config.color))) 7 | end 8 | end 9 | 10 | return { 11 | name = "Mark edge", 12 | description = "Marks column edge for long lines.", 13 | author = "Paul Kulchenko", 14 | version = 0.21, 15 | 16 | onEditorLoad = markEdge, 17 | onEditorNew = markEdge, 18 | } 19 | -------------------------------------------------------------------------------- /shebangtype.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2015 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | 3 | return { 4 | name = "File type based on Shebang", 5 | description = "Sets file type based on executable in shebang.", 6 | author = "Paul Kulchenko", 7 | version = 0.1, 8 | dependencies = 1.0, 9 | 10 | onEditorLoad = function(self, editor) 11 | local src = editor:GetLine(0) 12 | if editor:GetLexer() == wxstc.wxSTC_LEX_NULL and src:find('^#!') then 13 | local ext = src:match("%W(%w+)%s*$") 14 | if ext then editor:SetupKeywords(ext) end 15 | end 16 | end, 17 | } 18 | -------------------------------------------------------------------------------- /openwithdefault.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Open With Default", 3 | description = "Opens file with Default Program when activated.", 4 | author = "Paul Kulchenko", 5 | version = 0.21, 6 | dependencies = 1.0, 7 | 8 | onFiletreeActivate = function(self, tree, event, item_id) 9 | local fname = tree:GetItemText(item_id) 10 | local ext = wx.wxFileName(fname):GetExt() 11 | -- don't activate for known extensions 12 | if #(ide:GetKnownExtensions(ext)) == 1 then return end 13 | local ft = wx.wxTheMimeTypesManager:GetFileTypeFromExtension('.'..ext) 14 | if ft then 15 | tree:SelectItem(item_id) 16 | tree:AddPendingEvent(wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, ID.OPENEXTENSION)) 17 | return false 18 | end 19 | end, 20 | } 21 | -------------------------------------------------------------------------------- /hidemousewhentyping.lua: -------------------------------------------------------------------------------- 1 | local function configureEditor(self, editor) 2 | editor:Connect(wx.wxEVT_MOTION, function(event) 3 | if not event:Dragging() then 4 | editor:SetSTCCursor(0) 5 | editor:SetSTCCursor(-1) 6 | end 7 | event:Skip() 8 | end) 9 | editor:Connect(wx.wxEVT_KEY_DOWN, 10 | function (event) 11 | local mod = event:GetModifiers() 12 | if not mod or mod ~= wx.wxMOD_SHIFT then editor:SetCursor(wx.wxCursor(wx.wxCURSOR_BLANK)) end 13 | event:Skip() 14 | end) 15 | end 16 | 17 | return { 18 | name = "Hide mouse cursor when typing", 19 | description = "Hides mouse cursor when typing.", 20 | author = "Paul Kulchenko", 21 | version = 0.1, 22 | 23 | onEditorLoad = configureEditor, 24 | onEditorNew = configureEditor, 25 | } 26 | -------------------------------------------------------------------------------- /tildemenu.lua: -------------------------------------------------------------------------------- 1 | local id = ID("tildemenu.tildemenu") 2 | return { 3 | name = "Tilde", 4 | description = "Allows to enter tilde (~) on keyboards that may not have it.", 5 | author = "Paul Kulchenko", 6 | version = 0.21, 7 | dependencies = "1.0", 8 | 9 | onRegister = function(self) 10 | local menu = ide:FindTopMenu("&Edit") 11 | menu:Append(id, "Tilde\tAlt-'") 12 | ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, function() 13 | local ed = ide:GetEditor() 14 | if ed then ed:AddText("~") end 15 | end) 16 | ide:GetMainFrame():Connect(id, wx.wxEVT_UPDATE_UI, function(event) 17 | event:Enable(ide:GetEditor() ~= nil) 18 | end) 19 | end, 20 | 21 | onUnRegister = function(self) 22 | ide:RemoveMenuItem(id) 23 | end, 24 | } 25 | -------------------------------------------------------------------------------- /filetreeoneclick.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Filetree one-click activation", 3 | description = "Changes filetree to activate items on one-click (as in Sublime Text).", 4 | author = "Paul Kulchenko", 5 | version = 0.2, 6 | dependencies = "0.51", 7 | 8 | onFiletreeLDown = function(self, tree, event, item_id) 9 | if not item_id then return end 10 | if tree:IsDirectory(item_id) then 11 | tree:Toggle(item_id) 12 | return false 13 | else 14 | tree:ActivateItem(item_id) 15 | end 16 | end, 17 | onMenuFiletree = function(self, menu, tree, event) 18 | local item_id = event:GetItem() 19 | if not item_id then return end 20 | if tree:IsDirectory(item_id) then 21 | tree:Toggle(item_id) 22 | else 23 | tree:ActivateItem(item_id) 24 | end 25 | end, 26 | } 27 | -------------------------------------------------------------------------------- /savealleveryxrunning.lua: -------------------------------------------------------------------------------- 1 | local timer 2 | local evthandler 3 | return { 4 | name = "Save all files every X seconds while running/debugging", 5 | description = "Saves all modified files every X seconds while running/debugging.", 6 | author = "Paul Kulchenko", 7 | version = 0.2, 8 | dependencies = 0.51, 9 | 10 | onRegister = function(self) 11 | local handler = function() if ide:GetLaunchedProcess() then SaveAll(true) end end 12 | evthandler = wx.wxEvtHandler() 13 | evthandler:Connect(wx.wxEVT_TIMER, handler) 14 | timer = wx.wxTimer(evthandler) 15 | timer:Start((self:GetConfig().interval or 3)*1000) 16 | end, 17 | 18 | onUnRegister = function(self) 19 | if evthandler then 20 | timer:Stop() 21 | evthandler:Disconnect(wx.wxEVT_TIMER, wx.wxID_ANY, wx.wxID_ANY) 22 | evthandler = nil 23 | end 24 | end, 25 | } 26 | -------------------------------------------------------------------------------- /hidemenu.lua: -------------------------------------------------------------------------------- 1 | local oldmenu 2 | local nobar = wx.wxMenuBar(0) 3 | 4 | local function hideMenu() 5 | oldmenu = oldmenu or ide:GetMenuBar() 6 | ide:GetMainFrame():SetMenuBar(nobar) 7 | end 8 | 9 | local function showMenu() 10 | ide:GetMainFrame():SetMenuBar(oldmenu) 11 | end 12 | 13 | return { 14 | name = "Hide menu", 15 | description = "Hides the menubar.", 16 | author = "David Krawiec", 17 | version = 0.2, 18 | dependencies = "1.60", 19 | 20 | onAppLoad = function(package) 21 | hideMenu() 22 | end, 23 | 24 | onEditorKeyDown = function(self, editor, event) 25 | local key = event:GetKeyCode() 26 | if (key == wx.WXK_ALT and ide:GetMainFrame():GetMenuBar() == nobar) then 27 | showMenu() 28 | elseif (key == wx.WXK_ALT and ide:GetMainFrame():GetMenuBar() ~= nobar) then 29 | hideMenu() 30 | end 31 | end, 32 | } 33 | -------------------------------------------------------------------------------- /localhelpmenu.lua: -------------------------------------------------------------------------------- 1 | local id = ID("localhelpmenu.localhelpmenu") 2 | return { 3 | name = "Local Lua help", 4 | description = "Adds local help option to the menu.", 5 | author = "Paul Kulchenko", 6 | version = 0.3, 7 | dependencies = "1.30", 8 | 9 | onRegister = function(self) 10 | local menu = ide:FindTopMenu("&Help") 11 | menu:Append(id, "Lua Documentation") 12 | ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, 13 | function (event) wx.wxLaunchDefaultBrowser(self:GetConfig().index, 0) end) 14 | ide:GetMainFrame():Connect(id, wx.wxEVT_UPDATE_UI, 15 | function (event) event:Enable(self:GetConfig().index ~= nil) end) 16 | end, 17 | 18 | onUnRegister = function(self) 19 | ide:RemoveMenuItem(id) 20 | end, 21 | } 22 | 23 | --[[ configuration example: 24 | localhelpmenu = {index = "file:///D:/my/Lua/manual/index.html"} 25 | --]] 26 | -------------------------------------------------------------------------------- /autoindent.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Auto-indent based on source code", 3 | description = "Sets editor indentation based on file text analysis.", 4 | author = "Paul Kulchenko", 5 | version = 0.1, 6 | 7 | onEditorLoad = function(self, editor) 8 | if editor:GetUseTabs() then return end 9 | 10 | local spaces, pspaces, pdiff = {}, 0, 0 11 | for line = 0, math.min(100, editor:GetLineCount())-1 do 12 | local tspaces = #(editor:GetLine(line):match("^[ \t]*")) 13 | local tdiff = math.abs(tspaces-pspaces) 14 | if tdiff > 0 then pdiff = tdiff end 15 | if pdiff > 0 and pdiff <= 8 then spaces[pdiff] = (spaces[pdiff] or 0) + 1 end 16 | pspaces = tspaces 17 | end 18 | 19 | local maxv, maxn = 0 20 | for n,v in pairs(spaces) do if v > maxv then maxn, maxv = n, v end end 21 | 22 | local indent = maxn or ide:GetConfig().editor.tabwidth or 2 23 | editor:SetIndent(indent) 24 | end, 25 | } 26 | -------------------------------------------------------------------------------- /autodelimiter.lua: -------------------------------------------------------------------------------- 1 | local pairs = { 2 | ['('] = ')', ['['] = ']', ['{'] = '}', ['"'] = '"', ["'"] = "'"} 3 | local closing = [[)}]'"]] 4 | return { 5 | name = "Auto-insertion of delimiters", 6 | description = [[Adds auto-insertion of delimiters (), {}, [], '', and "".]], 7 | author = "Paul Kulchenko", 8 | version = 0.2, 9 | 10 | onEditorCharAdded = function(self, editor, event) 11 | local keycode = event:GetKey() 12 | if keycode > 255 then return end -- special or unicode characters can be skipped here 13 | 14 | local char = string.char(keycode) 15 | local curpos = editor:GetCurrentPos() 16 | 17 | if closing:find(char, 1, true) and editor:GetCharAt(curpos) == keycode then 18 | -- if the entered text matches the closing one 19 | -- and the current symbol is the same, then "eat" the character 20 | editor:DeleteRange(curpos, 1) 21 | elseif pairs[char] then 22 | -- if the entered matches opening delimiter, then insert the pair 23 | editor:InsertText(-1, pairs[char]) 24 | end 25 | end, 26 | } 27 | -------------------------------------------------------------------------------- /overtype.lua: -------------------------------------------------------------------------------- 1 | local id = ID("overtype.overtype") 2 | return { 3 | name = "Overtype on/off", 4 | description = "Allows to switch overtyping on/off on systems that don't provide shortcut for that.", 5 | author = "Paul Kulchenko", 6 | version = 0.31, 7 | dependencies = "1.0", 8 | 9 | onRegister = function(self) 10 | local menu = ide:FindTopMenu("&Edit") 11 | local pos = self:GetConfig().insertat and 12 | self:GetConfig().insertat-1 or menu:GetMenuItemCount() 13 | menu:InsertCheckItem(pos, id, "Overtype"..KSC(id, "Alt-Shift-I")) 14 | ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, function(event) 15 | local ed = ide:GetEditor() 16 | if ed then ed:SetOvertype(event:IsChecked()) end 17 | end) 18 | ide:GetMainFrame():Connect(id, wx.wxEVT_UPDATE_UI, function(event) 19 | local ed = ide:GetEditor() 20 | event:Check(ed and ed:GetOvertype()) 21 | event:Enable(ed ~= nil) 22 | end) 23 | end, 24 | 25 | onUnRegister = function(self) 26 | ide:RemoveMenuItem(id) 27 | end, 28 | } 29 | -------------------------------------------------------------------------------- /wordwrapmenu.lua: -------------------------------------------------------------------------------- 1 | local id = ID("wordwrapmenu.wordwrapmenu") 2 | return { 3 | name = "Wordwrap menu", 4 | description = "Adds word wrap option to the menu.", 5 | author = "Paul Kulchenko", 6 | version = 0.21, 7 | dependencies = "1.0", 8 | 9 | onRegister = function(self) 10 | local menu = ide:FindTopMenu("&Edit") 11 | local pos = self:GetConfig().insertat and 12 | self:GetConfig().insertat-1 or menu:GetMenuItemCount() 13 | menu:InsertCheckItem(pos, id, "WordWrap\tAlt-W") 14 | ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, function(event) 15 | local wrap = event:IsChecked() and wxstc.wxSTC_WRAP_WORD or wxstc.wxSTC_WRAP_NONE 16 | local ed = ide:GetEditor() 17 | if ed then ed:SetWrapMode(wrap) end 18 | end) 19 | ide:GetMainFrame():Connect(id, wx.wxEVT_UPDATE_UI, function(event) 20 | local ed = ide:GetEditor() 21 | event:Check(ed and ed:GetWrapMode() ~= wxstc.wxSTC_WRAP_NONE) 22 | event:Enable(ed ~= nil) 23 | end) 24 | end, 25 | 26 | onUnRegister = function(self) 27 | ide:RemoveMenuItem(id) 28 | end, 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013-2014 by Paul Kulchenko 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /closetabsleftright.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Close tabs left and right", 3 | description = "Closes editor tabs to the left and to the right of the current one.", 4 | author = "Paul Kulchenko", 5 | version = 0.12, 6 | dependencies = "1.60", 7 | 8 | onMenuEditorTab = function(self, menu, notebook, event, index) 9 | local idleft = ID(self.fname..".left") 10 | local idright = ID(self.fname..".right") 11 | 12 | menu:AppendSeparator() 13 | menu:Append(idleft, "Close All on Left") 14 | menu:Append(idright, "Close All on Right") 15 | menu:Enable(idleft, index > 0) 16 | menu:Enable(idright, index < notebook:GetPageCount()-1) 17 | notebook:Connect(idleft, wx.wxEVT_COMMAND_MENU_SELECTED, function() 18 | for _ = 0, index-1 do 19 | if not ide:GetDocument(notebook:GetPage(0)):Close() then break end 20 | end 21 | end) 22 | notebook:Connect(idright, wx.wxEVT_COMMAND_MENU_SELECTED, function() 23 | for _ = index+1, notebook:GetPageCount()-1 do 24 | if not ide:GetDocument(notebook:GetPage(index+1)):Close() then break end 25 | end 26 | end) 27 | end, 28 | } 29 | -------------------------------------------------------------------------------- /markchar.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Mark character", 3 | description = "Marks characters when typed with specific indicators.", 4 | author = "Paul Kulchenko", 5 | version = 0.1, 6 | 7 | onEditorCharAdded = function(self, editor, event) 8 | local cfg = self:GetConfig() 9 | if type(cfg.chars) ~= "table" or not cfg.chars[event:GetKey()] then return end 10 | if not self.indic then 11 | local indicname = "utf8char" 12 | ide:AddIndicator(indicname) 13 | self.indic = ide:GetIndicator(indicname) 14 | end 15 | local indicator = self.indic 16 | local pos = editor:GetCurrentPos()-1 17 | local style = bit.band(editor:GetStyleAt(pos),31) 18 | local color = cfg and type(cfg.color) == "table" and #(cfg.color) == 3 and 19 | wx.wxColour((table.unpack or unpack)(cfg.color)) or editor:StyleGetForeground(style) 20 | editor:IndicatorSetStyle(indicator, cfg and cfg.indicator or wxstc.wxSTC_INDIC_STRIKE) 21 | editor:IndicatorSetForeground(indicator, color) 22 | editor:SetIndicatorCurrent(indicator) 23 | editor:IndicatorFillRange(pos, 1) 24 | end, 25 | } 26 | 27 | --[[ configuration example: 28 | markchar = {chars = {[160] = true}, color = {255, 0, 0}, indicator = wxstc.wxSTC_INDIC_STRIKE} 29 | --]] 30 | -------------------------------------------------------------------------------- /projectsettings.lua: -------------------------------------------------------------------------------- 1 | local id = ID("projectsettings.settingsmenu") 2 | local filename 3 | return { 4 | name = "Project settings", 5 | description = "Adds project settings loaded on project switch.", 6 | author = "Paul Kulchenko", 7 | version = 0.21, 8 | dependencies = "1.30", 9 | 10 | onRegister = function(self) 11 | local menu = ide:FindTopMenu("&Edit") 12 | local prefs = menu:FindItem(ID.PREFERENCES):GetSubMenu() 13 | menuid = prefs:Append(id, "Settings: Project") 14 | 15 | local config = self:GetConfig() 16 | filename = config.filename or {'.zbstudio/user.lua'} 17 | ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, 18 | function (event) 19 | local project = ide:GetProject() 20 | if not project then return end 21 | for _, file in pairs(filename) do 22 | LoadFile(MergeFullPath(project, file)) 23 | end 24 | end) 25 | ide:GetMainFrame():Connect(id, wx.wxEVT_UPDATE_UI, 26 | function (event) event:Enable(#filename > 0) end) 27 | end, 28 | 29 | onUnRegister = function(self) 30 | ide:RemoveMenuItem(id) 31 | end, 32 | 33 | onProjectPreLoad = function(self, project) ide:AddConfig(project, filename) end, 34 | onProjectClose = function(self, project) ide:RemoveConfig(project) end, 35 | } 36 | -------------------------------------------------------------------------------- /launchtime.lua: -------------------------------------------------------------------------------- 1 | local log = {} 2 | local call = {} 3 | local function logit(msg) table.insert(log, ("%.3f %s"):format(os.clock(), msg)) end 4 | 5 | logit('started') 6 | return { 7 | name = "Measure IDE launch performance", 8 | description = "Measures IDE startup performance up to the first IDLE event.", 9 | author = "Paul Kulchenko", 10 | version = 0.11, 11 | 12 | onRegister = function(self) 13 | logit('OnRegister') 14 | debug.sethook(function(event, line) 15 | local src = debug.getinfo(2, "Sn") 16 | local name = src.name 17 | if not name then return end 18 | 19 | if event == "call" then 20 | call[name] = os.clock() 21 | else 22 | -- we may be returning from methods we haven't seen 23 | if not name or not call[name] then return end 24 | 25 | local calltime = os.clock()-call[name] 26 | if calltime >= 0.005 then 27 | logit(("%.3f+%.3f %s"):format(call[name], calltime, name..src.source..':'..src.linedefined)) 28 | end 29 | end 30 | end, "cr") 31 | end, 32 | 33 | onUnRegister = function(self) debug.sethook() end, 34 | 35 | onAppLoad = function(self) 36 | debug.sethook() 37 | logit('onAppLoad') 38 | end, 39 | 40 | onIdleOnce = function(self) 41 | logit('onIdleOnce') 42 | ide:Print(table.concat(log, "\n")) 43 | log = {} 44 | end, 45 | } 46 | -------------------------------------------------------------------------------- /verbosesaving.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Verbose Saving", 3 | description = "Saves a copy of each file on save in a separate directory with date and time appended to the file name.", 4 | author = "Rami Sabbagh", 5 | version = 1.1, 6 | 7 | splitFilePath = function(path) 8 | local p,n,e = path:match("(.-)([^\\/]-%.?([^%.\\/]*))$") 9 | n = n:sub(0,-(e:len()+2)) 10 | return p,n,e 11 | end, 12 | 13 | onRegister = function(self) 14 | self.loc = ide:GetConfig().verbosefolder 15 | if not self.loc then 16 | error("Must set 'verbosefolder' configuration settings in order for verbose file saving to work!") 17 | end 18 | end, 19 | 20 | onUnRegister = function(self) 21 | self.loc = nil 22 | end, 23 | 24 | onEditorSave = function(self,editor) 25 | if not self.loc then return end 26 | local filename = ide:GetDocument(editor):GetFilePath() 27 | filename = filename:gsub("\\","/") 28 | local path, name, extension = self.splitFilePath(filename) 29 | local data = editor:GetText() 30 | 31 | local time = os.date("%Y_%m_%d - %H.%M.%S",os.time()) 32 | local savename = name.." - "..time.."."..extension 33 | local file, err = io.open(self.loc..savename,"wb") 34 | if not file then 35 | error(err) 36 | else 37 | file:write(data) 38 | file:flush() 39 | file:close() 40 | end 41 | end 42 | } 43 | -------------------------------------------------------------------------------- /outputtofile.lua: -------------------------------------------------------------------------------- 1 | local filter, fname 2 | local function append(fname, s) 3 | if not fname then return end 4 | local f = io.open(fname, "a") 5 | or error(("Can't open file '%s' for writing"):format(fname)) 6 | f:write(s) 7 | f:close() 8 | end 9 | 10 | return { 11 | name = "Output to file", 12 | description = "Redirects debugging output to a file.", 13 | author = "Paul Kulchenko", 14 | version = 0.1, 15 | 16 | onRegister = function(self) 17 | local config = ide:GetConfig() 18 | local output = ide:GetOutput() 19 | local maxlines = self:GetConfig().maxlines or 100 20 | 21 | filter = config.debugger.outputfilter 22 | config.debugger.outputfilter = function(s) 23 | local start = output:GetLineCount() - maxlines 24 | if start >= 0 then -- trim the output to the right number of lines 25 | local readonly = output:GetReadOnly() 26 | output:SetReadOnly(false) 27 | output:SetTargetStart(0) 28 | output:SetTargetEnd(output:PositionFromLine(start+1)) 29 | output:ReplaceTarget("") 30 | output:SetReadOnly(readonly) 31 | end 32 | append(fname, s) 33 | return s 34 | end 35 | end, 36 | 37 | onUnRegister = function(self) 38 | ide:GetConfig().debugger.outputfilter = filter 39 | end, 40 | onProjectLoad = function(self, project) 41 | fname = MergeFullPath(project, self:GetConfig().fname or "output.log") 42 | end, 43 | } 44 | -------------------------------------------------------------------------------- /colourpicker.lua: -------------------------------------------------------------------------------- 1 | local id = ID("colourpicker.insertcolour") 2 | local function insertcolour(event) 3 | local editor = ide:GetEditor() 4 | if not editor then return end 5 | local rgb = editor:GetSelectedText():match("(%d+,%s*%d+,%s*%d+)") 6 | local colour = wx.wxColour(rgb and "rgb("..rgb..")" or wx.wxBLACK) 7 | local newcolour = wx.wxGetColourFromUser(ide:GetMainFrame(), colour) 8 | if newcolour:Ok() then -- user selected some colour 9 | if editor:GetCharAt(editor:GetCurrentPos()-1) == string.byte('x') then 10 | editor:DeleteRange(editor:GetCurrentPos()-1, 1) 11 | local newtext2 = newcolour:GetAsString(wx.wxC2S_HTML_SYNTAX):match("%x+%x+%x+") or "" 12 | ide:GetEditor():AddText(newtext2) 13 | else 14 | local newtext = newcolour:GetAsString(wx.wxC2S_CSS_SYNTAX):match("%d+,%s*%d+,%s*%d+") or "" 15 | ide:GetEditor():ReplaceSelection(newtext) 16 | end 17 | end 18 | end 19 | 20 | return { 21 | name = "Colour picker", 22 | description = "Selects color to insert in the document.", 23 | author = "Paul Kulchenko", 24 | version = 0.23, 25 | dependencies = "1.0", 26 | 27 | onRegister = function() 28 | local menu = ide:FindTopMenu("&View") 29 | menu:Append(id, "Colour Picker Window"..KSC(id)) 30 | ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, insertcolour) 31 | end, 32 | onUnRegister = function() 33 | ide:RemoveMenuItem(id) 34 | end, 35 | 36 | onMenuEditor = function(self, menu, editor, event) 37 | menu:AppendSeparator() 38 | menu:Append(id, "Insert Colour"..KSC(id)) 39 | end 40 | } 41 | -------------------------------------------------------------------------------- /realtimewatch.lua: -------------------------------------------------------------------------------- 1 | local redirect, filter = {} 2 | local stats = {} 3 | 4 | return { 5 | name = "Real-time watches", 6 | description = "Displays real-time values during debugging.", 7 | author = "Paul Kulchenko", 8 | version = 0.3, 9 | dependencies = "1.40", 10 | 11 | onRegister = function() 12 | local config = ide:GetConfig() 13 | redirect = config.debugger.redirect 14 | filter = config.debugger.outputfilter 15 | 16 | config.debugger.redirect = "r" 17 | config.debugger.outputfilter = function(s) 18 | local label, value = s:match('"(.-)%s*=%s*"\t(.+)') 19 | if not label or not value then return s end 20 | 21 | local num = tonumber(value) 22 | -- for numbers report min/max/count/avg 23 | if num then 24 | stats[label] = stats[label] or {} 25 | -- count, sum, min, max 26 | local stat = stats[label] 27 | stat[1] = (stat[1] or 0) + 1 28 | stat[2] = (stat[2] or 0) + num 29 | stat[3] = math.min(stat[3] or math.huge, num) 30 | stat[4] = math.max(stat[4] or -math.huge, num) 31 | value = ("%s (min: %s, max: %s, cnt: %s, avg: %s)") 32 | :format(num, stat[3], stat[4], stat[1], stat[2]/stat[1]) 33 | end 34 | 35 | ide:AddWatch(label, (value:gsub("\t", "; "))) 36 | return 37 | end 38 | end, 39 | 40 | onUnRegister = function(self) 41 | local config = ide:GetConfig() 42 | config.debugger.redirect = redirect 43 | config.debugger.outputfilter = filter 44 | end, 45 | 46 | onDebuggerLoad = function(self) 47 | stats = {} 48 | end, 49 | } 50 | -------------------------------------------------------------------------------- /xml.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2014 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | 3 | local spec = { 4 | exts = {"xml"}, 5 | lexer = wxstc.wxSTC_LEX_XML, 6 | apitype = "xml", 7 | stylingbits = 7, 8 | 9 | lexerstyleconvert = { 10 | text = {wxstc.wxSTC_H_DEFAULT,}, 11 | comment = {wxstc.wxSTC_H_COMMENT,}, 12 | stringeol = {wxstc.wxSTC_HJ_STRINGEOL,}, 13 | number = {wxstc.wxSTC_H_NUMBER,}, 14 | stringtxt = { 15 | wxstc.wxSTC_H_DOUBLESTRING, 16 | wxstc.wxSTC_H_SINGLESTRING, 17 | }, 18 | lexerdef= { 19 | wxstc.wxSTC_H_OTHER, 20 | wxstc.wxSTC_H_ENTITY, 21 | wxstc.wxSTC_H_VALUE, 22 | }, 23 | keywords0 = { 24 | wxstc.wxSTC_H_TAG, 25 | wxstc.wxSTC_H_ATTRIBUTE, 26 | }, 27 | keywords1 = {wxstc.wxSTC_H_TAGUNKNOWN, 28 | wxstc.wxSTC_H_ATTRIBUTEUNKNOWN, 29 | }, 30 | keywords2 = {wxstc.wxSTC_H_SCRIPT,}, 31 | keywords3 = {wxstc.wxSTC_LUA_WORD,}, 32 | keywords4 = {wxstc.wxSTC_LUA_WORD1,}, 33 | keywords5 = {wxstc.wxSTC_LUA_WORD2,}, 34 | preprocessor= {wxstc.wxSTC_LUA_PREPROCESSOR,}, 35 | }, 36 | 37 | keywords = { 38 | }, 39 | } 40 | 41 | return { 42 | name = "XML syntax highlighting", 43 | description = "Adds XML syntax highlighting.", 44 | author = "Paul Kulchenko", 45 | version = 0.21, 46 | 47 | onRegister = function(self) 48 | local keywords = self:GetConfig().keywords or '' 49 | spec.keywords[1] = keywords 50 | ide:AddSpec("xml", spec) 51 | end, 52 | onUnRegister = function(self) ide:RemoveSpec("xml") end, 53 | } 54 | 55 | --[[ configuration example: 56 | xml = {keywords = "foo bar"} 57 | --]] 58 | -------------------------------------------------------------------------------- /showluareference.lua: -------------------------------------------------------------------------------- 1 | local id = ID("showluareference.showluareferencemenu") 2 | local ident = "([a-zA-Z_][a-zA-Z_0-9%.%:]*)" 3 | return { 4 | name = "Show lua reference", 5 | description = "Adds 'show lua reference' option to the editor menu.", 6 | author = "Paul Kulchenko", 7 | version = 0.2, 8 | dependencies = "1.30", 9 | 10 | onMenuEditor = function(self, menu, editor, event) 11 | local point = editor:ScreenToClient(event:GetPosition()) 12 | local pos = editor:PositionFromPointClose(point.x, point.y) 13 | if not pos then return end 14 | 15 | local line = editor:LineFromPosition(pos) 16 | local linetx = editor:GetLine(line) 17 | local localpos = pos-editor:PositionFromLine(line) 18 | local selected = editor:GetSelectionStart() ~= editor:GetSelectionEnd() 19 | and pos >= editor:GetSelectionStart() and pos <= editor:GetSelectionEnd() 20 | 21 | local start = linetx:sub(1,localpos):find(ident.."$") 22 | local right = linetx:sub(localpos+1,#linetx):match("^([a-zA-Z_0-9]*)%s*['\"{%(]?") 23 | local ref = selected 24 | and editor:GetTextRange(editor:GetSelectionStart(), editor:GetSelectionEnd()) 25 | or (start and linetx:sub(start,localpos)..right or nil) 26 | 27 | if ref then 28 | menu:Append(id, ("Show Lua Reference: %s"):format(ref)) 29 | menu:Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, 30 | function() 31 | local url = ('http://www.lua.org/manual/%s/manual.html#%s'): 32 | format(ide:GetInterpreter().luaversion or '5.1', 33 | ref:find('^luaL?_') and ref or 'pdf-'..ref) 34 | wx.wxLaunchDefaultBrowser(url, 0) 35 | end) 36 | end 37 | end 38 | } 39 | -------------------------------------------------------------------------------- /moveline.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "Move line up/down", 3 | description = "Adds moving line or selection up or down using `Ctrl-Shift-Up/Down`.", 4 | author = "Paul Kulchenko", 5 | version = 0.11, 6 | 7 | onEditorKeyDown = function(self, editor, event) 8 | local key = event:GetKeyCode() 9 | local mod = event:GetModifiers() 10 | if (key == wx.WXK_UP or key == wx.WXK_DOWN) 11 | and (mod == wx.wxMOD_CONTROL + wx.wxMOD_SHIFT) then 12 | local line1 = editor:LineFromPosition(editor:GetSelectionStart()) 13 | local line2 = editor:LineFromPosition(editor:GetSelectionEnd()) 14 | local cut, insert 15 | if key == wx.WXK_UP and line1 > 0 then 16 | cut, insert = line1-1, line2 17 | elseif key == wx.WXK_DOWN and line2 < editor:GetLineCount()-1 then 18 | insert, cut = line1, line2+1 19 | else 20 | return 21 | end 22 | 23 | local line = editor:GetLine(cut) 24 | 25 | editor:BeginUndoAction() 26 | editor:DeleteRange(editor:PositionFromLine(cut), #line) 27 | local pos = editor:PositionFromLine(insert) 28 | local current, anchor = editor:GetCurrentPos(), editor:GetAnchor() 29 | -- inserting at current position requires a fix as the cursor is 30 | -- anchored to the beginning of the line, which won't move 31 | if pos == current then editor:SetCurrentPos(current+1) end 32 | if pos == anchor then editor:SetAnchor(anchor+1) end 33 | editor:SetTargetStart(pos) 34 | editor:SetTargetEnd(pos) 35 | editor:ReplaceTarget(line) 36 | if pos == current then editor:SetCurrentPos(editor:GetCurrentPos()-1) end 37 | if pos == anchor then editor:SetAnchor(editor:GetAnchor()-1) end 38 | editor:EnsureCaretVisible() 39 | editor:EndUndoAction() 40 | 41 | return false -- don't apply "default" handling 42 | end 43 | end, 44 | } 45 | -------------------------------------------------------------------------------- /refreshproject.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2014 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | 3 | local winapi 4 | 5 | local flags 6 | 7 | local needrefresh = {} 8 | local function refreshProjectTree() 9 | for file, kind in pairs(needrefresh) do 10 | -- if the file is removed, try to find a non-existing file in the same folder 11 | -- as this will trigger a refresh of that folder 12 | ide:GetProjectTree():FindItem( 13 | file..(kind == winapi.FILE_ACTION_REMOVED and "/../\1" or "")) 14 | end 15 | needrefresh = {} 16 | end 17 | local function handler(plugin, kind, file) 18 | needrefresh[file] = kind 19 | plugin.onIdleOnce = refreshProjectTree 20 | end 21 | 22 | local watches = {} 23 | return { 24 | name = "Refresh project tree", 25 | description = "Refreshes project tree when files change (Windows only).", 26 | author = "Paul Kulchenko", 27 | version = 0.21, 28 | dependencies = {0.71, osname = "Windows"}, 29 | 30 | onRegister = function(self) 31 | local ok 32 | ok, winapi = pcall(require, 'winapi') 33 | if not ok then return false end 34 | flags = winapi.FILE_NOTIFY_CHANGE_DIR_NAME + winapi.FILE_NOTIFY_CHANGE_FILE_NAME 35 | end, 36 | 37 | onIdle = function(self) if next(watches) then winapi.sleep(1) end end, 38 | 39 | onProjectLoad = function(plugin, project) 40 | if watches[project] then return end 41 | 42 | for _, watcher in pairs(watches) do watcher:kill() end 43 | watches = {} 44 | 45 | local enc = winapi.get_encoding(winapi.CP_UTF8) 46 | winapi.set_encoding(winapi.CP_UTF8) 47 | local watcher, err = winapi.watch_for_file_changes(project, flags, true, 48 | function(...) return handler(plugin, ...) end) 49 | winapi.set_encoding(enc) 50 | 51 | if not watcher then 52 | error(("Can't set watcher for project '%s': %s"):format(project, err)) 53 | end 54 | watches[project] = watcher 55 | end, 56 | } 57 | -------------------------------------------------------------------------------- /syntaxcheckontype.lua: -------------------------------------------------------------------------------- 1 | local lasterr 2 | local markername, marker 3 | local function clean(editor) 4 | ide:GetStatusBar():SetStatusText("") 5 | if marker then editor:MarkerDeleteAll(marker) end -- remove markers 6 | end 7 | local function setmarker(editor, cfgmark) 8 | marker = ide:AddMarker(markername, 9 | cfgmark.ch or wxstc.wxSTC_MARK_CHARACTER+(' '):byte(), 10 | cfgmark.fg or {0, 0, 0}, 11 | cfgmark.bg or {255, 192, 192}) 12 | if marker then editor:MarkerDefine(ide:GetMarker(markername)) end 13 | end 14 | return { 15 | name = "Syntax check while typing", 16 | description = "Reports syntax errors while typing (on `Enter`).", 17 | author = "Paul Kulchenko", 18 | version = 0.41, 19 | dependencies = 1.11, 20 | 21 | -- use the file name as the marker name to avoid conflicts 22 | onRegister = function(self) markername = self:GetFileName() end, 23 | onUnRegister = function(self) ide:RemoveMarker(markername) end, 24 | 25 | onEditorNew = function(self, editor) setmarker(editor, self:GetConfig().marker or {}) end, 26 | onEditorLoad = function(self, editor) setmarker(editor, self:GetConfig().marker or {}) end, 27 | 28 | onEditorCharAdded = function(self, editor, event) 29 | if lasterr then clean(editor); lasterr = nil end 30 | local keycode = event:GetKey() 31 | if keycode > 255 or string.char(keycode) ~= "\n" then return end 32 | 33 | local text = editor:GetText():gsub("^#!.-\n", "\n") 34 | local _, err = loadstring(text, ide:GetDocument(editor):GetFileName()) 35 | 36 | if err then 37 | local line1, err = err:match(":(%d+)%s*:(.+)") 38 | local line2 = err and err:match("line (%d+)") 39 | 40 | if line1 and marker then editor:MarkerAdd(line1-1, marker) end 41 | if line2 and marker then editor:MarkerAdd(line2-1, marker) end 42 | ide:SetStatus(err and "Syntax error: "..err or "") 43 | 44 | lasterr = err 45 | end 46 | end, 47 | } 48 | 49 | --[[ configuration example: 50 | syntaxcheckontype = {marker = 51 | {ch = wxstc.wxSTC_MARK_CHARACTER+('>'):byte(), 52 | fg = {0, 0, 0}, bg = {192, 192, 255}} 53 | } 54 | --]] 55 | -------------------------------------------------------------------------------- /extregister.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2014 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | 3 | local winapi 4 | 5 | local function setvalue(key, name, val) 6 | local k, err = winapi.create_reg_key(key) 7 | k, err = winapi.open_reg_key(key, true) 8 | if not k then 9 | ide:Print(("Failed to create key %s: %s"):format(key, err)) 10 | return 11 | end 12 | if not k:set_value(name, val, winapi.REG_SZ) then 13 | ide:Print(("Failed to update key %s"):format(key)) 14 | return 15 | end 16 | ide:Print(("Registered '%s'"):format(key)) 17 | return true 18 | end 19 | 20 | local function register() 21 | local exts = ide:GetKnownExtensions() 22 | if #exts == 0 then 23 | ide:Print("No known extensions to register.") 24 | return 25 | end 26 | 27 | local extensions = table.concat(exts, ", ") 28 | extensions = wx.wxGetTextFromUser("Enter extensions to associate with the IDE", 29 | "Register extensions", extensions) 30 | 31 | if #extensions == 0 then return end 32 | ide:Print(("Registering extensions '%s' for the current user.") 33 | :format(extensions)) 34 | 35 | if not setvalue([[HKEY_CURRENT_USER\Software\Classes\ZeroBrane.Studio\shell\edit\command]], 36 | "", ide:GetRootPath('zbstudio.exe')..[[ "%1"]]) then 37 | return 38 | end 39 | for ext in extensions:gmatch("(%w+)") do 40 | if not setvalue(([[HKEY_CURRENT_USER\Software\Classes\.%s]]):format(ext), 41 | "", [[ZeroBrane.Studio]]) 42 | or not setvalue(([[HKEY_CURRENT_USER\Software\Classes\.%s\OpenWithProgids]]):format(ext), 43 | [[ZeroBrane.Studio]], "") then 44 | return 45 | end 46 | end 47 | end 48 | 49 | return { 50 | name = "Extension register", 51 | description = "Registers known extensions to launch the IDE on Windows.", 52 | author = "Paul Kulchenko", 53 | version = 0.2, 54 | dependencies = {"1.30", osname = "Windows"}, 55 | 56 | onRegister = function(self) 57 | local ok 58 | ok, winapi = pcall(require, 'winapi') 59 | if not ok then return false end 60 | 61 | ide:AddTool("Register Known Extensions", register) 62 | end, 63 | 64 | onUnRegister = function(self) 65 | ide:RemoveTool("Register Known Extensions") 66 | end, 67 | } 68 | -------------------------------------------------------------------------------- /showreference.lua: -------------------------------------------------------------------------------- 1 | local id = ID("showreference.showreferencemenu") 2 | local ident = "([a-zA-Z_][a-zA-Z_0-9%.%:]*)" 3 | return { 4 | name = "Show reference", 5 | description = "Adds 'show reference' option to the editor menu.", 6 | author = "Paul Kulchenko", 7 | version = 0.3, 8 | dependencies = "1.30", 9 | 10 | onMenuEditor = function(self, menu, editor, event) 11 | local point = editor:ScreenToClient(event:GetPosition()) 12 | local pos = editor:PositionFromPointClose(point.x, point.y) 13 | if not pos then return end 14 | 15 | local line = editor:LineFromPosition(pos) 16 | local linetx = editor:GetLine(line) 17 | local localpos = pos-editor:PositionFromLine(line) 18 | local selected = editor:GetSelectionStart() ~= editor:GetSelectionEnd() 19 | and pos >= editor:GetSelectionStart() and pos <= editor:GetSelectionEnd() 20 | 21 | local start = linetx:sub(1,localpos):find(ident.."$") 22 | local right = linetx:sub(localpos+1,#linetx):match("^([a-zA-Z_0-9]*)%s*['\"{%(]?") 23 | local ref = selected 24 | and editor:GetTextRange(editor:GetSelectionStart(), editor:GetSelectionEnd()) 25 | or (start and linetx:sub(start,localpos)..right or nil) 26 | 27 | local target = self:GetConfig().target 28 | local transform = self:GetConfig().transform 29 | if ref and target then 30 | menu:Append(id, ("Show Reference: %s"):format(ref)) 31 | if transform then ref = select(2, pcall(transform, ref)) end 32 | menu:Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, 33 | function() wx.wxLaunchDefaultBrowser(target:format(ref), 0) end) 34 | end 35 | end 36 | } 37 | 38 | --[[ configuration example: 39 | showreference = { 40 | target = 'http://love2d.org/wiki/%s', 41 | } 42 | 43 | or 44 | 45 | showreference = { 46 | target = 'http://docs.coronalabs.com/api/%s.html', 47 | transform = function(s) 48 | local tip = GetTipInfo(ide:GetEditor(), s) 49 | if tip then s = tip:match("%)%s*(%S+)") or s end 50 | s = (type(G[s]) == "function" and "global." or "")..s 51 | s = s..(s:find("[%.%:]") and "" or ".index") 52 | s = s:find("^_") and "type."..s:sub(2) or "library."..s 53 | return(s:gsub("[%.%:]","/")) 54 | end, 55 | } 56 | --]] 57 | -------------------------------------------------------------------------------- /uniquetabname.lua: -------------------------------------------------------------------------------- 1 | local sep = GetPathSeparator() 2 | local function makeUnique(exeditor) 3 | local docs = {} 4 | for id, doc in pairs(ide:GetDocuments()) do 5 | if doc:GetEditor() ~= exeditor and doc:GetFileName() and doc:GetFilePath() then 6 | local fn = doc:GetFileName() 7 | local fpath = doc:GetFilePath() 8 | local uniquepath = true 9 | if docs[fn] then 10 | for _, tab in pairs(docs[fn]) do 11 | if tab.path == fpath then uniquepath = false end 12 | end 13 | end 14 | -- only count duplicates if they are for different paths 15 | -- this excludes clones and other situations when the paths match 16 | if uniquepath then 17 | docs[fn] = docs[fn] or {} 18 | table.insert(docs[fn], {doc = doc, path = fpath, parts = wx.wxFileName(doc:GetFilePath()):GetDirs()}) 19 | end 20 | end 21 | end 22 | 23 | while true do 24 | local updated = false 25 | local newdocs = {} 26 | for fn, tabs in pairs(docs) do 27 | if #tabs > 1 then -- conflicting name 28 | updated = true 29 | newdocs[fn] = nil 30 | for _, tab in ipairs(tabs) do 31 | local fn = (table.remove(tab.parts) or '?') .. sep .. fn 32 | if not docs[fn] then 33 | newdocs[fn] = newdocs[fn] or {} 34 | table.insert(newdocs[fn], tab) 35 | end 36 | end 37 | end 38 | end 39 | if not updated then break end 40 | docs = newdocs 41 | end 42 | 43 | -- update all labels as some might have lost their conflicts 44 | for fn, tabs in pairs(docs) do 45 | for _, tab in ipairs(tabs) do tab.doc:SetTabText(fn) end end 46 | end 47 | 48 | return { 49 | name = "Unique tabname", 50 | description = "Updates editor tab names to always stay unique.", 51 | author = "Paul Kulchenko", 52 | version = 0.2, 53 | 54 | onEditorLoad = function(self) makeUnique() end, 55 | onEditorSave = function(self) makeUnique() end, 56 | onEditorClose = function(self, editor) makeUnique(editor) end, 57 | onAppClose = function(self, app) 58 | -- restore "original" names back before saving configuration 59 | for _, doc in pairs(ide:GetDocuments()) do 60 | if doc:GetFileName() then doc:SetTabText(doc:GetFileName()) end 61 | end 62 | end 63 | } 64 | -------------------------------------------------------------------------------- /openimagefile.lua: -------------------------------------------------------------------------------- 1 | local function fileLoad(file) 2 | local f = FileRead(file) 3 | if not f then return end 4 | 5 | local fstream = wx.wxMemoryInputStream.new(f, #f) 6 | local log = wx.wxLogNull() 7 | local image = wx.wxImage() 8 | local loaded = image:LoadFile(fstream) 9 | return loaded and image or nil 10 | end 11 | 12 | local function fileMeta(name) 13 | local image = fileLoad(name) 14 | if not image then return end 15 | 16 | return image:GetWidth(), image:GetHeight() 17 | end 18 | 19 | local function fileShow(name) 20 | local image = fileLoad(name) 21 | if not image then return end 22 | 23 | local panel = wx.wxPanel(ide:GetMainFrame(), wx.wxID_ANY, 24 | wx.wxDefaultPosition, wx.wxDefaultSize, wx.wxFULL_REPAINT_ON_RESIZE) 25 | panel:Connect(wx.wxEVT_PAINT, function() 26 | local dc = wx.wxPaintDC(panel) 27 | dc:DrawBitmap(wx.wxBitmap(image), 0, 0, true) 28 | dc:delete() 29 | end) 30 | 31 | local width, height = image:GetWidth(), image:GetHeight() 32 | local mgr = ide:GetUIManager() 33 | mgr:AddPane(panel, wxaui.wxAuiPaneInfo(): 34 | Name("openimagefile"):CaptionVisible(true):Caption(('(%d x %d) %s'):format(width, height, name)): 35 | Float(true):MinSize(width,height):BestSize(width,height):FloatingSize(width,height): 36 | PaneBorder(false):CloseButton(true):MaximizeButton(false):PinButton(false)) 37 | mgr:Update() 38 | return true 39 | end 40 | 41 | return { 42 | name = "Open image file", 43 | description = "Opens image file from the file tree.", 44 | author = "Paul Kulchenko", 45 | version = 0.3, 46 | dependencies = 1.0, 47 | 48 | onFiletreeActivate = function(self, tree, event, item) 49 | if not item then return end 50 | if fileShow(tree:GetItemFullName(item)) then return false end 51 | end, 52 | 53 | onMenuFiletree = function(self, menu, tree, event) 54 | local item_id = event:GetItem() 55 | local name = tree:GetItemFullName(item_id) 56 | local width, height = fileMeta(name) 57 | if not width or not height then return end 58 | 59 | local id = ID(self.fname .. ".openimage") 60 | menu:AppendSeparator() 61 | menu:Append(id, ("Open Image (%d x %d)"):format(width, height)) 62 | tree:Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, 63 | function() fileShow(name) end) 64 | end, 65 | } 66 | -------------------------------------------------------------------------------- /analyzeall.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2014 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | 3 | local id = ID("analyzeall.analyzeall") 4 | 5 | local function path2mask(s) 6 | return s 7 | :gsub('([%(%)%.%%%+%-%?%[%^%$%]])','%%%1') -- escape all special symbols 8 | :gsub("%*", ".*") -- but expand asterisk into sequence of any symbols 9 | :gsub("[\\/]","[\\\\/]") -- allow for any path 10 | end 11 | 12 | local function analyzeProject(self) 13 | local frame = ide:GetMainFrame() 14 | ide:GetOutput():Erase() 15 | ide:Print("Analyzing the project code.") 16 | frame:Update() 17 | 18 | local errors, warnings = 0, 0 19 | local projectPath = ide:GetProject() 20 | if projectPath then 21 | local specs = self:GetConfig().ignore or {} 22 | local masks = {} 23 | for i in ipairs(specs) do masks[i] = "^"..path2mask(specs[i]).."$" end 24 | for _, filePath in ipairs(ide:GetFileList(projectPath, true, "*.lua")) do 25 | local checkPath = filePath:gsub(projectPath, "") 26 | local ignore = false 27 | for _, spec in ipairs(masks) do 28 | ignore = ignore or checkPath:find(spec) 29 | end 30 | if not ignore then 31 | local warn, err = ide:AnalyzeFile(filePath) 32 | if err then 33 | ide:Print(err) 34 | errors = errors + 1 35 | elseif #warn > 0 then 36 | for _, msg in ipairs(warn) do ide:Print(msg) end 37 | warnings = warnings + #warn 38 | end 39 | ide:Yield() -- refresh the output with new results 40 | end 41 | end 42 | end 43 | 44 | ide:Print(("%s error%s and %s warning%s."):format( 45 | errors > 0 and errors or 'no', errors == 1 and '' or 's', 46 | warnings > 0 and warnings or 'no', warnings == 1 and '' or 's' 47 | )) 48 | end 49 | 50 | return { 51 | name = "Analyze all files", 52 | description = "Analyzes all files in a project.", 53 | author = "Paul Kulchenko", 54 | version = 0.44, 55 | dependencies = "1.7", 56 | 57 | onRegister = function(package) 58 | local _, menu, analyzepos = ide:FindMenuItem(ID.ANALYZE) 59 | if menu then 60 | menu:Insert(analyzepos+1, id, TR("Analyze All")..KSC(id), TR("Analyze the project source code")) 61 | menu:Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, function() return analyzeProject(package) end) 62 | end 63 | end, 64 | 65 | onUnRegister = function(self) 66 | ide:RemoveMenuItem(id) 67 | end, 68 | } 69 | -------------------------------------------------------------------------------- /autodelimitersurroundselection.lua: -------------------------------------------------------------------------------- 1 | -- Modified version of autodelimiter.lua 2 | -- This version supports surround selection and autoremoving alone pairs 3 | -- Please see pull request #33 (https://github.com/pkulchenko/ZeroBranePackage/pull/33) for more information 4 | -- If you load this module and standard autodelimiter, the standard autodelimiter will be turned off to prevent collisions 5 | local cpairs = { 6 | ['('] = ')', ['['] = ']', ['{'] = '}', ['"'] = '"', ["'"] = "'"} 7 | local closing = [[)}]'"]] 8 | local selection, spos, epos = "" 9 | return { 10 | name = "Auto-insertion of delimiters", 11 | description = [[Extends auto-insertion of delimiters (), {}, [], '', and "" to add selection and removal of standalone pairs.]], 12 | author = "Paul Kulchenko (modified by Dominik Banaszak)", 13 | version = 0.42, 14 | dependencies = "1.30", 15 | 16 | onEditorKeyDown = function(self, editor, event) 17 | -- remove autodelimiter package to avoid conflicts 18 | if ide:GetPackage("autodelimiter") then 19 | ide:RemovePackage("autodelimiter") 20 | ide:Print("Disabled autodelimiter package to avoid conflict.") 21 | end 22 | local currentpos = editor:GetCurrentPos() 23 | local keycode = event:GetKeyCode() 24 | if keycode == 8 then -- backslash 25 | if cpairs[string.char(editor:GetCharAt(currentpos - 1))] == string.char(editor:GetCharAt(currentpos)) then 26 | editor:DeleteRange(currentpos, 1) 27 | end 28 | end 29 | selection = editor:GetSelectedText() 30 | spos, epos = editor:GetAnchor(), editor:GetCurrentPos() 31 | end, 32 | onEditorCharAdded = function(self, editor, event) 33 | local keycode = event:GetKey() 34 | local hyphen = string.byte("-") 35 | local backslash = string.byte("\\") 36 | if keycode > 255 then return end -- special or unicode characters can be skipped here 37 | 38 | local char = string.char(keycode) 39 | local curpos = editor:GetCurrentPos() 40 | 41 | if closing:find(char, 1, true) and editor:GetCharAt(curpos) == keycode then 42 | -- if the entered text matches the closing one 43 | -- and the current symbol is the same, then "eat" the character 44 | if editor:GetCharAt(curpos - 2) ~= backslash then 45 | editor:DeleteRange(curpos, 1) 46 | end 47 | elseif cpairs[char] then 48 | if editor:GetCharAt(curpos - 2) ~= hyphen and editor:GetCharAt(curpos - 3) ~= hyphen then 49 | -- if the entered matches opening delimiter, then insert the pair 50 | editor:InsertText(-1, selection .. cpairs[char]) 51 | if selection~='' then 52 | -- maintain selection. 53 | editor:SetAnchor(spos c #AC7947", ", c #AD7B46", "< c #A7794A", "1 c #817F7C", "2 c #CB9827", 26 | "3 c #C2942F", "4 c #CB962B", "5 c #C29434", "6 c #CB983C", "7 c #DFBB3A", 27 | "8 c #BF9041", "9 c #BB9049", "0 c #BD9249", "q c #BC9154", "w c #87826C", 28 | "e c #CA9643", "r c #C79943", "t c #C6914D", "y c #E5C84E", "u c #EAC955", 29 | "i c #F2DD73", "p c #F6DD77", "a c #3F99DC", "s c #3E9ADE", "d c #4F99C3", 30 | "f c #519ED0", "g c #4BA0DC", "h c #4CA1DF", "j c #52A6E2", "k c #55A7E4", 31 | "l c #5EAAE2", "z c #5CACE4", "x c #6BAFE2", "c c #6BB0E3", "v c #6AB1EA", 32 | "b c #6CB7E8", "n c #76B5E4", "m c #70B5EA", "M c #70B9E8", "N c #828383", 33 | "B c #848585", "V c #858686", "C c #868787", "Z c #878787", "A c #8F8F8F", 34 | "S c #949595", "D c #B7B8B8", "F c #81BAE3", "G c #81BAE4", "H c #81BBE5", 35 | "J c #B3D1D7", "K c #A0D3E8", "L c #BCD6E6", "P c #B0D7F0", "I c #BDDEF1", 36 | "U c #BAE5F6", "Y c #C0C0C0", "T c #CDCDCD", "R c #D4D4D4", "E c #D9D9D9", 37 | "W c #DCDCDC", "Q c #CADEED", "! c #C2DFF8", "~ c #C9E2FA", "^ c #CEE4FA", 38 | "/ c #CEEFFE", "( c #CEF2FF", ") c #E1E1E1", "_ c #E2E2E2", "` c #E4E4E4", 39 | "' c #EBEBEB", 40 | -- pixels -- 41 | " ", 42 | " &w.C# ss ", 43 | " %1D_'BO vl ", 44 | " $E_`Z1 Hs s ", 45 | " .YWRBo H~bkbs ", 46 | " +ETSwr=HQ!/Hk ", 47 | " .NAX9i6,IUh ", 48 | " O@ <8y2;h ", 49 | " f;574; ", 50 | " xLJ,5ue; ", 51 | " HH^PKd;0pt; ", 52 | " snsv(s ;que, ", 53 | " s sH *0pt* ", 54 | " zM ,q; ", 55 | " ss , ", 56 | " " 57 | })) 58 | tb:Realize() 59 | end, 60 | 61 | onUnRegister = function(self) 62 | local tb = ide:GetToolBar() 63 | tb:DeleteTool(tool) 64 | tb:Realize() 65 | 66 | ide:RemoveMenuItem(id) 67 | end, 68 | } 69 | -------------------------------------------------------------------------------- /referencepanel.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2014 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | 3 | local id = ID("referencepanel.referenceview") 4 | local refpanel = "referencepanel" 5 | local refeditor 6 | local spec = {iscomment = {}} 7 | return { 8 | name = "Show Reference in a panel", 9 | description = "Adds a panel for showing documentation based on tooltips.", 10 | author = "Paul Kulchenko", 11 | version = 0.2, 12 | dependencies = "1.30", 13 | 14 | onRegister = function(self) 15 | local e = ide:CreateBareEditor() 16 | refeditor = e 17 | 18 | local w, h = 250, 250 19 | local conf = function(pane) 20 | pane:Dock():MinSize(w,-1):BestSize(w,-1):FloatingSize(w,h) 21 | end 22 | if ide:IsPanelDocked(refpanel) then 23 | ide:AddPanelDocked(ide:GetOutputNotebook(), e, refpanel, TR("Reference"), conf) 24 | else 25 | ide:AddPanel(e, refpanel, TR("Reference"), conf) 26 | end 27 | 28 | do -- markup handling in the reference panel 29 | -- copy some settings from the lua spec 30 | for _, s in ipairs({'lexer', 'lexerstyleconvert'}) do 31 | spec[s] = ide.specs.lua[s] 32 | end 33 | -- this allows the markup to be recognized in all token types 34 | for i = 0, 16 do spec.iscomment[i] = true end 35 | e:Connect(wxstc.wxEVT_STC_UPDATEUI, function(event) MarkupStyle(e,0,e:GetLineCount()) end) 36 | end 37 | 38 | e:SetReadOnly(true) 39 | e:SetWrapMode(wxstc.wxSTC_WRAP_WORD) 40 | e:SetupKeywords("lua",spec,ide:GetConfig().styles,ide:GetOutput():GetFont()) 41 | 42 | -- remove all margins 43 | for m = 0, 4 do e:SetMarginWidth(m, 0) end 44 | 45 | -- disable dragging to the panel 46 | e:Connect(wxstc.wxEVT_STC_DO_DROP, function(event) event:SetDragResult(wx.wxDragNone) end) 47 | 48 | local menu = ide:FindTopMenu("&View") 49 | menu:InsertCheckItem(4, id, TR("Reference Window")..KSC(id)) 50 | menu:Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, function (event) 51 | local uimgr = ide:GetUIManager() 52 | uimgr:GetPane(refpanel):Show(not uimgr:GetPane(refpanel):IsShown()) 53 | uimgr:Update() 54 | end) 55 | ide:GetMainFrame():Connect(id, wx.wxEVT_UPDATE_UI, function (event) 56 | local pane = ide:GetUIManager():GetPane(refpanel) 57 | menu:Enable(event:GetId(), pane:IsOk()) -- disable if doesn't exist 58 | menu:Check(event:GetId(), pane:IsOk() and pane:IsShown()) 59 | end) 60 | end, 61 | 62 | onUnRegister = function(self) 63 | ide:RemoveMenuItem(id) 64 | end, 65 | 66 | onEditorCallTip = function(self, editor, tip, value, eval) 67 | if not refeditor or eval then return end 68 | 69 | -- update the reference text 70 | refeditor:SetReadOnly(false) 71 | refeditor:SetText(tip) 72 | refeditor:SetReadOnly(true) 73 | 74 | local pane = ide:GetUIManager():GetPane(refpanel) 75 | -- if the reference tab is docked or the pane is shown, 76 | -- then suppress the normal tooltip (`return false`) 77 | if not pane:IsOk() or pane:IsShown() then return false end 78 | end, 79 | } 80 | -------------------------------------------------------------------------------- /screenshot.lua: -------------------------------------------------------------------------------- 1 | local id = ID("screenshot.takeit") 2 | 3 | local function fileLoad(file) 4 | local f = FileRead(file) 5 | if not f then return end 6 | local fstream = wx.wxMemoryInputStream.new(f, #f) 7 | local log = wx.wxLogNull() 8 | local image = wx.wxImage() 9 | local loaded = image:LoadFile(fstream) 10 | return loaded and image or nil 11 | end 12 | 13 | local function fileMeta(name) 14 | local image = fileLoad(name) 15 | if not image then return end 16 | 17 | return image:GetWidth(), image:GetHeight() 18 | end 19 | 20 | local function fileShow(name) 21 | local image = fileLoad(name) 22 | if not image then return end 23 | 24 | local panel = wx.wxPanel(ide:GetMainFrame(), wx.wxID_ANY, 25 | wx.wxDefaultPosition, wx.wxDefaultSize, wx.wxFULL_REPAINT_ON_RESIZE) 26 | panel:Connect(wx.wxEVT_PAINT, function() 27 | local dc = wx.wxPaintDC(panel) 28 | dc:DrawBitmap(wx.wxBitmap(image), 0, 0, true) 29 | dc:delete() 30 | end) 31 | 32 | local width, height = image:GetWidth(), image:GetHeight() 33 | local mgr = ide:GetUIManager() 34 | mgr:AddPane(panel, wxaui.wxAuiPaneInfo(): 35 | Name("screenshot"):CaptionVisible(true):Caption(('(%d x %d) %s'):format(width, height, name)): 36 | Float(true):MinSize(width,height):BestSize(width,height):FloatingSize(width,height): 37 | PaneBorder(false):CloseButton(true):MaximizeButton(false):PinButton(false)) 38 | mgr:Update() 39 | return true 40 | end 41 | 42 | local function takeScreenshot(file) 43 | file = file or ("screenshot-%s.png"):format(os.date():gsub(" ","T"):gsub("[/:]","")) 44 | local win = ide:GetMainFrame() 45 | local topleft = win:GetPosition() 46 | local winDC = wx.wxWindowDC(win) 47 | local width, height = winDC:GetSize() 48 | local bitmap = wx.wxBitmap(width, height, -1) 49 | local scrDC = wx.wxScreenDC() 50 | local memDC = wx.wxMemoryDC() 51 | memDC:SelectObject(bitmap) 52 | memDC:Blit(0, 0, width, height, scrDC, topleft:GetX(), topleft:GetY()) 53 | memDC:SelectObject(wx.wxNullBitmap) 54 | 55 | local path = ide:MergePath(wx.wxStandardPaths.Get():GetUserDir(wx.wxStandardPaths.Dir_Pictures), file) 56 | bitmap:SaveFile(path, wx.wxBITMAP_TYPE_PNG) 57 | return path 58 | end 59 | 60 | local timer 61 | local delay 62 | 63 | local function takeDelayedScreenshot() 64 | if not delay then delay = 5 end 65 | if timer then 66 | if delay > 0 then 67 | ide:SetStatus(delay.."...") 68 | delay = delay - 1 69 | timer:Start(1000, wx.wxTIMER_ONE_SHOT) 70 | return 71 | else 72 | delay = nil 73 | ide:SetStatus("") 74 | end 75 | end 76 | fileShow(takeScreenshot()) 77 | end 78 | 79 | return { 80 | name = "Screenshot", 81 | description = "Takes a delayed screenshot of the application window and saves it into a file.", 82 | author = "Paul Kulchenko", 83 | version = 0.11, 84 | dependencies = "1.61", 85 | 86 | onRegister = function() 87 | local menu = ide:FindTopMenu("&View") 88 | menu:Append(id, "Take Screenshot"..KSC(id)) 89 | ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, takeDelayedScreenshot) 90 | timer = ide:AddTimer(ide:GetMainFrame(), takeDelayedScreenshot) 91 | end, 92 | onUnRegister = function() 93 | ide:RemoveMenuItem(id) 94 | end, 95 | } 96 | -------------------------------------------------------------------------------- /cloneview.lua: -------------------------------------------------------------------------------- 1 | local clones = {} 2 | return { 3 | name = "Clone view", 4 | description = "Clones the current editor tab.", 5 | author = "Paul Kulchenko", 6 | version = "0.20", 7 | dependencies = "1.70", 8 | 9 | -- release document pointer for closed tabs 10 | -- remove from the list of clones (in either direction) this closed editor 11 | onEditorClose = function(self, editor) 12 | if not clones[editor] then return end 13 | 14 | if clones[editor].pointer then 15 | clones[editor].editor:ReleaseDocument(clones[editor].pointer) 16 | elseif editor.UseDynamicWords then 17 | -- closing the "source" editor of the close; restore dynamic words 18 | clones[editor].editor:UseDynamicWords(editor:UseDynamicWords()) 19 | end 20 | -- remove the editor this one clones from the list of clones 21 | clones[clones[editor].editor] = nil 22 | -- now remove this editor from the list of clones 23 | clones[editor] = nil 24 | end, 25 | 26 | -- mark the other document as not modified when the clone is saved (either one) 27 | onEditorSave = function(self, editor) 28 | if not clones[editor] then return end 29 | 30 | local doc1, doc2 = ide:GetDocument(editor), ide:GetDocument(clones[editor].editor) 31 | doc2:SetModified(false) 32 | if doc1.GetFileModifiedTime then 33 | doc2:SetFileModifiedTime(doc1:GetFileModifiedTime()) 34 | else 35 | doc2:SetModTime(doc1:GetModTime()) 36 | end 37 | end, 38 | 39 | onMenuEditorTab = function(self, menu, notebook, event, index) 40 | local idvert = ID(self.fname..".clone.vert") 41 | local idhorz = ID(self.fname..".clone.horz") 42 | 43 | local cloner = function(event) 44 | local e1 = ide:GetEditor(index) 45 | local e2 = NewFile(ide:GetDocument(e1):GetTabText()) 46 | local docpointer = e1:GetDocPointer() 47 | e1:AddRefDocument(docpointer) 48 | clones[e2] = {editor = e1, pointer = docpointer} 49 | clones[e1] = {editor = e2} 50 | e2:SetDocPointer(docpointer) 51 | ide:GetEditorNotebook():Split(notebook:GetSelection(), 52 | event:GetId() == idhorz and wx.wxRIGHT or wx.wxBOTTOM) 53 | notebook:SetSelection(index) 54 | local doc1, doc2 = ide:GetDocument(e1), ide:GetDocument(e2) 55 | doc2:SetModified(doc1:IsModified()) 56 | doc2:SetFilePath(doc1:GetFilePath()) 57 | doc2:SetTabText() -- reset tab text to reflect "modified" status 58 | if e2.UseDynamicWords then e2:UseDynamicWords(false) end 59 | end 60 | 61 | local cloned = clones[ide:GetEditor(index)] 62 | menu:AppendSeparator() 63 | menu:Append(idhorz, "Clone Horizontally") 64 | menu:Append(idvert, "Clone Vertically") 65 | -- disable if this editor already has a clone 66 | menu:Enable(idhorz, not cloned) 67 | menu:Enable(idvert, not cloned) 68 | notebook:Connect(idvert, wx.wxEVT_COMMAND_MENU_SELECTED, cloner) 69 | notebook:Connect(idhorz, wx.wxEVT_COMMAND_MENU_SELECTED, cloner) 70 | end, 71 | 72 | onEditorFocusSet = function(self, editor) 73 | if not clones[editor] then return end 74 | -- since the cloned editor tab doesn't get MODIFIED events, 75 | -- refresh if the number of tokens is different in cloned windows 76 | if editor.IndicateSymbols 77 | and editor:GetTokenList() ~= clones[editor].editor:GetTokenList() then 78 | editor:IndicateSymbols() 79 | end 80 | end, 81 | } 82 | -------------------------------------------------------------------------------- /highlightselected.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2015-16 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | 3 | local updateneeded, cfg 4 | local indicname = "highlightselected.selected" 5 | local function onUpdate(event, editor) 6 | if bit.band(event:GetUpdated(), wxstc.wxSTC_UPDATE_SELECTION) > 0 then updateneeded = editor end 7 | end 8 | return { 9 | name = "Highlight selected", 10 | description = "Highlights all instances of a selected word.", 11 | author = "Paul Kulchenko", 12 | version = 0.20, 13 | dependencies = "1.20", 14 | 15 | onRegister = function(package) 16 | ide:AddIndicator(indicname) 17 | cfg = package:GetConfig() 18 | ide:GetOutput():Connect(wxstc.wxEVT_STC_UPDATEUI, function(event) 19 | onUpdate(event, ide:GetOutput()) 20 | event:Skip() 21 | end) 22 | end, 23 | onUnRegister = function() 24 | ide:RemoveIndicator(indicname) 25 | ide:GetOutput():Disconnect(wxstc.wxEVT_STC_UPDATEUI) 26 | end, 27 | 28 | onEditorUpdateUI = function(self, editor, event) onUpdate(event, editor) end, 29 | 30 | onIdle = function(self) 31 | if not updateneeded then return end 32 | local editor = updateneeded 33 | updateneeded = false 34 | 35 | local length, curpos = editor:GetLength(), editor:GetCurrentPos() 36 | local ssel, esel = editor:GetSelection() 37 | local value = editor:GetTextRange(ssel, esel) 38 | local indicator = ide:GetIndicator(indicname) 39 | 40 | local function clearIndicator() 41 | editor:SetIndicatorCurrent(indicator) 42 | editor:IndicatorClearRange(0, length) 43 | end 44 | 45 | if #value == 0 or not value:find('%w') then return clearIndicator() end 46 | 47 | local word = editor:GetTextRange( -- try to select a word under caret 48 | editor:WordStartPosition(curpos, true), editor:WordEndPosition(curpos, true)) 49 | if #word == 0 then 50 | word = editor:GetTextRange( -- try to select a non-word under caret 51 | editor:WordStartPosition(curpos, false), editor:WordEndPosition(curpos, false)) 52 | end 53 | if value ~= word then return clearIndicator() end 54 | 55 | local style = bit.band(editor:GetStyleAt(editor:GetSelectionStart()),31) 56 | local color = cfg and type(cfg.color) == "table" and #(cfg.color) == 3 and 57 | wx.wxColour((table.unpack or unpack)(cfg.color)) or editor:StyleGetForeground(style) 58 | editor:IndicatorSetStyle(indicator, cfg and cfg.indicator or wxstc.wxSTC_INDIC_ROUNDBOX) 59 | editor:IndicatorSetForeground(indicator, color) 60 | editor:SetIndicatorCurrent(indicator) 61 | editor:IndicatorClearRange(0, length) 62 | 63 | -- save the flags to restore after the search is done to not affect other searches 64 | local flags = editor:GetSearchFlags() 65 | editor:SetSearchFlags(wxstc.wxSTC_FIND_WHOLEWORD + wxstc.wxSTC_FIND_MATCHCASE) 66 | 67 | local pos, num = 0, 0 68 | while true do 69 | editor:SetTargetStart(pos) 70 | editor:SetTargetEnd(length) 71 | pos = editor:SearchInTarget(value) 72 | if pos == -1 then break end 73 | 74 | editor:IndicatorFillRange(pos, #value) 75 | pos = pos + #value 76 | num = num + 1 77 | end 78 | ide:SetStatusFor(("Found %d instance(s)."):format(num), 5) 79 | editor:SetSearchFlags(flags) 80 | end, 81 | } 82 | 83 | --[[ configuration example: 84 | highlightselected = {indicator = wxstc.wxSTC_INDIC_ROUNDBOX, color = {255, 0, 0}} 85 | --]] 86 | -------------------------------------------------------------------------------- /remoteedit.lua: -------------------------------------------------------------------------------- 1 | local id = ID("remoteedit.openremotefile") 2 | local lastfile = "" 3 | local editors = {} 4 | local function reportErr(err) return(err:gsub('.-:%d+:%s*','')) end 5 | 6 | local mobdebug = require("mobdebug") 7 | local copas = require("copas") 8 | 9 | return { 10 | name = "Remote edit", 11 | description = "Allows to edit files remotely while debugging is in progress.", 12 | author = "Paul Kulchenko", 13 | version = 0.13, 14 | dependencies = "1.40", 15 | 16 | onRegister = function(self) 17 | local menu = ide:GetMenuBar():GetMenu(ide:GetMenuBar():FindMenu(TR("&File"))) 18 | menu:Insert(2, id, "Open Remotely...") 19 | 20 | ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, 21 | function() 22 | local file = wx.wxGetTextFromUser("Enter name (with path) of the remote file", 23 | "Open remote file", lastfile) 24 | if file and #file > 0 then 25 | self:loadFile(file) 26 | lastfile = file 27 | end 28 | end) 29 | ide:GetMainFrame():Connect(id, wx.wxEVT_UPDATE_UI, 30 | function (event) 31 | local debugger = ide:GetDebugger() 32 | event:Enable(debugger:IsConnected() and not debugger:IsRunning()) 33 | end) 34 | end, 35 | 36 | onUnRegister = function(self) 37 | ide:RemoveMenuItem(id) 38 | end, 39 | 40 | onEditorClose = function(self, editor) 41 | editors[editor] = nil 42 | end, 43 | 44 | onEditorPreSave = function(self, editor) 45 | local remote = editors[editor] 46 | if remote and ide:GetDocument(editor):IsModified() then 47 | self:saveFile(remote, editor) 48 | return false 49 | end 50 | end, 51 | 52 | loadFile = function(self, remote) 53 | local debugger = ide:GetDebugger() 54 | if not debugger:IsConnected() or debugger:IsRunning() then return end 55 | local code = ([[(function() local f, err = io.open(%s); if not f then error(err) end; local c = f:read('*a'); f:close(); return c end)()]]) 56 | :format(mobdebug.line(remote)) 57 | copas.addthread(function() 58 | local debugger = ide:GetDebugger() 59 | local res, _, err = debugger:evaluate(code) 60 | if err then 61 | ide:Print(("Failed to load file '%s': %s."):format(remote, reportErr(err))) 62 | return 63 | end 64 | 65 | local ok, content = LoadSafe("return "..res) 66 | if ok then 67 | ide:Print(("Loaded file '%s'."):format(remote)) 68 | self.onIdleOnce = function() 69 | local editor = NewFile("remote: "..remote) 70 | editor:SetText(content) 71 | editor:SetSavePoint() 72 | editors[editor] = remote 73 | end 74 | else 75 | ide:Print(("Failed to load file '%s': %s."):format(remote, content)) 76 | end 77 | end) 78 | end, 79 | 80 | saveFile = function(self, remote, editor) 81 | local debugger = ide:GetDebugger() 82 | if not debugger:IsConnected() or debugger:IsRunning() then return end 83 | local content = editor:GetText() 84 | local code = ([[local f, err = io.open(%s, 'w'); if not f then error(err) end; f:write(%s); f:close()]]) 85 | :format(mobdebug.line(remote), mobdebug.line(content)) 86 | copas.addthread(function() 87 | local err = select(3, debugger:execute(code)) 88 | if not err then 89 | editor:SetSavePoint() 90 | ide:Print(("Saved file '%s'."):format(remote)) 91 | else 92 | ide:Print(("Failed to save file '%s': %s."):format(remote, reportErr(err))) 93 | end 94 | end) 95 | end, 96 | } 97 | -------------------------------------------------------------------------------- /clippy.lua: -------------------------------------------------------------------------------- 1 | local id = ID("clippy.clippy") 2 | local menuid 3 | local stack 4 | local kStackLimit = 10 5 | 6 | local function SaveStack(self) 7 | local settings = self:GetSettings() 8 | settings.stack = stack 9 | self:SetSettings( settings ) 10 | end 11 | 12 | local function SaveClip() 13 | local tdo = wx.wxTextDataObject("None") 14 | if wx.wxClipboard:Get():Open() then 15 | wx.wxClipboard:Get():GetData(tdo) 16 | wx.wxClipboard:Get():Close() 17 | 18 | local newclip = tdo:GetText() 19 | if newclip ~= "" then 20 | for i,oldclip in ipairs( stack ) do 21 | if newclip == oldclip then 22 | table.remove( stack, i ) 23 | table.insert( stack, 1, newclip ) 24 | stack[kStackLimit] = nil 25 | return 26 | end 27 | end 28 | table.insert( stack, 1, newclip ) 29 | stack[kStackLimit] = nil 30 | end 31 | end 32 | end 33 | 34 | local function OpenClipList( editor ) 35 | if not editor then 36 | return 37 | end 38 | 39 | if editor:AutoCompActive() then 40 | editor:AutoCompCancel() 41 | end 42 | 43 | editor:AutoCompSetSeparator(string.byte('\n')) 44 | editor:AutoCompSetTypeSeparator(0) 45 | 46 | local list, firstline, rem = {} 47 | for i,clip in ipairs(stack) do 48 | firstline, rem = string.match(clip,'([^\r\n]+)(.*)') 49 | if firstline~=nil then 50 | if rem ~= "" then firstline = firstline .. "..." end 51 | list[#list+1] = i.."\t "..firstline 52 | end 53 | end 54 | editor:UserListShow(2,table.concat(list,'\n')) 55 | editor:AutoCompSelect( list[2] or "" ) 56 | 57 | editor:AutoCompSetSeparator(string.byte(' ')) 58 | editor:AutoCompSetTypeSeparator(string.byte('?')) 59 | end 60 | 61 | function PasteClip(i) 62 | local newclip = stack[i] 63 | local tdo = wx.wxTextDataObject(newclip) 64 | if wx.wxClipboard:Get():Open() then 65 | wx.wxClipboard:Get():SetData(tdo) 66 | wx.wxClipboard:Get():Close() 67 | 68 | if i ~= 1 then 69 | table.remove( stack, i ) 70 | table.insert( stack, 1, newclip ) 71 | stack[kStackLimit] = nil 72 | end 73 | 74 | ide.frame:AddPendingEvent(wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, ID.PASTE)) 75 | return true 76 | end 77 | return false 78 | end 79 | 80 | local function OnRegister(self) 81 | local menu = ide:GetMenuBar():GetMenu(ide:GetMenuBar():FindMenu(TR("&Edit"))) 82 | menuid = menu:Append(id, "Open Clip Stack"..KSC(id, "Ctrl-Shift-V")) 83 | ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, function (event) 84 | OpenClipList(ide:GetEditorWithFocus(ide:GetEditor())) 85 | end) 86 | ide:GetMainFrame():Connect(id, wx.wxEVT_UPDATE_UI, function (event) 87 | event:Check(ide:GetEditorWithFocus(ide:GetEditor()) ~= nil) 88 | end) 89 | 90 | local settings = self:GetSettings() 91 | stack = settings.stack or {} 92 | settings.stack = stack 93 | self:SetSettings(settings) 94 | end 95 | 96 | local function OnUnRegister(self) 97 | local menu = ide:GetMenuBar():GetMenu(ide:GetMenuBar():FindMenu(TR("&Edit"))) 98 | ide:GetMainFrame():Disconnect(id, wx.wxID_ANY, wx.wxID_ANY) 99 | if menuid then menu:Destroy(menuid) end 100 | end 101 | 102 | local function OnEditorAction( self, editor, event ) 103 | local eid = event:GetId() 104 | if eid == ID.COPY or eid == ID.CUT then 105 | -- call the original handler first to process Copy/Cut event 106 | self.onEditorAction = nil 107 | ide.frame:ProcessEvent(wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, eid)) 108 | self.onEditorAction = OnEditorAction 109 | SaveClip() 110 | SaveStack(self) 111 | return false 112 | end 113 | end 114 | 115 | local function OnEditorUserlistSelection( self, editor, event ) 116 | if event:GetListType() == 2 then 117 | local i = tonumber( event:GetText():sub(1,1) ); 118 | PasteClip(i) 119 | SaveStack(self) 120 | return false 121 | end 122 | end 123 | 124 | return 125 | { 126 | name = "Clippy", 127 | description = "Enables a stack-based clipboard which saves the last 10 entries.", 128 | author = "sclark39", 129 | dependencies = "1.3", 130 | version = 0.24, 131 | onRegister = OnRegister, 132 | onUnRegister = OnUnRegister, 133 | onEditorAction = OnEditorAction, 134 | onEditorUserlistSelection = OnEditorUserlistSelection, 135 | } 136 | -------------------------------------------------------------------------------- /moonscript.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2014 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | 3 | local exe 4 | local win = ide.osname == "Windows" 5 | 6 | local init = [=[ 7 | (loadstring or load)([[ 8 | if pcall(require, "mobdebug") then 9 | io.stdout:setvbuf('no') 10 | local cache = {} 11 | local lt = require("moonscript.line_tables") 12 | local rln = require("moonscript.errors").reverse_line_number 13 | local mdb = require "mobdebug" 14 | mdb.linemap = function(line, src) 15 | return src:find('%.moon$') and (tonumber(lt[src] and rln(src:gsub("^@",""), lt[src], line, cache) or line) or 1) or nil 16 | end 17 | mdb.loadstring = require("moonscript").loadstring 18 | end 19 | ]])() 20 | ]=] 21 | 22 | local interpreter = { 23 | name = "Moonscript", 24 | description = "Moonscript interpreter", 25 | api = {"baselib"}, 26 | frun = function(self,wfilename,rundebug) 27 | exe = exe or ide.config.path.moonscript -- check if the path is configured 28 | if not exe then 29 | local sep = win and ';' or ':' 30 | local default = win and GenerateProgramFilesPath('moonscript', sep)..sep or '' 31 | local path = default 32 | ..(os.getenv('PATH') or '')..sep 33 | ..(GetPathWithSep(self:fworkdir(wfilename)))..sep 34 | ..(os.getenv('HOME') and GetPathWithSep(os.getenv('HOME'))..'bin' or '') 35 | local paths = {} 36 | for p in path:gmatch("[^"..sep.."]+") do 37 | exe = exe or GetFullPathIfExists(p, win and 'moon.exe' or 'moon') 38 | table.insert(paths, p) 39 | end 40 | if not exe then 41 | ide:Print("Can't find moonscript executable in any of the following folders: " 42 | ..table.concat(paths, ", ")) 43 | return 44 | end 45 | end 46 | 47 | local filepath = wfilename:GetFullPath() 48 | if rundebug then 49 | ide:GetDebugger():SetOptions({ 50 | init = init, 51 | runstart = ide.config.debugger.runonstart == true, 52 | }) 53 | 54 | rundebug = ('require("mobdebug").start()\nrequire("moonscript").dofile [[%s]]'):format(filepath) 55 | 56 | local tmpfile = wx.wxFileName() 57 | tmpfile:AssignTempFileName(".") 58 | filepath = tmpfile:GetFullPath() 59 | local f = io.open(filepath, "w") 60 | if not f then 61 | ide:Print("Can't open temporary file '"..filepath.."' for writing.") 62 | return 63 | end 64 | f:write(init..rundebug) 65 | f:close() 66 | else 67 | -- if running on Windows and can't open the file, this may mean that 68 | -- the file path includes unicode characters that need special handling 69 | local fh = io.open(filepath, "r") 70 | if fh then fh:close() end 71 | if ide.osname == 'Windows' and pcall(require, "winapi") 72 | and wfilename:FileExists() and not fh then 73 | winapi.set_encoding(winapi.CP_UTF8) 74 | filepath = winapi.short_path(filepath) 75 | end 76 | end 77 | local params = ide.config.arg.any or ide.config.arg.moonscript 78 | local code = ([["%s"]]):format(filepath) 79 | local cmd = '"'..exe..'" '..code..(params and " "..params or "") 80 | 81 | -- CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback) 82 | return CommandLineRun(cmd,self:fworkdir(wfilename),true,false,nil,nil, 83 | function() if rundebug then wx.wxRemoveFile(filepath) end end) 84 | end, 85 | fprojdir = function(self,wfilename) 86 | return wfilename:GetPath(wx.wxPATH_GET_VOLUME) 87 | end, 88 | fworkdir = function(self,wfilename) 89 | return ide.config.path.projectdir or wfilename:GetPath(wx.wxPATH_GET_VOLUME) 90 | end, 91 | hasdebugger = true, 92 | fattachdebug = function(self) ide:GetDebugger():SetOptions({init = init}) end, 93 | skipcompile = true, 94 | unhideanywindow = true, 95 | takeparameters = true, 96 | } 97 | 98 | local name = 'moonscript' 99 | return { 100 | name = "Moonscript", 101 | description = "Implements integration with Moonscript language.", 102 | author = "Paul Kulchenko", 103 | version = 0.35, 104 | dependencies = "1.60", 105 | 106 | onRegister = function(self) 107 | ide:AddInterpreter(name, interpreter) 108 | end, 109 | onUnRegister = function(self) 110 | ide:RemoveInterpreter(name) 111 | end, 112 | } 113 | 114 | --[[ configuration example: 115 | -- if `moon` executable is not in PATH, set the path to it manually 116 | path.moonscript = "/full/path/moon.exe" 117 | --]] 118 | -------------------------------------------------------------------------------- /todo.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2014 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | -- TODO: on select, go to the relevant line 3 | local id = ID("TODOpanel.referenceview") 4 | local TODOpanel = "TODOpanel" 5 | local refeditor 6 | local spec = {iscomment = {}} 7 | 8 | local function mapTODOS(self,editor,event) 9 | 10 | local tasksListStr = "Tasks List: \n\n" 11 | local taskList = {} 12 | local text = editor:GetText() 13 | local i = 0 14 | local positions = {} 15 | 16 | for _, pattern in ipairs(self.patterns) do 17 | while true do 18 | 19 | --find next todo index 20 | i = string.find(text, pattern, i+1) 21 | if i == nil then 22 | i = 0 23 | break 24 | end 25 | local j = string.find(text, "\n",i+1) 26 | local taskStr = string.sub(text, i ,j) 27 | table.insert(taskList, {line = taskStr, pos = i}) 28 | end 29 | end 30 | 31 | table.sort(taskList, function(a,b) return a.pos 0 and selected or editor:GetTextDyn() 44 | local sn, sv, sw, sl, sa, sc = 0, 0, 0, 0, 0, 0 45 | for sentence in (text.." "):gmatch("(.-[%.%?!]+)") do 46 | local osw = sw 47 | for word in sentence:gmatch("(%w+)") do 48 | local v = select(2, word:gsub("[aeiouyAYIOUY]+","")) 49 | v = v - (v > 1 and word:find("[eE]$") and 1 or 0) 50 | if v > 0 then 51 | sl = sl + #word -- chars in words 52 | sw = sw + 1 -- words 53 | sv = sv + v -- syllables 54 | end 55 | end 56 | -- skip sentences without words; this excludes `1.` from `1. Some Text` and `.2` from `Section 1.2.` 57 | if sw > osw then -- if this sentence has any words 58 | sn = sn + 1 -- sentences 59 | sa = sa + #sentence -- total chars in sentences 60 | sc = sc + #(sentence:gsub("%s+","")) -- chars in sentences 61 | end 62 | end 63 | -- reading difficulty level based on the average number of words per sentence 64 | local difficultylevel = { 65 | {29, "very difficult"}, 66 | {25, "difficult"}, 67 | {21, "fairly difficult"}, 68 | {17, "standard"}, 69 | {14, "fairly easy"}, 70 | {11, "easy"}, 71 | {8, "very easy"}, 72 | } 73 | local function difficulty(n) 74 | for _, level in ipairs(difficultylevel) do 75 | if n > level[1] then return level[2] end 76 | end 77 | return "super easy" 78 | end 79 | 80 | local msg = ([[ 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |
Document Statistics
Sentences:%d
Words:%d
Characters (no spaces):%d
Characters (with spaces):%d
 
Sentence Statistics
Words:%.1f (%s)
Syllables:%d
Characters:%d
 
Word Statistics
Syllables:%.1f
Characters:%.1f
99 | 100 | ]]):format(sn, sw, sc, sa, sw/sn, difficulty(sw/sn), sv/sn, sa/sn, sv/sw, sa/sw) 101 | 102 | displayHTML(msg) 103 | end 104 | 105 | return { 106 | name = "Word count", 107 | description = "Counts the number of words and other statistics in the document.", 108 | author = "Paul Kulchenko", 109 | version = 0.1, 110 | dependencies = "1.30", 111 | 112 | onRegister = function() 113 | local menu = ide:FindTopMenu("&Edit") 114 | menu:Append(id, "Word Count"..KSC(id)) 115 | ide:GetMainFrame():Connect(id, wx.wxEVT_UPDATE_UI, function(event) 116 | event:Enable(ide:GetEditor() ~= nil) 117 | end) 118 | ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, showWordCount) 119 | end, 120 | onUnRegister = function() 121 | ide:RemoveMenuItem(id) 122 | end, 123 | } 124 | -------------------------------------------------------------------------------- /eris.lua: -------------------------------------------------------------------------------- 1 | local function makeErisInterpreter( version, name, basename ) 2 | basename = basename or 'lua' 3 | 4 | assert( basename:match( '^%l+$' ), 'Executable basename must be all lowercase' ) 5 | 6 | local capbasename = basename:sub( 1, 1 ):upper() .. basename:sub( 2 ) 7 | 8 | local function exePath(self, version) 9 | local version = tostring(version or ""):gsub('%.','') 10 | local mainpath = ide:GetRootPath() 11 | local macExe = mainpath..([[bin/%s.app/Contents/MacOS/%s%s]]):format(basename, basename, version) 12 | return (ide.config.path[basename..version] 13 | or (ide.osname == "Windows" and mainpath..([[bin\%s%s.exe]]):format(basename, version)) 14 | or (ide.osname == "Unix" and mainpath..([[bin/linux/%s/%s%s]]):format(ide.osarch, basename, version)) 15 | or (wx.wxFileExists(macExe) and macExe or mainpath..([[bin/%s%s]]):format(basename, version))), 16 | ide.config.path[basename..version] ~= nil 17 | end 18 | 19 | return { 20 | name = ("%s%s"):format(capbasename, name or version or ""), 21 | description = ("%s%s interpreter with debugger"):format(capbasename, name or version or ""), 22 | api = {"baselib"}, 23 | luaversion = version or '5.1', 24 | fexepath = exePath, 25 | frun = function(self,wfilename,rundebug) 26 | local exe, iscustom = self:fexepath(version or "") 27 | local filepath = wfilename:GetFullPath() 28 | 29 | do 30 | -- if running on Windows and can't open the file, this may mean that 31 | -- the file path includes unicode characters that need special handling 32 | local fh = io.open(filepath, "r") 33 | if fh then fh:close() end 34 | if ide.osname == 'Windows' and pcall(require, "winapi") 35 | and wfilename:FileExists() and not fh then 36 | winapi.set_encoding(winapi.CP_UTF8) 37 | local shortpath = winapi.short_path(filepath) 38 | if shortpath == filepath then 39 | ide:Print( 40 | ("Can't get short path for a Unicode file name '%s' to open the file.") 41 | :format(filepath)) 42 | ide:Print( 43 | ("You can enable short names by using `fsutil 8dot3name set %s: 0` and recreate the file or directory.") 44 | :format(wfilename:GetVolume())) 45 | end 46 | filepath = shortpath 47 | end 48 | end 49 | 50 | if rundebug then 51 | ide:GetDebugger():SetOptions({runstart = ide.config.debugger.runonstart == true}) 52 | 53 | -- update arg to point to the proper file 54 | rundebug = ('if arg then arg[0] = [[%s]] end '):format(filepath)..rundebug 55 | 56 | local tmpfile = wx.wxFileName() 57 | tmpfile:AssignTempFileName(".") 58 | filepath = tmpfile:GetFullPath() 59 | local f = io.open(filepath, "w") 60 | if not f then 61 | ide:Print("Can't open temporary file '"..filepath.."' for writing.") 62 | return 63 | end 64 | f:write(rundebug) 65 | f:close() 66 | end 67 | local params = self:GetCommandLineArg("lua") 68 | local code = ([[-e "io.stdout:setvbuf('no')" "%s"]]):format(filepath) 69 | local cmd = '"'..exe..'" '..code..(params and " "..params or "") 70 | 71 | -- modify CPATH to work with other Lua versions 72 | local envname = "LUA_CPATH" 73 | if version then 74 | local env = "LUA_CPATH_"..string.gsub(version, '%.', '_') 75 | if os.getenv(env) then envname = env end 76 | end 77 | 78 | local cpath = os.getenv(envname) 79 | if rundebug and cpath and not iscustom then 80 | -- prepend osclibs as the libraries may be needed for debugging, 81 | -- but only if no path.lua is set as it may conflict with system libs 82 | wx.wxSetEnv(envname, ide.osclibs..';'..cpath) 83 | end 84 | if version and cpath then 85 | local cpath = os.getenv(envname) 86 | local clibs = string.format('/clibs%s/', version):gsub('%.','') 87 | if not cpath:find(clibs, 1, true) then cpath = cpath:gsub('/clibs/', clibs) end 88 | wx.wxSetEnv(envname, cpath) 89 | end 90 | 91 | -- CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback) 92 | local pid = CommandLineRun(cmd,self:fworkdir(wfilename),true,false,nil,nil, 93 | function() if rundebug then wx.wxRemoveFile(filepath) end end) 94 | 95 | if (rundebug or version) and cpath then wx.wxSetEnv(envname, cpath) end 96 | return pid 97 | end, 98 | hasdebugger = true, 99 | scratchextloop = false, 100 | unhideanywindow = true, 101 | takeparameters = true, 102 | } 103 | end 104 | 105 | return { 106 | name = "Eris", 107 | description = "Implements integration with the Lua + Eris interpreter (5.3).", 108 | author = "raingloom", 109 | version = 0.11, 110 | onRegister = function() 111 | ide:AddInterpreter( 'eris53', makeErisInterpreter(5.3, '5.3', 'eris')) 112 | end, 113 | onUnRegister = function() 114 | ide:RemoveInterpreter( 'eris53' ) 115 | end, 116 | } -------------------------------------------------------------------------------- /redbean.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2022 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | 3 | local pathcache 4 | local win = ide and ide.osname == "Windows" 5 | 6 | local interpreter = { 7 | name = "Redbean", 8 | description = "Redbean Lua debugger", 9 | api = {"baselib"}, 10 | frun = function(self,wfilename,rundebug) 11 | local projdir = self:fworkdir(wfilename) 12 | local redbean = ide.config.path.redbean or pathcache and pathcache[projdir] 13 | if redbean and not wx.wxFileExists(redbean) then 14 | ide:Print(("Can't find configured redbean executable: '%s'."):format(redbean)) 15 | redbean = nil 16 | end 17 | if not redbean then 18 | local sep = win and ';' or ':' 19 | local default = win and GenerateProgramFilesPath('', sep)..sep or '' 20 | local path = default 21 | ..(os.getenv('PATH') or '')..sep 22 | ..projdir..sep 23 | ..(os.getenv('HOME') and GetPathWithSep(os.getenv('HOME'))..'bin' or '') 24 | local paths = {} 25 | for p in path:gmatch("[^"..sep.."]+") do 26 | redbean = redbean or GetFullPathIfExists(p, 'redbean.com') 27 | table.insert(paths, p) 28 | end 29 | if not redbean then 30 | ide:Print("Can't find redbean executable in any of the following folders: " 31 | ..table.concat(paths, ", ")) 32 | return 33 | end 34 | end 35 | 36 | local filepath = wfilename:GetFullPath() 37 | if rundebug then 38 | ide:GetDebugger():SetOptions({ runstart = ide.config.debugger.runonstart ~= false }) 39 | local mdb = MergeFullPath(GetPathWithSep(ide.editorFilename), "lualibs/mobdebug/") 40 | rundebug = ([[-e "package.path=[=[%s]=] package.loaded['socket']=require'redbean'"]]) 41 | :format(ide:GetPackage("redbean"):GetFilePath()) 42 | .." "..([[-e "package.path=[=[%s]=] 43 | MDB=require('mobdebug') 44 | MDB.start() 45 | function OnServerStart() 46 | local OHR=OnHttpRequest 47 | if OHR then 48 | OnHttpRequest=function(...) MDB.on() return OHR(...) end 49 | end 50 | end"]]):format(MergeFullPath(GetPathWithSep(ide.editorFilename), "lualibs/mobdebug/?.lua")) 51 | :gsub("\r?\n%s*"," ") 52 | else 53 | -- if running on Windows and can't open the file, this may mean that 54 | -- the file path includes unicode characters that need special handling 55 | local fh = io.open(filepath, "r") 56 | if fh then fh:close() end 57 | if ide.osname == 'Windows' and pcall(require, "winapi") 58 | and wfilename:FileExists() and not fh then 59 | winapi.set_encoding(winapi.CP_UTF8) 60 | filepath = winapi.short_path(filepath) 61 | end 62 | end 63 | local params = ide.config.arg.any or ide.config.arg.redbean 64 | local code = rundebug or "" 65 | local cmd = '"'..redbean..'" '..code..(params and " "..params or "") 66 | 67 | -- CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback) 68 | return CommandLineRun(cmd,self:fworkdir(wfilename),true,false,nil,nil) 69 | end, 70 | hasdebugger = true, 71 | skipcompile = true, 72 | unhideanywindow = true, 73 | takeparameters = true, 74 | } 75 | 76 | local name = 'redbean' 77 | return { 78 | name = "Redbean", 79 | description = "Implements integration with Redbean server.", 80 | author = "Paul Kulchenko", 81 | version = 0.12, 82 | dependencies = "1.60", 83 | 84 | onRegister = function(self) 85 | ide:AddInterpreter(name, interpreter) 86 | end, 87 | onUnRegister = function(self) 88 | ide:RemoveInterpreter(name) 89 | end, 90 | -- socket.lua mock to allow redbean.lua to be required from the app when debugged 91 | -- since it needs to be loaded form the IDE, which may be running a Lua version 92 | -- that doesn't include bit-wise operators, load the fragment at run-time. 93 | tcp = function() 94 | return load[[return { 95 | _ = assert(unix.socket()), 96 | buf = "", 97 | settimeout = function(self, t) self._timeout = t and t*1000 or nil end, 98 | connect = function(self, ip, port) 99 | return assert(unix.connect(self._, assert(ResolveIp(ip)), port)) 100 | end, 101 | close = function(self) return assert(unix.close(self._)) end, 102 | send = function(self, data) 103 | local CANWRITE = unix.POLLOUT | unix.POLLWRNORM 104 | local events = assert(unix.poll({[self._] = unix.POLLOUT}, self._timeout)) 105 | if not events[self._] then return nil, "timeout" end 106 | if events[self._] & CANWRITE == 0 then return nil, "close" end 107 | local sent, err = unix.send(self._, data) 108 | if not sent and err:name() == "EAGAIN" then return nil, "timeout" end 109 | return sent, err 110 | end, 111 | receive = function(self, pattern) 112 | local CANREAD = unix.POLLIN | unix.POLLRDNORM | unix.POLLRDBAND 113 | local size = tonumber(pattern) 114 | if size then 115 | if #self.buf < size then 116 | local events = assert(unix.poll({[self._] = unix.POLLIN}, self._timeout)) 117 | if not events[self._] then return nil, "timeout" end 118 | if events[self._] & CANREAD == 0 then return nil, "close" end 119 | self.buf = self.buf .. assert(unix.recv(self._, size-#self.buf)) 120 | end 121 | local res = self.buf:sub(1, size) 122 | self.buf = self.buf:sub(size+1) 123 | return res 124 | end 125 | while not self.buf:find("\n") do 126 | self.buf = self.buf .. assert(unix.recv(self._, 4096)) 127 | end 128 | local pos = self.buf:find("\n") 129 | local res = self.buf:sub(1, pos-1):gsub("\r","") 130 | self.buf = self.buf:sub(pos+1) 131 | return res 132 | end, 133 | }]]() 134 | end, 135 | } 136 | 137 | --[[ configuration example: 138 | -- if `redbean` executable is not in the project folder or PATH, set the path to it manually 139 | path.redbean = "/full/path/redbean.com" 140 | --]] 141 | -------------------------------------------------------------------------------- /tools/makedescriptions.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2017-18 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | -- Contributed by Chronos Phaenon Eosphoros (@cpeosphoros) 3 | 4 | local lfs = require("lfs") 5 | 6 | -- Usage: run this script from ZeroBrane Studio with the directory with the packages 7 | -- set as the project directory. 8 | -- The results are saved to DESCRIPTIONS.md and can be copied into README.md as a section. 9 | local path = "./" 10 | local includecomments = false 11 | local abridged = true 12 | 13 | -- These are dummy zbstudio "globals" needed for plugins to load. No api event 14 | -- will actually be called. Only zbstudio "globals" used in the main chunk of 15 | -- the plugin need to be included. Be sure to commit and PR changes in this 16 | -- string together with the plugins'. 17 | local env = [[ 18 | local function ID() return "a" end 19 | local function GetPathSeparator() return "" end 20 | local ide = {} 21 | local temp = {} 22 | temp.keymap = {} 23 | function ide:GetConfig() return temp end 24 | ide.specs = {} 25 | ide.config = {} 26 | local wxstc = {} 27 | ]] 28 | 29 | local function errorf(...) 30 | error(string.format(...), 2) 31 | end 32 | 33 | local function printf(...) 34 | print(string.format(...)) 35 | end 36 | 37 | local function readfile(filePath) 38 | local input = io.open(filePath) 39 | local data = input:read("*a") 40 | input:close() 41 | return data 42 | end 43 | 44 | local function sortedKeys(tbl) 45 | local sorted = {} 46 | for k, _ in pairs (tbl) do 47 | table.insert(sorted, k) 48 | end 49 | table.sort(sorted, function(i, j) 50 | if type(i) == type(j) then return i < j end 51 | return type(i) == "number" 52 | end) 53 | return sorted 54 | end 55 | 56 | local blockComment = false 57 | local function isComment(line) 58 | if blockComment then 59 | local _,_, block = string.find(line, "^%s*([-]*%]%])") 60 | if block then 61 | blockComment = false 62 | end 63 | return true 64 | end 65 | local text = line:gsub("^%s*", "") 66 | if text == "" then return true end 67 | local _,_, block = string.find(line, "^%s*([-]*%[%[)") 68 | if block then 69 | blockComment = true 70 | return true 71 | end 72 | local _,_, dashes = string.find(line, "^%s*([-]*)") 73 | return dashes ~= "" and dashes ~= "-" 74 | end 75 | 76 | local files = {} 77 | local cache = {} 78 | 79 | local function process(key) 80 | printf("Loading %s ", key) 81 | local package, err = load(env.." "..files[key], key) 82 | if not package then 83 | print(err) 84 | errorf("Error loading %s.", key) 85 | return 86 | end 87 | 88 | local ok 89 | ok, package = pcall(package) 90 | if not ok then 91 | print(package) 92 | errorf("Error calling %s. Package not loaded.", key) 93 | return 94 | end 95 | 96 | cache[key] = {} 97 | cache[key].name = package.name or "Not given." 98 | cache[key].description = package.description or "Not given." 99 | cache[key].author = package.author or "Not given." 100 | cache[key].version = package.version or 0 101 | cache[key].dependencies = package.dependencies or "None." 102 | 103 | if not( type(cache[key].dependencies) == "string" or 104 | type(cache[key].dependencies) == "number" or 105 | type(cache[key].dependencies) == "table" 106 | ) then 107 | errorf("Invalid dependencies in %s. Package not loaded.", key) 108 | end 109 | 110 | cache[key].comments = {} 111 | for line in string.gmatch(files[key], "([^\n]*)\n") do 112 | if not isComment(line) then break end 113 | table.insert(cache[key].comments, line) 114 | end 115 | end 116 | 117 | -------------------------------------------------------------------------------- 118 | -- Main execution block 119 | -------------------------------------------------------------------------------- 120 | 121 | for file in lfs.dir(path) do 122 | local filePath = path..file 123 | local extension = file:match("[^.]+$") 124 | if extension == "lua" then 125 | local ok, attrs = pcall(lfs.attributes, filePath) 126 | if ok and attrs.mode ~= "directory" then 127 | files[file] = readfile(filePath) 128 | end 129 | end 130 | end 131 | 132 | local keys = sortedKeys(files) 133 | 134 | local result = {} 135 | 136 | local function insert(str) 137 | table.insert(result,str) 138 | end 139 | 140 | insert("## Package List") 141 | insert("") 142 | 143 | local gitUrl = "" 144 | 145 | for _, key in ipairs(keys) do 146 | process(key) 147 | insert(("* [%s](%s): %s%s"):format( 148 | key, gitUrl..key, cache[key].description, abridged and "" or (" ([details](#%s))"):format(key))) 149 | end 150 | 151 | if not abridged then 152 | insert("") 153 | insert("## Plugin Details") 154 | 155 | for _, key in ipairs(keys) do 156 | insert("") 157 | insert("### "..key) 158 | insert("* **Name:** "..cache[key].name) 159 | insert("* **Description:** "..cache[key].description) 160 | insert("* **Author:** "..cache[key].author) 161 | insert("* **Version:** "..cache[key].version) 162 | local dependencies = cache[key].dependencies 163 | if type(dependencies) == "string" or type(dependencies) == "number" then 164 | insert("* **Dependencies:** "..dependencies) 165 | else --We have a table 166 | insert("* **Dependencies:**") 167 | local depKeys = sortedKeys(dependencies) 168 | for _, depKey in ipairs(depKeys) do 169 | if type(depKey) == "number" then 170 | insert("\t* "..dependencies[depKey]) 171 | else 172 | insert("\t* "..depKey..": "..dependencies[depKey]) 173 | end 174 | end 175 | end 176 | local comments = cache[key].comments 177 | if #comments > 0 and includecomments then 178 | insert("* **Comments:**") 179 | insert("") 180 | insert("```") 181 | for _, v in ipairs(comments) do 182 | insert(v) 183 | end 184 | insert("```") 185 | end 186 | end 187 | end 188 | 189 | print() 190 | 191 | local output = io.open(path.."DESCRIPTIONS.md", "w") 192 | for _, v in ipairs(result) do 193 | output:write(v.."\n") 194 | end 195 | output:close() 196 | -------------------------------------------------------------------------------- /todoall.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2017 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | -- Contributed by Chronos Phaenon Eosphoros (@cpeosphoros) 3 | -- Some code taken from those plugins: 4 | -- todo.lua: 5 | -- Copyright 2014 Paul Kulchenko, ZeroBrane LLC; All rights reserved 6 | -- Contributed by Mark Fainstein 7 | -- analyzeall.lua: 8 | -- Copyright 2014 Paul Kulchenko, ZeroBrane LLC; All rights reserved 9 | 10 | local id = ID("TODOAllpanel.referenceview") 11 | local TODOpanel = "TODOAllpanel" 12 | local refeditor 13 | local spec = {iscomment = {}} 14 | 15 | local function path2mask(s) 16 | return s 17 | :gsub('([%(%)%.%%%+%-%?%[%^%$%]])','%%%1') -- escape all special symbols 18 | :gsub("%*", ".*") -- but expand asterisk into sequence of any symbols 19 | :gsub("[\\/]","[\\\\/]") -- allow for any path 20 | end 21 | 22 | local fileTasks 23 | 24 | local function mapTODOS(fileName, text) 25 | local i = 0 26 | local tasks = {} 27 | while true do 28 | --find next todo index 29 | i = string.find(text, "TODO:", i+1) 30 | if i == nil then 31 | fileTasks[fileName] = tasks 32 | break 33 | end 34 | local j = string.find(text, "\n",i+1) 35 | local taskStr = string.sub(text, i+5, j) 36 | table.insert(tasks, {pos = i, str = taskStr}) 37 | end 38 | end 39 | 40 | local function readfile(filePath) 41 | local input = io.open(filePath) 42 | local data = input:read("*a") 43 | input:close() 44 | return data 45 | end 46 | 47 | local function sortedKeys(tbl) 48 | local sorted = {} 49 | for k, _ in pairs (tbl) do 50 | table.insert(sorted, k) 51 | end 52 | table.sort(sorted) 53 | return sorted 54 | end 55 | 56 | local projectPath 57 | 58 | local function fileNameFromPath(filePath) 59 | return filePath:gsub(projectPath, "") 60 | end 61 | 62 | local function mapProject(self, editor) 63 | 64 | local specs = self:GetConfig().ignore or {} 65 | if editor then 66 | mapTODOS(fileNameFromPath(ide:GetDocument(editor):GetFilePath()), editor:GetText()) 67 | else 68 | local masks = {} 69 | for i in ipairs(specs) do masks[i] = "^"..path2mask(specs[i]).."$" end 70 | for _, filePath in ipairs(ide:GetFileList(projectPath, true, "*.lua")) do 71 | local fileName = fileNameFromPath(filePath) 72 | local ignore = false or editor 73 | for _, spec in ipairs(masks) do 74 | ignore = ignore or fileName:find(spec) 75 | end 76 | -- TODO: testing here 77 | if not ignore then 78 | mapTODOS(fileName, readfile(filePath)) 79 | end 80 | end 81 | end 82 | 83 | local files = sortedKeys(fileTasks) 84 | local tasksListStr = "Tasks List: \n\n" 85 | local lineCounter = 2 86 | local positions = {} 87 | 88 | local function insertLine(line, pos, file) 89 | tasksListStr = tasksListStr .. line 90 | lineCounter = lineCounter + 1 91 | if pos then 92 | positions[lineCounter] = {pos = pos, file = file} 93 | end 94 | end 95 | 96 | for _, file in ipairs(files) do 97 | local tasks = fileTasks[file] 98 | if tasks and #tasks ~= 0 then 99 | insertLine(file .. ":\n", 1, file) 100 | for counter, taskStr in ipairs(tasks) do 101 | insertLine(counter.."."..taskStr.str, taskStr.pos, file) 102 | end 103 | insertLine("\n") 104 | end 105 | end 106 | 107 | refeditor:SetReadOnly(false) 108 | refeditor:SetText(tasksListStr) 109 | refeditor:SetReadOnly(true) 110 | 111 | --On click of a task, go to relevant position in the text 112 | refeditor:Connect(wxstc.wxEVT_STC_DOUBLECLICK, function() 113 | local line = refeditor:GetCurrentLine()+1 114 | local position = positions[line] 115 | if not position then return end 116 | local filePath = projectPath .. position.file 117 | local docs = ide:GetDocuments() 118 | 119 | local editor 120 | for _, doc in ipairs(docs) do 121 | if doc:GetFilePath() == filePath then 122 | editor = doc:GetEditor() 123 | break 124 | end 125 | end 126 | if not editor then 127 | editor = ide:LoadFile(filePath) 128 | if not editor then error("Couldn't load " .. filePath) end 129 | end 130 | 131 | editor:GotoPosEnforcePolicy(position.pos - 1) 132 | if not ide:GetEditorWithFocus(editor) then ide:GetDocument(editor):SetActive() end 133 | end) 134 | end 135 | 136 | return { 137 | name = "Show project-wide TODO panel", 138 | description = "Adds a project-wide panel for showing a tasks list.", 139 | author = "Chronos Phaenon Eosphoros", 140 | version = 0.13, 141 | dependencies = "1.60", 142 | 143 | onRegister = function(self) 144 | local e = ide:CreateBareEditor() 145 | refeditor = e 146 | 147 | local w, h = 250, 250 148 | local conf = function(pane) 149 | pane:Dock():MinSize(w,-1):BestSize(w,-1):FloatingSize(w,h) 150 | end 151 | local layout = ide:GetSetting("/view", "uimgrlayout") 152 | 153 | --if ide:IsPanelDocked(TODOpanel) then 154 | if not layout or not layout:find(TODOpanel) then 155 | ide:AddPanelDocked(ide.frame.projnotebook, e, TODOpanel, TR("PTasks"), conf) 156 | else 157 | ide:AddPanel(e, TODOpanel, TR("Tasks"), conf) 158 | end 159 | 160 | do -- markup handling in the reference panel 161 | -- copy some settings from the lua spec 162 | for _, s in ipairs({'lexer', 'lexerstyleconvert'}) do 163 | spec[s] = ide.specs.lua[s] 164 | end 165 | -- this allows the markup to be recognized in all token types 166 | for i = 0, 16 do spec.iscomment[i] = true end 167 | e:Connect(wxstc.wxEVT_STC_UPDATEUI, function() MarkupStyle(e,0,e:GetLineCount()) end) 168 | end 169 | 170 | e:SetReadOnly(true) 171 | e:SetWrapMode(wxstc.wxSTC_WRAP_NONE) 172 | e:SetupKeywords("lua",spec,ide:GetConfig().styles,ide:GetOutput():GetFont()) 173 | 174 | -- remove all margins 175 | for m = 0, 4 do e:SetMarginWidth(m, 0) end 176 | 177 | -- disable dragging to the panel 178 | e:Connect(wxstc.wxEVT_STC_DO_DROP, function(event) event:SetDragResult(wx.wxDragNone) end) 179 | 180 | local menu = ide:GetMenuBar():GetMenu(ide:GetMenuBar():FindMenu(TR("&Project"))) 181 | menu:InsertCheckItem(4, id, TR("Tasks List")..KSC(id)) 182 | menu:Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, function () 183 | local uimgr = ide:GetUIManager() 184 | uimgr:GetPane(TODOpanel):Show(not uimgr:GetPane(TODOpanel):IsShown()) 185 | uimgr:Update() 186 | end) 187 | ide:GetMainFrame():Connect(id, wx.wxEVT_UPDATE_UI, function (event) 188 | local pane = ide:GetUIManager():GetPane(TODOpanel) 189 | menu:Enable(event:GetId(), pane:IsOk()) -- disable if doesn't exist 190 | menu:Check(event:GetId(), pane:IsOk() and pane:IsShown()) 191 | end) 192 | end, 193 | 194 | onUnRegister = function(self) 195 | ide:RemoveMenuItem(id) 196 | end, 197 | 198 | onProjectLoad = function(self, project) 199 | fileTasks = {} 200 | projectPath = project 201 | mapProject(self) 202 | end, 203 | 204 | onEditorCharAdded = function(self, editor) --, event) 205 | mapProject(self, editor) 206 | end, 207 | } 208 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Description 2 | 3 | ZeroBrane Package is a collection of packages for [ZeroBrane Studio](http://studio.zerobrane.com). 4 | 5 | You can find more information about ZeroBrane Studio packages and plugins in the [documentation](http://studio.zerobrane.com/doc-plugin.html). 6 | 7 | ## Installation 8 | 9 | To install a plugin, copy its `.lua` file to `ZBS/packages/` or `HOME/.zbstudio/packages/` folder 10 | (where `ZBS` is the path to ZeroBrane Studio location and `HOME` is the path specified by the `HOME` environment variable). 11 | The first location allows you to have per-instance plugins, while the second allows to have per-user plugins. 12 | The second option may also be **preferrable for Mac OSX users** as the `ZBS/packages/` folder may be overwritten during an application upgrade. 13 | 14 | ## Dependencies 15 | 16 | The plugins may depend on a particular version of ZeroBrane Studio. 17 | One of the fields in the plugin description is `dependencies` that may have as its value 18 | (1) a table with various dependencies or (2) a minumum version number of ZeroBrane Studio required to run the plugin. 19 | 20 | If the version number for ZeroBrane Studio is **larger than the most recent released version** (for example, the current release version is 0.50, but the plugin has a dependency on 0.51), 21 | this means that it requires a development version currently being worked on (which will become the next release version). 22 | 23 | ## Package List 24 | 25 | * [analyzeall.lua](analyzeall.lua): Analyzes all files in a project. 26 | * [autodelimiter.lua](autodelimiter.lua): Adds auto-insertion of delimiters (), {}, [], '', and "". 27 | * [autodelimitersurroundselection.lua](autodelimitersurroundselection.lua): Extends auto-insertion of delimiters (), {}, [], '', and "" to add selection and removal of standalone pairs. 28 | * [autoindent.lua](autoindent.lua): Sets editor indentation based on file text analysis. 29 | * [autostartdebug.lua](autostartdebug.lua): Auto-starts debugger server. 30 | * [blockcursor.lua](blockcursor.lua): Switches cursor to a block cursor. 31 | * [clippy.lua](clippy.lua): Enables a stack-based clipboard which saves the last 10 entries. 32 | * [cloneview.lua](cloneview.lua): Clones the current editor tab. 33 | * [closetabsleftright.lua](closetabsleftright.lua): Closes editor tabs to the left and to the right of the current one. 34 | * [colourpicker.lua](colourpicker.lua): Selects color to insert in the document. 35 | * [cuberite.lua](cuberite.lua): Implements integration with Cuberite - the custom C++ minecraft server. 36 | * [documentmap.lua](documentmap.lua): Adds document map. 37 | * [edgemark.lua](edgemark.lua): Marks column edge for long lines. 38 | * [editorautofocusbymouse.lua](editorautofocusbymouse.lua): Moves focus to an editor tab the mouse is over. 39 | * [eris.lua](eris.lua): Implements integration with the Lua + Eris interpreter (5.3). 40 | * [escapetoquit.lua](escapetoquit.lua): Exits application on Escape. 41 | * [extregister.lua](extregister.lua): Registers known extensions to launch the IDE on Windows. 42 | * [filetreeoneclick.lua](filetreeoneclick.lua): Changes filetree to activate items on one-click (as in Sublime Text). 43 | * [hidemenu.lua](hidemenu.lua): Hides and shows the menu bar when pressing alt. 44 | * [hidemousewhentyping.lua](hidemousewhentyping.lua): Hides mouse cursor when typing. 45 | * [highlightselected.lua](highlightselected.lua): Highlights all instances of a selected word. 46 | * [launchtime.lua](launchtime.lua): Measures IDE startup performance up to the first IDLE event. 47 | * [localhelpmenu.lua](localhelpmenu.lua): Adds local help option to the menu. 48 | * [luadist.lua](luadist.lua): Provides LuaDist integration to install modules from LuaDist. 49 | * [maketoolbar.lua](maketoolbar.lua): Adds a menu item and toolbar button that run `make`. 50 | * [markchar.lua](markchar.lua): Marks characters when typed with specific indicators. 51 | * [moonscript.lua](moonscript.lua): Implements integration with Moonscript language. 52 | * [moonscriptlove.lua](moonscriptlove.lua): Implements integration with Moonscript with LÖVE. 53 | * [moveline.lua](moveline.lua): Adds moving line or selection up or down using `Ctrl-Shift-Up/Down`. 54 | * [noblinkcursor.lua](noblinkcursor.lua): Disables cursor blinking. 55 | * [openimagefile.lua](openimagefile.lua): Opens image file from the file tree. 56 | * [openra.lua](openra.lua): Adds API description for auto-complete and tooltip support for OpenRA. 57 | * [openwithdefault.lua](openwithdefault.lua): Opens file with Default Program when activated. 58 | * [outputclone.lua](outputclone.lua): Clones Output window to keep it on the screen when the application loses focus (OSX). 59 | * [outputtofile.lua](outputtofile.lua): Redirects debugging output to a file. 60 | * [overtype.lua](overtype.lua): Allows to switch overtyping on/off on systems that don't provide shortcut for that. 61 | * [projectsettings.lua](projectsettings.lua): Adds project settings loaded on project switch. 62 | * [realtimewatch.lua](realtimewatch.lua): Displays real-time values during debugging. 63 | * [redbean.lua](redbean.lua): Adds integration and debugging for Redbean web server. 64 | * [redis.lua](redis.lua): Integrates with Redis. 65 | * [referencepanel.lua](referencepanel.lua): Adds a panel for showing documentation based on tooltips. 66 | * [refreshproject.lua](refreshproject.lua): Refreshes project tree when files change (Windows only). 67 | * [remoteedit.lua](remoteedit.lua): Allows to edit files remotely while debugging is in progress. 68 | * [savealleveryxrunning.lua](savealleveryxrunning.lua): Saves all modified files every X seconds while running/debugging. 69 | * [saveonappswitch.lua](saveonappswitch.lua): Saves all modified files when app focus is lost. 70 | * [saveonfocuslost.lua](saveonfocuslost.lua): Saves a file when editor focus is lost. 71 | * [screenshot.lua](screenshot.lua): Takes a delayed screenshot of the application window and saves it into a file. 72 | * [shebangtype.lua](shebangtype.lua): Sets file type based on executable in shebang. 73 | * [showluareference.lua](showluareference.lua): Adds 'show lua reference' option to the editor menu. 74 | * [showreference.lua](showreference.lua): Adds 'show reference' option to the editor menu. 75 | * [striptrailingwhitespace.lua](striptrailingwhitespace.lua): Strips trailing whitespaces before saving a file. 76 | * [syntaxcheckontype.lua](syntaxcheckontype.lua): Reports syntax errors while typing (on `Enter`). 77 | * [tasks.lua](tasks.lua): Provides project wide tasks panel. 78 | * [tildemenu.lua](tildemenu.lua): Allows to enter tilde (~) on keyboards that may not have it. 79 | * [todo.lua](todo.lua): Adds a panel for showing a tasks list. 80 | * [todoall.lua](todoall.lua): Adds a project-wide panel for showing a tasks list. 81 | * [torch7.lua](torch7.lua): Implements integration with torch7 environment. 82 | * [uniquetabname.lua](uniquetabname.lua): Updates editor tab names to always stay unique. 83 | * [urho3d.lua](urho3d.lua): Implements integration with Urho3D game engine. 84 | * [verbosesaving.lua](verbosesaving.lua): Saves a copy of each file on save in a separate directory with date and time appended to the file name. 85 | * [wordcount.lua](wordcount.lua): Counts the number of words and other statistics in the document. 86 | * [wordwrapmenu.lua](wordwrapmenu.lua): Adds word wrap option to the menu. 87 | * [xml.lua](xml.lua): Adds XML syntax highlighting. 88 | 89 | ## Author 90 | 91 | Paul Kulchenko (paul@kulchenko.com) 92 | 93 | ## License 94 | 95 | See [LICENSE](LICENSE). 96 | -------------------------------------------------------------------------------- /torch7.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2015 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | -- Path handling for Torch and QLua is based on Torch/QLua interpreters from ZBS-torch by Soumith Chintala 3 | 4 | local win = ide.osname == 'Windows' 5 | local sep = win and ';' or ':' 6 | 7 | local debinit = [[ 8 | local mdb = require('mobdebug') 9 | local line = mdb.line 10 | mdb.line = function(v) 11 | local r = line(v) 12 | return type(v) == 'userdata' and loadstring("return "..r)() or r 13 | end]] 14 | 15 | local function fixBS(s) -- string callback to eliminate backspaces from Torch output 16 | while s:find("\b") do 17 | s = s 18 | :gsub("[^\b\r\n]\b","") -- remove a backspace and a previous character 19 | :gsub("^\b+","") -- remove all leading backspaces (if any) 20 | :gsub("([\r\n])\b+","%1") -- remove a backspace and a previous character 21 | end 22 | return s 23 | end 24 | 25 | local lpaths = { 26 | "install/share/lua/5.1/?.lua", "install/share/lua/5.1/?/init.lua", 27 | "share/lua/5.1/?.lua", "share/lua/5.1/?/init.lua", 28 | "./?.lua", "./?/init.lua", 29 | } 30 | local cpaths = { 31 | "install/lib/lua/5.1/?.", "install/lib/lua/5.1/loadall.", "install/lib/?.", 32 | "lib/lua/5.1/?.", "lib/lua/5.1/loadall.", "lib/?.", 33 | "?.", 34 | } 35 | local function setEnv(torchroot, usepackage) 36 | local tluapath = '' 37 | for _, val in pairs(lpaths) do 38 | tluapath = tluapath .. MergeFullPath(torchroot, val) .. ";" 39 | end 40 | local _, luapath = wx.wxGetEnv("LUA_PATH") 41 | wx.wxSetEnv("LUA_PATH", tluapath..(luapath or "")) 42 | 43 | local ext = win and 'dll' or 'so' 44 | local tluacpath = '' 45 | for _, val in pairs(cpaths) do 46 | tluacpath = tluacpath .. MergeFullPath(torchroot, val..ext) .. ";" 47 | end 48 | local _, luacpath = wx.wxGetEnv("LUA_CPATH") 49 | wx.wxSetEnv("LUA_CPATH", tluacpath..(luacpath or "")) 50 | 51 | local _, path = wx.wxGetEnv("PATH") 52 | wx.wxSetEnv("PATH", torchroot..(#path > 0 and sep..path or "")) 53 | 54 | local env = {LUA_PATH = luapath, LUA_CPATH = luacpath, PATH = path} 55 | 56 | if usepackage then -- also assign package variables if requested 57 | env.path, package.path = package.path or false, tluapath .. (package.path or "") 58 | env.cpath, package.cpath = package.cpath or false, tluacpath .. (package.cpath or "") 59 | end 60 | 61 | return env 62 | end 63 | 64 | local function unsetEnv(env) 65 | for env, val in ipairs(env) do 66 | if package[env] then 67 | package[env] = val or nil 68 | else 69 | if val and #val > 0 then wx.wxSetEnv(env, val) else wx.wxUnsetEnv(env) end 70 | end 71 | end 72 | end 73 | 74 | local function findCmd(cmd, env) 75 | local path = (os.getenv('PATH') or '')..sep 76 | ..(env or '')..sep 77 | ..(os.getenv('HOME') and os.getenv('HOME') .. '/bin' or '') 78 | local paths = {} 79 | local res 80 | for p in path:gmatch("[^"..sep.."]+") do 81 | res = res or GetFullPathIfExists(p, cmd) 82 | table.insert(paths, p) 83 | end 84 | 85 | if not res then 86 | ide:Print(("Can't find %s in any of the folders in PATH or TORCH_BIN: "):format(cmd) 87 | ..table.concat(paths, ", ")) 88 | return 89 | end 90 | return res 91 | end 92 | 93 | local qluaInterpreter = { 94 | name = "QLua-LuaJIT", 95 | description = "Qt hooks for luajit", 96 | api = {"baselib", "qlua"}, 97 | frun = function(self,wfilename,rundebug) 98 | local qlua = ide.config.path.qlua or findCmd('qlua', os.getenv('QLUA_BIN')) 99 | if not qlua then return end 100 | 101 | -- make minor modifications to the cpath to take care of OSX 102 | -- make sure the root is using Torch exe location 103 | local torchroot = MergeFullPath(GetPathWithSep(qlua), "../") 104 | local env = setEnv(torchroot) 105 | 106 | local filepath = wfilename:GetFullPath() 107 | local script 108 | if rundebug then 109 | ide:GetDebugger():SetOptions({runstart = ide.config.debugger.runonstart == true, init = debinit}) 110 | script = rundebug 111 | else 112 | -- if running on Windows and can't open the file, this may mean that 113 | -- the file path includes unicode characters that need special handling 114 | local fh = io.open(filepath, "r") 115 | if fh then fh:close() end 116 | if win and pcall(require, "winapi") 117 | and wfilename:FileExists() and not fh then 118 | winapi.set_encoding(winapi.CP_UTF8) 119 | filepath = winapi.short_path(filepath) 120 | end 121 | 122 | script = ('dofile [[%s]]'):format(filepath) 123 | end 124 | local code = ([[xpcall(function() io.stdout:setvbuf('no'); %s end,function(err) print(debug.traceback(err)) end)]]):format(script) 125 | local cmd = '"'..qlua..'" -e "'..code..'"' 126 | -- CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback) 127 | local pid = CommandLineRun(cmd,self:fworkdir(wfilename),true,false,fixBS) 128 | unsetEnv(env) 129 | return pid 130 | end, 131 | hasdebugger = true, 132 | fattachdebug = function(self) 133 | ide:GetDebugger():SetOptions({ 134 | runstart = ide.config.debugger.runonstart == true, 135 | init = debinit 136 | }) 137 | end, 138 | scratchextloop = true, 139 | } 140 | 141 | local torchInterpreter = { 142 | name = "Torch-7", 143 | description = "Torch machine learning package", 144 | api = {"baselib", "torch"}, 145 | frun = function(self,wfilename,rundebug) 146 | -- check if the path is configured 147 | local torch = ide.config.path.torch or findCmd(win and 'th.bat' or 'th', os.getenv('TORCH_BIN')) 148 | if not torch then return end 149 | 150 | local filepath = wfilename:GetFullPath() 151 | if rundebug then 152 | ide:GetDebugger():SetOptions({runstart = ide.config.debugger.runonstart == true, init = debinit}) 153 | -- update arg to point to the proper file 154 | rundebug = ('if arg then arg[0] = [[%s]] end '):format(filepath)..rundebug 155 | 156 | local tmpfile = wx.wxFileName() 157 | tmpfile:AssignTempFileName(".") 158 | filepath = tmpfile:GetFullPath() 159 | local f = io.open(filepath, "w") 160 | if not f then 161 | ide:Print("Can't open temporary file '"..filepath.."' for writing.") 162 | return 163 | end 164 | f:write("io.stdout:setvbuf('no'); " .. rundebug) 165 | f:close() 166 | else 167 | -- if running on Windows and can't open the file, this may mean that 168 | -- the file path includes unicode characters that need special handling 169 | local fh = io.open(filepath, "r") 170 | if fh then fh:close() end 171 | if win and pcall(require, "winapi") 172 | and wfilename:FileExists() and not fh then 173 | winapi.set_encoding(winapi.CP_UTF8) 174 | filepath = winapi.short_path(filepath) 175 | end 176 | end 177 | 178 | -- doesn't need set environment with setEnv as it's already done in onInterpreterLoad 179 | local params = ide.config.arg.any or ide.config.arg.torch7 or '' 180 | local uselua = wx.wxDirExists(torch) 181 | local cmd = ([["%s" "%s" %s]]):format( 182 | uselua and ide:GetInterpreters().luadeb:GetExePath("") or torch, filepath, params) 183 | -- CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback) 184 | return CommandLineRun(cmd,self:fworkdir(wfilename),true,false,fixBS,nil, 185 | function() if rundebug then wx.wxRemoveFile(filepath) end end) 186 | end, 187 | hasdebugger = true, 188 | fattachdebug = function(self) 189 | ide:GetDebugger():SetOptions({ 190 | runstart = ide.config.debugger.runonstart == true, 191 | init = debinit 192 | }) 193 | end, 194 | scratchextloop = true, 195 | takeparameters = true, 196 | } 197 | 198 | return { 199 | name = "Torch7", 200 | description = "Implements integration with torch7 environment.", 201 | author = "Paul Kulchenko", 202 | version = 0.58, 203 | dependencies = "1.40", 204 | 205 | onRegister = function(self) 206 | ide:AddInterpreter("torch", torchInterpreter) 207 | ide:AddInterpreter("qlua", qluaInterpreter) 208 | end, 209 | onUnRegister = function(self) 210 | ide:RemoveInterpreter("torch") 211 | ide:RemoveInterpreter("qlua") 212 | end, 213 | 214 | onInterpreterLoad = function(self, interpreter) 215 | if interpreter:GetFileName() ~= "torch" then return end 216 | local torch = ide.config.path.torch or findCmd(win and 'th.bat' or 'th', os.getenv('TORCH_BIN')) 217 | if not torch then return end 218 | local uselua = wx.wxDirExists(torch) 219 | local torchroot = uselua and torch or MergeFullPath(GetPathWithSep(torch), "../") 220 | interpreter.env = setEnv(torchroot, true) 221 | end, 222 | onInterpreterClose = function(self, interpreter) 223 | if interpreter:GetFileName() ~= "torch" then return end 224 | if interpreter.env then unsetEnv(interpreter.env) end 225 | end, 226 | } 227 | -------------------------------------------------------------------------------- /moonscriptlove.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2014 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | 3 | local exe = {moonc = nil, love = nil} 4 | local win = ide.osname == "Windows" 5 | 6 | local init = [=[ 7 | (loadstring or load)([[ 8 | if pcall(require, "mobdebug") then 9 | io.stdout:setvbuf('no') 10 | local cache = {} 11 | local lt = require("moonscript.line_tables") 12 | local rln = require("moonscript.errors").reverse_line_number 13 | local mdb = require "mobdebug" 14 | mdb.linemap = function(line, src) 15 | return src:find('%.moon$') and (tonumber(lt[src] and rln(src:gsub("^@",""), lt[src], line, cache) or line) or 1) or nil 16 | end 17 | mdb.loadstring = require("moonscript").loadstring 18 | end 19 | ]])() 20 | ]=] 21 | 22 | local interpreter = { 23 | name = "Moonscript LÖVE", 24 | description = "Moonscript LÖVE interpreter", 25 | api = {"baselib"}, 26 | frun = function(self,wfilename,rundebug) 27 | exe.moonc = exe.moonc or ide.config.path.moonc -- check if the path is configured 28 | if not exe.moonc then 29 | local sep = win and ';' or ':' 30 | local default = win and GenerateProgramFilesPath('moonscript', sep)..sep or '' 31 | local path = default 32 | ..(os.getenv('PATH') or '')..sep 33 | ..(GetPathWithSep(self:fworkdir(wfilename)))..sep 34 | ..(os.getenv('HOME') and GetPathWithSep(os.getenv('HOME'))..'bin' or '') 35 | local paths = {} 36 | for p in path:gmatch("[^"..sep.."]+") do 37 | exe.moonc = exe.moonc or GetFullPathIfExists(p, win and 'moonc.exe' or 'moonc') 38 | table.insert(paths, p) 39 | end 40 | if not exe.moonc then 41 | ide:Print("Can't find moonc executable in any of the following folders: " 42 | ..table.concat(paths, ", ")) 43 | return 44 | end 45 | end 46 | 47 | exe.love = exe.love or ide.config.path.love -- check if the path is configured 48 | if not exe.love then 49 | local sep = win and ';' or ':' 50 | local default = win and GenerateProgramFilesPath('love', sep)..sep or '' 51 | local path = default 52 | ..(os.getenv('PATH') or '')..sep 53 | ..(GetPathWithSep(self:fworkdir(wfilename)))..sep 54 | ..(os.getenv('HOME') and GetPathWithSep(os.getenv('HOME'))..'bin' or '') 55 | local paths = {} 56 | for p in path:gmatch("[^"..sep.."]+") do 57 | exe.love = exe.love or GetFullPathIfExists(p, win and 'love.exe' or 'love') 58 | table.insert(paths, p) 59 | end 60 | if not exe.love then 61 | ide:Print("Can't find love executable in any of the following folders: " 62 | ..table.concat(paths, ", ")) 63 | return 64 | end 65 | end 66 | 67 | local filepath = wfilename:GetFullPath() 68 | if rundebug then 69 | ide:GetDebugger():SetOptions({ 70 | init = init, 71 | runstart = ide.config.debugger.runonstart == true, 72 | }) 73 | 74 | rundebug = ('require("mobdebug").start()\nrequire("moonscript").dofile [[%s]]'):format(filepath) 75 | 76 | local tmpfile = wx.wxFileName() 77 | tmpfile:AssignTempFileName(".") 78 | filepath = tmpfile:GetFullPath() 79 | local f = io.open(filepath, "w") 80 | if not f then 81 | ide:Print("Can't open temporary file '"..filepath.."' for writing") 82 | return 83 | end 84 | f:write(init..rundebug) 85 | f:close() 86 | else 87 | -- if running on Windows and can't open the file, this may mean that 88 | -- the file path includes unicode characters that need special handling 89 | local fh = io.open(filepath, "r") 90 | if fh then fh:close() end 91 | if ide.osname == 'Windows' and pcall(require, "winapi") 92 | and wfilename:FileExists() and not fh then 93 | winapi.set_encoding(winapi.CP_UTF8) 94 | filepath = winapi.short_path(filepath) 95 | end 96 | end 97 | local params = ide.config.arg.any or ide.config.arg.moonscript 98 | local code = ([["%s"]]):format(filepath) 99 | local cmd = {} 100 | cmd.moonc = '"'..exe.moonc..'" -t .moonscript-love "'.. self:fworkdir(wfilename) .. '"' .. (params and " "..params or "") 101 | cmd.love = '"' .. exe.love .. '" .' 102 | 103 | -- CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback) 104 | ide:Print("Compiling MoonScript") 105 | return CommandLineRun(cmd.moonc,self:fworkdir(wfilename), true, false, nil, nil, 106 | function() 107 | ide:Print("Starting Love2D") 108 | CommandLineRun(cmd.love, self:fworkdir(wfilename), true, false, nil, nil, function() 109 | if rundebug then wx.wxRemoveFile(filepath) end 110 | end) 111 | end) 112 | end, 113 | hasdebugger = true, 114 | fattachdebug = function(self) ide:GetDebugger():SetOptions({init = init}) end, 115 | skipcompile = true, 116 | unhideanywindow = true, 117 | takeparameters = true, 118 | } 119 | 120 | local spec = { 121 | exts = {"moon"}, 122 | lexer = wxstc.wxSTC_LEX_COFFEESCRIPT, 123 | apitype = "lua", 124 | linecomment = "--", 125 | sep = ".\\", 126 | 127 | -- borrow this logic from the Lua spec 128 | typeassigns = ide.specs.lua and ide.specs.lua.typeassigns, 129 | 130 | lexerstyleconvert = { 131 | text = {wxstc.wxSTC_COFFEESCRIPT_IDENTIFIER,}, 132 | 133 | lexerdef = {wxstc.wxSTC_COFFEESCRIPT_DEFAULT,}, 134 | comment = {wxstc.wxSTC_COFFEESCRIPT_COMMENT, 135 | wxstc.wxSTC_COFFEESCRIPT_COMMENTLINE, 136 | wxstc.wxSTC_COFFEESCRIPT_COMMENTDOC,}, 137 | stringtxt = {wxstc.wxSTC_COFFEESCRIPT_STRING, 138 | wxstc.wxSTC_COFFEESCRIPT_CHARACTER, 139 | wxstc.wxSTC_COFFEESCRIPT_LITERALSTRING,}, 140 | stringeol = {wxstc.wxSTC_COFFEESCRIPT_STRINGEOL,}, 141 | preprocessor= {wxstc.wxSTC_COFFEESCRIPT_PREPROCESSOR,}, 142 | operator = {wxstc.wxSTC_COFFEESCRIPT_OPERATOR,}, 143 | number = {wxstc.wxSTC_COFFEESCRIPT_NUMBER,}, 144 | 145 | keywords0 = {wxstc.wxSTC_COFFEESCRIPT_WORD,}, 146 | keywords1 = {wxstc.wxSTC_COFFEESCRIPT_WORD2,}, 147 | keywords2 = {wxstc.wxSTC_COFFEESCRIPT_GLOBALCLASS,}, 148 | }, 149 | 150 | keywords = { 151 | [[and break do else elseif end for function if in not or repeat return then until while local ]] 152 | ..[[super with import export class extends from using continue switch when]], 153 | 154 | [[_G _VERSION _ENV false io.stderr io.stdin io.stdout nil math.huge math.pi self true]], 155 | 156 | [[]], 157 | 158 | [[assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring 159 | module next pairs pcall print rawequal rawget rawlen rawset require 160 | select setfenv setmetatable tonumber tostring type unpack xpcall 161 | bit32.arshift bit32.band bit32.bnot bit32.bor bit32.btest bit32.bxor bit32.extract 162 | bit32.lrotate bit32.lshift bit32.replace bit32.rrotate bit32.rshift 163 | coroutine.create coroutine.resume coroutine.running coroutine.status coroutine.wrap coroutine.yield 164 | debug.debug debug.getfenv debug.gethook debug.getinfo debug.getlocal 165 | debug.getmetatable debug.getregistry debug.getupvalue debug.getuservalue debug.setfenv 166 | debug.sethook debug.setlocal debug.setmetatable debug.setupvalue debug.setuservalue 167 | debug.traceback debug.upvalueid debug.upvaluejoin 168 | io.close io.flush io.input io.lines io.open io.output io.popen io.read io.tmpfile io.type io.write 169 | close flush lines read seek setvbuf write 170 | math.abs math.acos math.asin math.atan math.atan2 math.ceil math.cos math.cosh math.deg math.exp 171 | math.floor math.fmod math.frexp math.ldexp math.log math.log10 math.max math.min math.modf 172 | math.pow math.rad math.random math.randomseed math.sin math.sinh math.sqrt math.tan math.tanh 173 | os.clock os.date os.difftime os.execute os.exit os.getenv os.remove os.rename os.setlocale os.time os.tmpname 174 | package.loadlib package.searchpath package.seeall package.config 175 | package.cpath package.loaded package.loaders package.path package.preload package.searchers 176 | string.byte string.char string.dump string.find string.format string.gmatch string.gsub string.len 177 | string.lower string.match string.rep string.reverse string.sub string.upper 178 | byte find format gmatch gsub len lower match rep reverse sub upper 179 | table.concat table.insert table.maxn table.pack table.remove table.sort table.unpack]] 180 | }, 181 | } 182 | 183 | local name = 'moonscriptlove' 184 | return { 185 | name = "Moonscript LÖVE", 186 | description = "Implements integration with Moonscript with LÖVE.", 187 | author = "Paul Kulchenko, Dominik \"Zatherz\" Banaszak", 188 | version = 0.33, 189 | dependencies = "1.40", 190 | 191 | onRegister = function(self) 192 | ide:AddInterpreter(name, interpreter) 193 | ide:AddSpec(name, spec) 194 | end, 195 | onUnRegister = function(self) 196 | ide:RemoveInterpreter(name) 197 | ide:RemoveSpec(name) 198 | end, 199 | } 200 | -------------------------------------------------------------------------------- /documentmap.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2014-18 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | 3 | local mappanel = "documentmappanel" 4 | local markers = {CURRENT = "docmap.current", BACKGROUND = "docmap.background"} 5 | local editormap, editorlinked 6 | local id 7 | local win = ide.osname == 'Windows' 8 | local needupdate 9 | local colors = { -- default values if no style colors are set 10 | text = {64, 64, 64}, 11 | background = {208, 208, 208}, 12 | current = {240, 240, 230}, 13 | } 14 | local function switchEditor(editor) 15 | if editorlinked == editor then return end 16 | if editormap then 17 | if editor then 18 | local font = editor:GetFont() 19 | editormap:SetFont(font) 20 | editormap:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, font) 21 | 22 | -- reset styles when switching the editor 23 | local styles = ide:GetConfig().styles 24 | markers[markers.BACKGROUND] = ide:AddMarker(markers.BACKGROUND, 25 | wxstc.wxSTC_MARK_BACKGROUND, 26 | styles.text.fg or colors.text, 27 | styles.sel.bg or colors.background) 28 | editormap:MarkerDefine(ide:GetMarker(markers.BACKGROUND)) 29 | 30 | markers[markers.CURRENT] = ide:AddMarker(markers.CURRENT, 31 | wxstc.wxSTC_MARK_BACKGROUND, 32 | styles.text.fg or colors.text, 33 | styles.caretlinebg.bg or colors.current) 34 | editormap:MarkerDefine(ide:GetMarker(markers.CURRENT)) 35 | end 36 | 37 | editormap:MarkerDeleteAll(markers[markers.CURRENT]) 38 | editormap:MarkerDeleteAll(markers[markers.BACKGROUND]) 39 | end 40 | if editorlinked then 41 | -- clear the editor in case the last editor tab was closed 42 | if editormap and not editor 43 | and ide:GetEditorNotebook():GetPageCount() == 1 then 44 | editormap:SetDocPointer() 45 | end 46 | end 47 | if editor then 48 | editormap:SetDocPointer(editor:GetDocPointer()) 49 | end 50 | editorlinked = editor 51 | end 52 | 53 | local function screenFirstLast(e) 54 | local firstline = e:DocLineFromVisible(e:GetFirstVisibleLine()) 55 | local linesvisible = (e:DocLineFromVisible(e:GetFirstVisibleLine()+e:LinesOnScreen()-1) 56 | - firstline) 57 | return firstline, math.min(e:GetLineCount(), firstline + linesvisible) 58 | end 59 | 60 | local function sync(e1, e2) 61 | local firstline, lastline = screenFirstLast(e1) 62 | 63 | e2:MarkerDeleteAll(markers[markers.BACKGROUND]) 64 | for line = firstline, lastline do 65 | e2:MarkerAdd(line, markers[markers.BACKGROUND]) 66 | end 67 | 68 | local currline = e1:GetCurrentLine() 69 | e2:MarkerDeleteAll(markers[markers.CURRENT]) 70 | e2:MarkerAdd(currline, markers[markers.CURRENT]) 71 | 72 | local linesmax1 = math.max(1, e1:GetLineCount() - (lastline-firstline)) 73 | local linesmax2 = math.max(1, e2:GetLineCount() - e2:LinesOnScreen()) 74 | local line2 = firstline * linesmax2 / linesmax1 75 | e2:SetFirstVisibleLine(e2:VisibleFromDocLine(math.floor(line2))) 76 | 77 | -- force refresh to keep the map editor up-to-date and reduce jumpy scroll 78 | if win then e2:Refresh() e2:Update() end 79 | end 80 | 81 | return { 82 | name = "Document Map", 83 | description = "Adds document map.", 84 | author = "Paul Kulchenko", 85 | version = 0.31, 86 | dependencies = "1.30", 87 | 88 | onRegister = function(self) 89 | local e = wxstc.wxStyledTextCtrl(ide:GetMainFrame(), wx.wxID_ANY, 90 | wx.wxDefaultPosition, wx.wxSize(20, 20), wx.wxBORDER_NONE) 91 | editormap = e 92 | 93 | local w, h = 150, 150 94 | ide:AddPanel(e, mappanel, TR("Document Map"), function(pane) 95 | pane:Dock():Right():TopDockable(false):BottomDockable(false) 96 | :MinSize(w,-1):BestSize(w,-1):FloatingSize(w,h) 97 | end) 98 | 99 | -- remove all margins 100 | for m = 0, 4 do e:SetMarginWidth(m, 0) end 101 | e:SetUseHorizontalScrollBar(false) 102 | e:SetUseVerticalScrollBar(false) 103 | e:SetZoom(self:GetConfig().zoom or -7) 104 | e:SetSelBackground(false, wx.wxBLACK) 105 | e:SetCaretStyle(0) -- disable caret as it may be visible when selecting on inactive map 106 | e:SetCursor(wx.wxCursor(wx.wxCURSOR_ARROW)) 107 | 108 | -- disable dragging from or to the map 109 | e:Connect(wxstc.wxEVT_STC_START_DRAG, function(event) event:SetDragText("") end) 110 | e:Connect(wxstc.wxEVT_STC_DO_DROP, function(event) event:SetDragResult(wx.wxDragNone) end) 111 | 112 | do -- set readonly when switched to 113 | local ro 114 | e:Connect(wx.wxEVT_SET_FOCUS, function() ro = e:GetReadOnly() e:SetReadOnly(true) end) 115 | e:Connect(wx.wxEVT_KILL_FOCUS, function() e:SetReadOnly(ro or false) end) 116 | end 117 | 118 | local function setFocus(editor) 119 | editor:SetFocus() 120 | if ide.osname == 'Macintosh' then 121 | -- manually trigger KILL_FOCUS event on OSX: http://trac.wxwidgets.org/ticket/14142 122 | editormap:GetEventHandler():ProcessEvent(wx.wxFocusEvent(wx.wxEVT_KILL_FOCUS)) 123 | end 124 | end 125 | 126 | local function jumpLinked(point) 127 | local pos = e:PositionFromPoint(point) 128 | local firstline, lastline = screenFirstLast(editorlinked) 129 | local onscreen = lastline-firstline 130 | local topline = math.floor(e:LineFromPosition(pos)-onscreen/2) 131 | editorlinked:SetFirstVisibleLine(editorlinked:VisibleFromDocLine(topline)) 132 | end 133 | 134 | local scroll 135 | local function scrollLinked(point) 136 | local onscreen = math.min(editorlinked:LinesOnScreen(), editorlinked:GetLineCount()) 137 | local line = e:LineFromPosition(e:PositionFromPoint(point)) 138 | local lineheight = e:TextHeight(line) 139 | local count = e:GetLineCount() 140 | local height = math.min(count * lineheight, e:GetClientSize():GetHeight()) 141 | local scrollnow = (point:GetY() - scroll) / (height - onscreen * lineheight) 142 | local topline = math.floor((count-onscreen)*scrollnow) 143 | editorlinked:SetFirstVisibleLine(editorlinked:VisibleFromDocLine(topline)) 144 | end 145 | 146 | e:Connect(wx.wxEVT_LEFT_DOWN, function(event) 147 | if not editorlinked then return end 148 | 149 | local point = event:GetPosition() 150 | local pos = e:PositionFromPoint(point) 151 | local line = e:LineFromPosition(pos) 152 | local firstline, lastline = screenFirstLast(editorlinked) 153 | if line >= firstline and line <= lastline then 154 | scroll = (line-firstline) * e:TextHeight(line) 155 | if win then e:CaptureMouse() end 156 | else 157 | jumpLinked(point) 158 | setFocus(editorlinked) 159 | end 160 | end) 161 | e:Connect(wx.wxEVT_LEFT_UP, function(event) 162 | if not editorlinked then return end 163 | if scroll then 164 | scroll = nil 165 | setFocus(editorlinked) 166 | if win then e:ReleaseMouse() end 167 | end 168 | end) 169 | e:Connect(wx.wxEVT_MOTION, function(event) 170 | if not editorlinked then return end 171 | if scroll then scrollLinked(event:GetPosition()) end 172 | end) 173 | -- ignore all double click events as they cause selection in the editor 174 | e:Connect(wx.wxEVT_LEFT_DCLICK, function(event) end) 175 | -- ignore context menu 176 | e:Connect(wx.wxEVT_CONTEXT_MENU, function(event) end) 177 | -- set the cursor so it doesn't look like vertical beam 178 | e:Connect(wx.wxEVT_SET_CURSOR, function(event) 179 | event:SetCursor(wx.wxCursor(wx.wxCURSOR_ARROW)) 180 | end) 181 | 182 | local menu = ide:FindTopMenu("&View") 183 | id = ID("documentmap.documentmapview") 184 | menu:InsertCheckItem(4, id, TR("Document Map Window")..KSC(id)) 185 | ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, function (event) 186 | local uimgr = ide:GetUIManager() 187 | uimgr:GetPane(mappanel):Show(not uimgr:GetPane(mappanel):IsShown()) 188 | uimgr:Update() 189 | end) 190 | ide:GetMainFrame():Connect(id, wx.wxEVT_UPDATE_UI, function (event) 191 | local pane = ide:GetUIManager():GetPane(mappanel) 192 | ide:GetMenuBar():Enable(event:GetId(), pane:IsOk()) -- disable if doesn't exist 193 | ide:GetMenuBar():Check(event:GetId(), pane:IsOk() and pane:IsShown()) 194 | end) 195 | end, 196 | 197 | onUnRegister = function(self) 198 | switchEditor() 199 | ide:RemoveMenuItem(id) 200 | ide:RemovePanel(mappanel) 201 | end, 202 | 203 | onEditorFocusSet = function(self, editor) 204 | if editorlinked ~= editor then 205 | switchEditor(editor) 206 | 207 | -- fix markers in the editor, otherwise they are shown as default markers 208 | editor:MarkerDefine(markers[markers.CURRENT], wxstc.wxSTC_MARK_EMPTY) 209 | editor:MarkerDefine(markers[markers.BACKGROUND], wxstc.wxSTC_MARK_EMPTY) 210 | 211 | local doc = ide:GetDocument(editor) 212 | if editormap and doc then editor.SetupKeywords(editormap, doc:GetFileExt()) end 213 | needupdate = true 214 | end 215 | end, 216 | 217 | onEditorClose = function(self, editor) 218 | if editor == editorlinked then switchEditor() end 219 | end, 220 | 221 | onEditorUpdateUI = function(self, editor) 222 | needupdate = true 223 | end, 224 | 225 | onEditorPainted = function(self, editor) 226 | if editormap and editor == editorlinked and needupdate then 227 | needupdate = false 228 | sync(editorlinked, editormap) 229 | end 230 | end, 231 | } 232 | 233 | --[[ configuration example: 234 | documentmap = {zoom = -7} -- zoom can be set from -10 (smallest) to 0 (normal) 235 | --]] 236 | -------------------------------------------------------------------------------- /luadist.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2013 Paul Kulchenko 2 | 3 | local script = ([==[ 4 | io.stdout:setvbuf('no') 5 | local conf = require 'dist.config' 6 | for k, v in pairs(%s) do conf[k] = v end 7 | for k, v in pairs(%s) do conf.variables[k] = v end 8 | os.exit = function() error('done') end 9 | local s = os.time() 10 | local ok, err = pcall(require('luadist').%s.run, [[%s]], %s) 11 | if not ok and not err:find('done$') then print(err) end 12 | print('Completed in '..(os.time()-s)..' second(s).')]==]):gsub('\n', '; ') 13 | 14 | local echoscript = ([==[ 15 | print('PATH: ', os.getenv('PATH')) 16 | print('LUA_PATH: ', os.getenv('LUA_PATH')) 17 | print('LUA_CPATH: ', os.getenv('LUA_CPATH')) 18 | print([[params: %s]]) 19 | print([[variables: %s]]) 20 | --[[%s]] 21 | print([[root: %s]]) 22 | print([[libs: %s]]) 23 | ]==]):gsub('\n', '; ') 24 | 25 | local win, mac = ide.osname == 'Windows', ide.osname == 'Macintosh' 26 | local ext = win and 'dll' or 'so' 27 | local distarch = mac and 'Darwin' or win and 'Windows' or 'Linux' 28 | local disttype = ide.osarch 29 | local function serialize(s) 30 | return require('mobdebug').line(s, {comment = false}):gsub('"',"'") end 31 | 32 | local function run(plugin, command, ...) 33 | local libs = {...} 34 | local ver = tonumber(libs[1]) and tostring(table.remove(libs, 1)) or '5.1' 35 | local opt = type(libs[#libs]) == 'table' and table.remove(libs) or {} 36 | local int = ide:GetConfig().default.interpreter 37 | local exe = ide:GetInterpreters()[int]:fexepath("") 38 | local root = plugin:GetConfig().root or MergeFullPath(ide.oshome, 'luadist/'..ver) 39 | local install = command == 'install' 40 | local params = { 41 | distinfos_dir = 'dists', 42 | source = not (install and (win or mac)), 43 | arch = distarch, 44 | type = disttype, 45 | -- caching doesn't work well when mixing binary and source installs; 46 | -- can still be enabled manually from `install` call ({cache = true}) 47 | cache = false, 48 | -- only specify components when installing, otherwise 49 | -- removing components doesn't work (as only listed ones are removed). 50 | components = install and {'Runtime', 'Documentation', 'Header', 'Library', 'Unspecified'} or nil, 51 | -- need to reset all *_dir and *_file references as they are generated 52 | -- before arch is set, which makes separators not always correct. 53 | cache_dir = MergeFullPath('tmp', 'cache'), 54 | log_file = MergeFullPath('tmp', 'luadist.log'), 55 | manifest_file = MergeFullPath('tmp/cache', '.gitmodules'), 56 | dep_cache_file = MergeFullPath('tmp/cache', '.depcache'), 57 | -- "manifest download" has clever logic to figure out root directory 58 | -- based on package.path, which is not quite correct when Lua5.2 is 59 | -- the current interpreter; set it explicitly. 60 | root_dir = root, 61 | } 62 | local variables = { 63 | CMAKE_SHARED_MODULE_CREATE_C_FLAGS = mac and "-bundle -undefined dynamic_lookup" or nil, 64 | CMAKE_FIND_FRAMEWORK = mac and "LAST" or nil, 65 | CMAKE_OSX_ARCHITECTURES = mac and "i386 -arch x86_64" or nil, 66 | } 67 | -- update manually specified parameters: 68 | -- upper-case only -- CMake, all others -- LuaDist parameters 69 | for k,v in pairs(opt) do 70 | if k:match('^[A-Z_]+$') then variables[k] = v else params[k] = v end 71 | end 72 | 73 | -- .depcache keeps track of installed modules, but doesn't reset the 74 | -- cache when switching between binary/source, so when lpeg-0.12 (source) 75 | -- is in the cache and (binary) is requested, the error is returned. 76 | -- reset cache timeout to avoid binary/source mismatch 77 | if not params.cache_timeout and not params.source then 78 | params.cache_timeout = 0 end 79 | 80 | -- not the best way to hardcode the Lua versions, but LuaDist doesn't 81 | -- accept lua-5.2 as a version to install and doesn't report the latest. 82 | local realver = ver == '5.2' and '5.2.2' or '5.1.5' 83 | local fakedist = MergeFullPath(root, 'dists/lua-'..ver..'/dist.info') 84 | local realdist = GetFullPathIfExists(root, 'dists/lua-'..realver..'/dist.info') 85 | 86 | if install and #libs > 0 then 87 | local installlua = libs[1]:find('^lua-') 88 | if params.source then 89 | if wx.wxFileExists(fakedist) then 90 | -- remove file and the folder 91 | wx.wxRemoveFile(fakedist) 92 | wx.wxRmdir(GetPathWithSep(fakedist)) 93 | end 94 | if not realdist then -- maybe a different Lua version installed? 95 | local distdir = MergeFullPath(root, params.distinfos_dir) 96 | local candidates = ide:GetFileList(distdir, true, 'dist.info') 97 | for _, file in ipairs(candidates) do 98 | local luaver = file:match('[/\\]lua%-([%d%.]+)[/\\]dist.info$') 99 | if luaver then realver = luaver; break end 100 | end 101 | end 102 | if not installlua then table.insert(libs, 1, 'lua-'..realver) end 103 | elseif not installlua 104 | and not wx.wxFileExists(fakedist) and not realdist then 105 | local distinfo = ('version="%s"\nname="lua"\narch="%s"\ntype="%s"\nfiles={Runtime={}}') 106 | :format(ver, distarch, disttype) 107 | local ok, err = FileWrite(fakedist, distinfo) 108 | if not ok then 109 | ide:GetConsole():Error(("Can't write dist.info file to '%s': %s") 110 | :format(fakedist, err)) 111 | return 112 | end 113 | table.insert(libs, 1, 'lua-'..ver) 114 | end 115 | end 116 | 117 | if command ~= 'help' then 118 | ide:GetConsole():Print(("Running '%s' for Lua %s in '%s'."):format(command, ver, root)) 119 | end 120 | 121 | local cmd = ('"%s" -e "%s"'):format( 122 | exe, 123 | (command == 'echo' and echoscript or script):format( 124 | serialize(params), serialize(variables), command, root, serialize(libs)) 125 | ) 126 | 127 | -- add "clibs" to PATH to allow required DLLs to load 128 | local _, path = wx.wxGetEnv("PATH") 129 | if win and path then 130 | local clibs = MergeFullPath(GetPathWithSep(exe), 'clibs') 131 | -- set it first in case the current interpreter is Lua 5.2 and PATH is already modified 132 | wx.wxSetEnv("PATH", clibs..';'..path) 133 | end 134 | -- set LUA_DIR as LuaDist sometime picks up proxy liblua, 135 | -- which is not suitable for linking 136 | local _, ldir = wx.wxGetEnv("LUA_DIR") 137 | if win then wx.wxSetEnv("LUA_DIR", root) end 138 | 139 | local workdir = wx.wxFileName.SplitPath(ide.editorFilename) 140 | CommandLineToShell(CommandLineRun(cmd,workdir,true,false), true) 141 | 142 | -- restore environment 143 | if win and path then wx.wxSetEnv("PATH", path) end 144 | if win and ldir then wx.wxSetEnv("LUA_DIR", ldir) end 145 | end 146 | 147 | local paths = {} 148 | 149 | return { 150 | name = "LuaDist integration", 151 | description = "Provides LuaDist integration to install modules from LuaDist.", 152 | author = "Paul Kulchenko", 153 | version = 0.21, 154 | dependencies = "1.70", 155 | 156 | onRegister = function(self) 157 | -- force loading liblua.dll on windows so that it's available if needed; 158 | -- load something that requires liblua.dll so that it's in memory and 159 | -- can be used by modules that require it from local console. 160 | local _, path = wx.wxGetEnv("PATH") 161 | if win and path then 162 | local clibs = ide.osclibs:gsub('%?%.dll','') 163 | wx.wxSetEnv("PATH", clibs..';'..path) 164 | local ok = pcall(require, 'git.core') 165 | wx.wxSetEnv("PATH", path) 166 | if not ok then 167 | ide:Print("Couldn't find LuaDist dependency ('git.core'); 'luadist' commands may not work.") 168 | end 169 | end 170 | 171 | -- update path/cpath so that LuaDist modules are available from the console 172 | local root = MergeFullPath(ide.oshome, 'luadist/5.1') 173 | local lib = MergeFullPath(root, 'lib/lua') 174 | if not package.path:find(lib, 1, true) then 175 | package.path = package.path..(';%s/?.lua;%s/?/init.lua'):format(lib, lib) 176 | end 177 | if not package.cpath:find(lib, 1, true) then 178 | package.cpath = package.cpath..(';%s/?.%s'):format(lib, ext) 179 | end 180 | 181 | -- register all LuaDist commands 182 | local commands = {} 183 | for _, command in ipairs({ 184 | 'help', 'install', 'remove', 'refresh', 'list', 'info', 'search', 185 | 'fetch', 'make', 'upload', 'tree', 'selftest', 'echo', 186 | }) do commands[command] = function(...) return run(self, command, ...) end end 187 | 188 | ide:AddConsoleAlias("luadist", commands) 189 | end, 190 | onUnRegister = function(self) ide:RemoveConsoleAlias("luadist") end, 191 | 192 | onInterpreterLoad = function(self, interpreter) 193 | if not interpreter.luaversion then return end 194 | 195 | local version = tostring(interpreter.luaversion) 196 | local root = self:GetConfig().root or MergeFullPath(ide.oshome, ('luadist/%s'):format(version)) 197 | local lib = MergeFullPath(root, 'lib/lua') 198 | local bin = MergeFullPath(root, 'bin') 199 | 200 | -- need to set PATH on windows to allow liblua.dll from LuaDist to load 201 | -- need to reference bin/, but it may also include liblua.dll if lua 202 | -- is installed through LuaDist, so put bin/ after clibs/ to make sure 203 | -- that proxy liblua.dll is loaded instead of the real one. 204 | local _, path = wx.wxGetEnv("PATH") 205 | if win and path then 206 | local clibs = ide.osclibs:gsub('%?%.dll','') 207 | :gsub('/clibs', '/clibs' .. (version > '5.1' and version:gsub('%.','') or '')) 208 | wx.wxSetEnv("PATH", clibs..';'..bin..';'..path) 209 | end 210 | 211 | -- keep "libs" last as luadist dependencies need to be loaded from 212 | -- the IDE location first as dist/* module has been modified. 213 | local libs = (';%s/?.%s;'):format(lib, ext) 214 | local _, lcpath = wx.wxGetEnv('LUA_CPATH') 215 | if lcpath then wx.wxSetEnv('LUA_CPATH', lcpath..libs) end 216 | 217 | local libs = (';%s/?.lua;%s/?/init.lua;'):format(lib, lib) 218 | local _, lpath = wx.wxGetEnv('LUA_PATH') 219 | if lpath then wx.wxSetEnv('LUA_PATH', lpath..libs) end 220 | 221 | paths = {PATH = path, LUA_CPATH = lcpath, LUA_PATH = lpath} 222 | end, 223 | onInterpreterClose = function(self, interpreter) 224 | local version = interpreter.luaversion 225 | if not version then return end 226 | 227 | for p,v in pairs(paths) do if v then wx.wxSetEnv(p, v) end end 228 | end, 229 | } 230 | -------------------------------------------------------------------------------- /cuberite.lua: -------------------------------------------------------------------------------- 1 | -- Implements Cuberite interpreter description and interface for ZBStudio. 2 | -- Cuberite executable can have a postfix depending on the compilation mode (debug / release). 3 | 4 | local function MakeCuberiteInterpreter(a_Self, a_InterpreterPostfix, a_ExePostfix) 5 | assert(a_Self) 6 | assert(type(a_InterpreterPostfix) == "string") 7 | assert(type(a_ExePostfix) == "string") 8 | 9 | return 10 | { 11 | name = "Cuberite" .. a_InterpreterPostfix, 12 | description = "Cuberite - the custom C++ minecraft server", 13 | api = { 14 | "baselib", 15 | "mcserver_api", -- Keep the old MCServer name for compatibility reasons 16 | "cuberite_api" 17 | }, 18 | 19 | frun = function(self, wfilename, withdebug) 20 | -- Cuberite plugins are always in a "Plugins/" subfolder located at the executable level 21 | -- Get to the executable by removing the last two dirs: 22 | local ExePath = wx.wxFileName(wfilename) 23 | ExePath:RemoveLastDir() 24 | ExePath:RemoveLastDir() 25 | ExePath:ClearExt() 26 | ExePath:SetName("") 27 | local ExeName = wx.wxFileName(ExePath) 28 | 29 | -- The executable name depends on the debug / non-debug build mode, it can have a postfix 30 | ExeName:SetName("Cuberite" .. a_ExePostfix) 31 | 32 | -- Executable has a .exe ext on Windows 33 | if (ide.osname == 'Windows') then 34 | ExeName:SetExt("exe") 35 | end 36 | 37 | -- Check if we're in a subfolder inside the plugin folder, try going up one level if executable not found: 38 | if not(ExeName:FileExists()) then 39 | ide:GetOutput():Write("The Cuberite executable cannot be found in \"" .. ExeName:GetFullPath() .. "\". Trying one folder up.\n") 40 | ExeName:RemoveLastDir() 41 | ExePath:RemoveLastDir() 42 | if not(ExeName:FileExists()) then 43 | ide:GetOutput():Write("The Cuberite executable cannot be found in \"" .. ExeName:GetFullPath() .. "\". Aborting the debugger.\n") 44 | return 45 | end 46 | end 47 | 48 | -- Start the debugger server: 49 | if withdebug then 50 | DebuggerAttachDefault({ 51 | runstart = (ide.config.debugger.runonstart == true), 52 | basedir = ExePath:GetFullPath(), 53 | }) 54 | end 55 | 56 | -- Add a "nooutbuf" cmdline param to the server, causing it to call setvbuf to disable output buffering: 57 | local Cmd = ExeName:GetFullPath() .. " --no-output-buffering" 58 | 59 | -- Force ZBS not to hide Cuberite window, save and restore previous state: 60 | local SavedUnhideConsoleWindow = ide.config.unhidewindow.ConsoleWindowClass 61 | ide.config.unhidewindow.ConsoleWindowClass = 1 -- show if hidden 62 | 63 | -- Create the !EnableMobDebug.lua file so that the Cuberite plugin starts the debugging session, when loaded: 64 | local EnablerPath = wx.wxFileName(wfilename) 65 | EnablerPath:SetName("!EnableMobDebug") 66 | EnablerPath:SetExt("lua") 67 | local f = io.open(EnablerPath:GetFullPath(), "w") 68 | if (f ~= nil) then 69 | f:write( 70 | [[ 71 | -- !EnableMobDebug.lua 72 | 73 | -- This file is created by the ZeroBrane Studio debugger, do NOT commit it to your repository! 74 | -- It is safe to delete this file once the debugger is stopped. 75 | 76 | -- When this file is loaded in the ZeroBrane Studio, the debugger will pause when Cuberite detects a problem in your plugin 77 | -- If you close this file, the debugger will no longer pause on problems 78 | 79 | local g_mobdebug = require("mobdebug") 80 | g_mobdebug.start() 81 | 82 | function BreakIntoDebugger(a_Message) 83 | g_mobdebug:pause() 84 | -- If your plugin breaks here, it means that Cuberite has run into a problem in your plugin 85 | -- Inspect the stack and the server console for the error report 86 | -- If you close this file while the debugger is paused here, Cuberite will be terminated! 87 | LOG("Broken into debugger: " .. a_Message) 88 | end 89 | ]] 90 | ) 91 | f:close() 92 | end 93 | 94 | -- Open the "!EnableMobDebug.lua" file in the editor, if not already open (so that breakpoints work): 95 | local enablerEditor 96 | local fullEnablerPath = EnablerPath:GetFullPath() 97 | if not(ide:FindDocument(fullEnablerPath)) then 98 | enablerEditor = LoadFile(fullEnablerPath) 99 | end 100 | 101 | -- When the enabler gets closed, invalidate our enablerEditor variable: 102 | a_Self.onEditorClose = function(self, a_Editor) 103 | if (a_Editor == enablerEditor) then 104 | enablerEditor = nil 105 | end 106 | end 107 | 108 | -- Create the closure to call upon debugging finish: 109 | local OnFinished = function() 110 | -- Close the "!EnableMobDebug.lua" file editor: 111 | if (enablerEditor) then 112 | ide:GetDocument(enablerEditor):Close() 113 | end 114 | 115 | -- Remove the editor-close watcher: 116 | a_Self.onEditorClose = nil 117 | 118 | -- Restore the Unhide status: 119 | ide.config.unhidewindow.ConsoleWindowClass = SavedUnhideConsoleWindow 120 | 121 | -- Remove the !EnableMobDebug.lua file: 122 | os.remove(EnablerPath:GetFullPath()) 123 | end 124 | 125 | -- Run the server: 126 | local pid = CommandLineRun( 127 | Cmd, -- Command to run 128 | ExePath:GetFullPath(), -- Working directory for the debuggee 129 | false, -- Redirect debuggee output to Output pane? (NOTE: This force-hides the Cuberite window, not desirable!) 130 | true, -- Add a no-hide flag to WX 131 | nil, -- StringCallback, whatever that is 132 | nil, -- UID to identify this running program; nil to auto-assign 133 | OnFinished -- Callback to call once the debuggee terminates 134 | ) 135 | end, 136 | 137 | hasdebugger = true, 138 | } 139 | end 140 | 141 | 142 | 143 | 144 | local function analyzeProject() 145 | local projectPath = ide:GetProject() 146 | if not(projectPath) then 147 | ide:GetOutput():Write("No project path has been defined.\n") 148 | return 149 | end 150 | 151 | -- Get a list of all the files in the order in which Cuberite loads them (Info.lua is always last): 152 | local files = {} 153 | for _, filePath in ipairs(ide:GetFileList(projectPath, false, "*.lua")) do 154 | table.insert(files, filePath) 155 | end 156 | table.sort(files, 157 | function (a_File1, a_File2) 158 | if (a_File1:match("[/\\]Info.lua")) then 159 | return false 160 | elseif (a_File2:match("[/\\]Info.lua")) then 161 | return true 162 | else 163 | return a_File1 < a_File2 164 | end 165 | end 166 | ) 167 | 168 | -- List all files in the console: 169 | ide:GetOutput():Write("Files for analysis:\n") 170 | for _, file in ipairs(files) do 171 | ide:GetOutput():Write(file .. "\n") 172 | end 173 | ide:GetOutput():Write("Analyzing...\n") 174 | 175 | -- Concatenate all the files, remember their line begin positions: 176 | local lineBegin = {} -- array of {File = "filename", LineBegin = , LineEnd = } 177 | local whole = {} -- array of individual files' contents 178 | local curLineBegin = 1 179 | for _, file in ipairs(files) do 180 | local curFile = { "do" } 181 | local lastLineNum = 0 182 | for line in io.lines(file) do 183 | table.insert(curFile, line) 184 | lastLineNum = lastLineNum + 1 185 | end 186 | table.insert(curFile, "end") 187 | table.insert(lineBegin, {File = file, LineBegin = curLineBegin + 1, LineEnd = curLineBegin + lastLineNum + 1}) 188 | curLineBegin = curLineBegin + lastLineNum + 2 189 | table.insert(whole, table.concat(curFile, "\n")) 190 | end 191 | 192 | -- Analyze the concatenated files: 193 | local warn, err, line, pos = ide:AnalyzeString(table.concat(whole, "\n")) 194 | if (err) then 195 | ide:GetOutput():Write("Error: " .. err .. "\n") 196 | return 197 | end 198 | 199 | -- Function that translates concatenated-linenums back into source + linenum 200 | local function findSourceByLine(a_LineNum) 201 | for _, begin in ipairs(lineBegin) do 202 | if (a_LineNum < begin.LineEnd) then 203 | return begin.File, a_LineNum - begin.LineBegin + 1 204 | end 205 | end 206 | end 207 | 208 | -- Parse the analysis results back to original files: 209 | for _, w in ipairs(warn) do 210 | local wtext = w:gsub("^:(%d*):(.*)", 211 | function (a_LineNum, a_Message) 212 | local srcFile, srcLineNum = findSourceByLine(tonumber(a_LineNum)) 213 | ide:GetOutput():Write(srcFile .. ":" .. srcLineNum .. ": " .. a_Message .. "\n") 214 | end 215 | ) 216 | end 217 | ide:GetOutput():Write("Analysis completed.\n") 218 | end 219 | 220 | 221 | 222 | 223 | 224 | local function runInfoDump() 225 | local projectPath = ide:GetProject() 226 | if not(projectPath) then 227 | ide:GetOutput():Write("No project path has been defined.\n") 228 | return 229 | end 230 | 231 | -- Get the path to InfoDump.lua file, that is one folder up from the current project: 232 | local dumpScriptPath = wx.wxFileName(projectPath) 233 | local pluginName = dumpScriptPath:GetDirs()[#dumpScriptPath:GetDirs()] 234 | dumpScriptPath:RemoveLastDir() 235 | local dumpScript = wx.wxFileName(dumpScriptPath) 236 | dumpScript:SetName("InfoDump") 237 | dumpScript:SetExt("lua") 238 | local fullPath = dumpScript:GetFullPath() 239 | if not(wx.wxFileExists(fullPath)) then 240 | ide:GetOutput():Write("The InfoDump.lua script was not found (tried " .. fullPath .. ")\n") 241 | return 242 | end 243 | 244 | -- Execute the script: 245 | local cmd = "lua " .. fullPath .. " " .. pluginName 246 | CommandLineRun( 247 | cmd, -- Command to run 248 | dumpScriptPath:GetFullPath(), -- Working directory for the debuggee 249 | true, -- Redirect debuggee output to Output pane? 250 | true -- Add a no-hide flag to WX 251 | ) 252 | ide:GetOutput():Write("The InfoDump.lua script was executed.\n") 253 | end 254 | 255 | return { 256 | name = "Cuberite integration", 257 | description = "Implements integration with Cuberite - the custom C++ minecraft server.", 258 | author = "Mattes D (https://github.com/madmaxoft)", 259 | version = 0.55, 260 | dependencies = "1.70", 261 | 262 | AnalysisMenuID = ID("analyze.cuberite_analyzeall"), 263 | InfoDumpMenuID = ID("project.cuberite_infodump"), 264 | 265 | onRegister = function(self) 266 | -- Add the interpreters 267 | self.InterpreterDebug = MakeCuberiteInterpreter(self, " - debug mode", "_debug") 268 | self.InterpreterRelease = MakeCuberiteInterpreter(self, " - release mode", "") 269 | ide:AddInterpreter("cuberite_debug", self.InterpreterDebug) 270 | ide:AddInterpreter("cuberite_release", self.InterpreterRelease) 271 | 272 | -- Add the analysis menu item: 273 | local _, menu, pos = ide:FindMenuItem(ID.ANALYZE) 274 | if pos then 275 | menu:Insert(pos + 1, self.AnalysisMenuID, TR("Analyze as Cuberite") .. KSC(id), TR("Analyze the project source code as Cuberite")) 276 | ide:GetMainFrame():Connect(self.AnalysisMenuID, wx.wxEVT_COMMAND_MENU_SELECTED, analyzeProject) 277 | end 278 | 279 | -- Add the InfoDump menu item: 280 | _, menu, pos = ide:FindMenuItem(ID.INTERPRETER) 281 | if (pos) then 282 | self.Separator1 = menu:AppendSeparator() 283 | menu:Append(self.InfoDumpMenuID, TR("Cuberite InfoDump") .. KSC(id), TR("Run the InfoDump script on the current plugin")) 284 | ide:GetMainFrame():Connect(self.InfoDumpMenuID, wx.wxEVT_COMMAND_MENU_SELECTED, runInfoDump) 285 | end 286 | end, 287 | 288 | onUnRegister = function(self) 289 | -- Remove the interpreters: 290 | ide:RemoveInterpreter("cuberite_debug") 291 | ide:RemoveInterpreter("cuberite_release") 292 | 293 | -- Remove the menu items: 294 | ide:RemoveMenuItem(self.AnalysisMenuID) 295 | ide:RemoveMenuItem(self.InfoDumpMenuID) 296 | self.Separator1:GetParent():Delete(self.Separator1) 297 | end, 298 | } 299 | 300 | 301 | 302 | 303 | -------------------------------------------------------------------------------- /tasks.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2017-18 Paul Kulchenko, ZeroBrane LLC; All rights reserved 2 | -- 3 | -- Contributed by Paul Reilly (@paul-reilly) 4 | -- 5 | -- Based on: 6 | -- todoall.lua: Contributed by Chronos Phaenon Eosphoros (@cpeosphoros) 7 | -- (Copyright 2014 Paul Kulchenko, ZeroBrane LLC; All rights reserved) 8 | -- 9 | ---------------------------------------------------------------------------------------------------- 10 | -- 11 | -- PROJECT TASKS (tasks.lua): 12 | -- Show list of tasks from every Lua file in the project path and in all subdirectories 13 | -- that haven't been excluded (see ignore below). 14 | -- 15 | -- It also shows tasks from every open file, but will remove those from the list when the 16 | -- file is closed if it is not in the project directory. TODOs from project file are from 17 | -- from project files are always listed, even when the file has not been opened at all. 18 | -- 19 | -- Configuration: 20 | -- In your system or user config/preferences file, you can set the tokens used by this package 21 | -- and the beginning of paths relative to the project path to ignore... 22 | -- 23 | -- e.g. 24 | -- tasks = { 25 | -- singleFileMode = true, 26 | -- showOnlyFilesWithTasks = false, 27 | -- ignore = { "Export" }, 28 | -- patterns = { { name = "TODOs", pattern = "TODO[:;>]" }, 29 | -- { name = "FIXMEs", pattern = "FIXME[:;>]" }, 30 | -- { name = "My tasks", pattern = "@paulr[:;>]" } 31 | -- }, 32 | -- showNames = true, 33 | -- dontAlwaysScrollView = false, 34 | -- noButtons = true, 35 | -- noIcons = true 36 | -- } 37 | -- 38 | -- ... if you don't have this in either of your user.lua files, or if any of the 39 | -- options are omitted, then default settings are used ... 40 | -- 41 | -- singleFileMode: default - false 42 | -- Show only one file at a time, like todo.all. Toggle via right click menu. 43 | -- 44 | -- showOnlyFilesWithTasks: default - true 45 | -- With this set to false, all project files are always listed. Toggle via 46 | -- right click menu. 47 | -- 48 | -- ignore: default - ignore nothing 49 | -- "Export" will ignore all files in eg PROJECTPATH/Export Android/ 50 | -- ...but won't ignore eg PROJECTPATH/Export Manager.lua 51 | -- 52 | -- patterns: default - TODOs and FIXMEs with [:;>] pattern 53 | -- Note... spaces in the patterns create issues (inc %s). 'name' is what is 54 | -- shown on the list. Can be completely different from pattern. 55 | -- 56 | -- showNames: default - false 57 | -- Set to true, this shows tasks/pattern types in their own branches. Toggle 58 | -- via right click menu. 59 | -- 60 | -- dontAlwaysScrollView: default - true 61 | -- Set to false to not scroll activated file to top of list, instead just 62 | -- ensuring it's visible and highlighting it 63 | -- 64 | -- noButtons: default - false .. set to true to not show +/- tree buttons 65 | -- noIcons: default - false .. set true to remove icons 66 | -- 67 | -- 68 | ---------------------------------------------------------------------------------------------------- 69 | 70 | local id = ID("taskspanel.referenceview") 71 | local tasksPanel = "taskspanel" 72 | local zeroBraneLoaded = false -- set in onIdleOnce 73 | local needRefresh = false -- set in onEditorUIUpdate event 74 | local timer = {} -- tree.ctrl is updated on editor 75 | timer.lastTick = os:clock() -- events, but we can use this for 76 | timer.interval = 0.33 -- minimum time between updates 77 | local projectPath -- set in onProjectLoad 78 | local config = {} 79 | local tree = {} -- helper functions for wxTreeCtrl 80 | local patterns = {} -- our task patterns from user.lua (or default) 81 | local DEBUG = false -- set to true to get output from any _DBG calls 82 | local _DBG -- (...) -- function for console output, definition at EOF 83 | local currentEditor -- set in onEditorFocusSet, used in onProjectLoad 84 | local imglist -- icons for tree, set if required in onRegister 85 | 86 | local mapProject, fileNameFromPath -- forward decs 87 | 88 | -- parent structure different on Mac so this is cross platform method of 89 | -- getting parent panel 90 | 91 | local function getNoteBook() 92 | local nbc = "wxAuiNotebook" 93 | return tree.ctrl:GetParent():GetClassInfo():GetClassName() == nbc and 94 | tree.ctrl:GetParent():DynamicCast(nbc) or nil 95 | end 96 | 97 | -- first level from root contain file nodes for this plugin 98 | tree.addFileNode = function(fn) 99 | local root = tree.ctrl:GetRootItem() 100 | return tree.ctrl:AppendItem(root, fn, 0) 101 | end 102 | 103 | -- 104 | tree.getFileNode = function(fn) 105 | return tree.getChildByItemText(tree.ctrl:GetRootItem(), fn) 106 | end 107 | 108 | -- 109 | tree.isFileNode = function(node) 110 | return tree.ctrl:GetItemParent(node):GetValue() == tree.ctrl:GetRootItem():GetValue() 111 | end 112 | 113 | -- 114 | tree.getFileNodeOfNode = function(node) 115 | if tree.isFileNode(node) then return node end 116 | local parent = tree.ctrl:GetItemParent(node) 117 | while parent:IsOk() do 118 | if tree.isFileNode(parent) then return parent end 119 | parent = tree.ctrl:GetItemParent(parent) 120 | end 121 | return false 122 | end 123 | 124 | -- 125 | tree.getChildByItemText = function(parentItem, childName) 126 | local child, text = tree.ctrl:GetFirstChild(parentItem), nil 127 | while child:IsOk() do 128 | text = tree.ctrl:GetItemText(child) 129 | if text == childName then return child end 130 | child = tree.ctrl:GetNextSibling(child) 131 | end 132 | return nil 133 | end 134 | 135 | -- 136 | tree.getChildByDataTableItem = function(parentItem, tableItemName, value) 137 | local child, data = tree.ctrl:GetFirstChild(parentItem), nil 138 | while child:IsOk() do 139 | data = tree.getDataTable(child) 140 | if data then 141 | if data[tableItemName] then 142 | if data[tableItemName] == value then 143 | return child 144 | end 145 | end 146 | end 147 | child = tree.ctrl:GetNextSibling(child) 148 | end 149 | return nil 150 | end 151 | 152 | -- 153 | tree.getTaskByPosition = function(parentItem, pos) 154 | local child, data = tree.ctrl:GetFirstChild(parentItem), nil 155 | while child:IsOk() do 156 | data = tree.getDataTable(child) 157 | if data then 158 | if data.pos == pos then 159 | return child 160 | end 161 | end 162 | child = tree.ctrl:GetNextSibling(child) 163 | end 164 | return nil 165 | end 166 | 167 | -- each node item in a wxTreeCtrl can store a table of data 168 | tree.getDataTable = function(item) 169 | local itemData = tree.ctrl:GetItemData(item) 170 | if not itemData then return nil end 171 | local data = itemData:GetData() 172 | return data 173 | end 174 | 175 | -- 176 | tree.setDataTable = function(item, t) 177 | local itemData = tree.ctrl:GetItemData(item) 178 | if itemData == nil then itemData = wx.wxLuaTreeItemData() end 179 | itemData:SetData(t) 180 | tree.ctrl:SetItemData(item, itemData) 181 | end 182 | 183 | -- 184 | tree.hasTask = function(pattNode, taskStr, taskPos, checkIfFound) 185 | local str, pos = false, false 186 | local item = tree.getChildByItemText(pattNode, taskStr) 187 | if item == nil then 188 | item = tree.getTaskByPosition(pattNode, taskPos) 189 | if item == nil then return false end 190 | pos = true -- only pos matches so text has changed 191 | else 192 | str = true -- item text has been matched 193 | end 194 | local data = tree.getDataTable(item) 195 | if data then 196 | if checkIfFound then 197 | data.isChecked = true 198 | end 199 | if pos then tree.ctrl:SetItemText(item, taskStr) 200 | elseif str then data.pos = taskPos end -- update in case it's changed 201 | tree.setDataTable(item, data) 202 | return true 203 | end 204 | return false 205 | end 206 | 207 | -- go through children and delete any that don't have isChecked set to true 208 | -- this is because new/different and matched children were set to true 209 | -- leaving only unmatched - deleted ones - as false 210 | tree.deleteUncheckedChildren = function(parentItem, reset) 211 | local child, data = tree.ctrl:GetFirstChild(parentItem), nil 212 | while child:IsOk() do 213 | data = tree.getDataTable(child) 214 | if data then 215 | if not data.isChecked then -- delete 216 | if tree.ctrl:GetNextSibling(child):IsOk() then 217 | child = tree.ctrl:GetNextSibling(child) 218 | tree.ctrl:Delete(tree.ctrl:GetPrevSibling(child)) 219 | else 220 | tree.ctrl:Delete(child) 221 | break 222 | end 223 | else 224 | if reset then 225 | data.isChecked = false -- reset for next round of checking 226 | end 227 | tree.setDataTable(child, data) 228 | child = tree.ctrl:GetNextSibling(child) 229 | end 230 | else 231 | child = tree.ctrl:GetNextSibling(child) 232 | end 233 | end 234 | end 235 | 236 | -- 237 | tree.getOrCreateFileNode = function(createIfNotFound, fileName) 238 | local fileNode = tree.getFileNode(fileName) 239 | if fileNode == nil then 240 | if createIfNotFound then 241 | fileNode = tree.addFileNode(fileName) 242 | tree.setDataTable(fileNode, { file = fileName }) 243 | else 244 | return nil 245 | end 246 | end 247 | return fileNode 248 | end 249 | 250 | -- 251 | tree.getOrCreatePatternNode = function(createIfNotFound, fileNode, name) 252 | local pattNode = tree.getChildByItemText(fileNode, name) 253 | if pattNode == nil then 254 | if createIfNotFound then 255 | pattNode = tree.ctrl:AppendItem(fileNode, name, 3) 256 | tree.ctrl:SetItemTextColour(pattNode, 257 | wx.wxColour(table.unpack(ide.config.styles.keywords5.fg))) 258 | else 259 | return nil 260 | end 261 | end 262 | return pattNode 263 | end 264 | 265 | -- 266 | tree.itemIsOrIsDescendantOf = function(item, ancestor) 267 | if item == nil or ancestor == nil then return nil end 268 | local parent = tree.ctrl:GetItemParent(item) 269 | while parent:IsOk() do 270 | if parent:GetValue() == ancestor:GetValue() then return true end 271 | parent = tree.ctrl:GetItemParent(parent) 272 | end 273 | return false 274 | end 275 | 276 | -- 277 | tree.reset = function() 278 | tree.ctrl:DeleteAllItems() 279 | local root = tree.ctrl:AddRoot("Project Tasks", 0) 280 | tree.ctrl:ExpandAllChildren(root) 281 | end 282 | 283 | -- 284 | tree.scrollTo = function(self, node) 285 | if tree.ctrl:IsVisible(node) then return end 286 | local nb = getNoteBook() 287 | if nb then nb:Freeze() end 288 | if not config.dontAlwaysScrollView then 289 | tree.ctrl:ScrollTo(node) 290 | else 291 | tree.ctrl:EnsureVisible(node) 292 | end 293 | tree.ctrl:SetScrollPos(wx.wxHORIZONTAL, 0, true) 294 | if nb then nb:Thaw() end 295 | end 296 | 297 | -- 298 | tree.ensureFileNodeVisible = function(fileNode) 299 | -- ensure file node and last grandchild/child is visible so that 300 | -- it's all in view 301 | if not config.dontAlwaysScrollView then 302 | tree:scrollTo(fileNode) 303 | return 304 | else 305 | local lastChild = tree.ctrl:GetLastChild(fileNode) 306 | if lastChild then 307 | if config.showNames then 308 | local lastGrandChild = tree.ctrl:GetLastChild(lastChild) 309 | if lastGrandChild then 310 | tree:scrollTo(lastGrandChild) 311 | else 312 | tree:scrollTo(lastChild) 313 | end 314 | else 315 | tree:scrollTo(lastChild) 316 | end 317 | end 318 | -- do this last in case the window is small and scrolling to the last grand/child 319 | -- pushes the filename out of the top 320 | tree:scrollTo(fileNode) 321 | end 322 | end 323 | 324 | -- insert task, either at end of tree or if unsorted is false, in order 325 | -- of position from data table 326 | tree.insertTask = function(parent, itemText, unsorted, pos) 327 | if unsorted then 328 | return tree.ctrl:AppendItem(parent, itemText, 1) 329 | else 330 | local child = tree.ctrl:GetFirstChild(parent) 331 | if not child:IsOk() then 332 | return tree.ctrl:AppendItem(parent, itemText, 1) 333 | end 334 | while child:IsOk() do 335 | local t = tree.getDataTable(child) 336 | if pos < t.pos then 337 | return tree.ctrl:InsertItem(parent, tree.ctrl:GetPrevSibling(child), itemText, 1) 338 | end 339 | child = tree.ctrl:GetNextSibling(child) 340 | end 341 | return tree.ctrl:AppendItem(parent, itemText, 1) 342 | end 343 | end 344 | 345 | -- 346 | function fileNameFromPath(filePath) 347 | return filePath:gsub(projectPath, "") 348 | end 349 | 350 | -- 351 | local function path2mask(s) 352 | return s 353 | :gsub('([%(%)%.%%%+%-%?%[%^%$%]])','%%%1') -- escape all special symbols 354 | :gsub("%*", ".*") -- but expand asterisk into sequence of any symbols 355 | :gsub("[\\/]","[\\\\/]") -- allow for any path 356 | end 357 | 358 | -- 359 | local function countNewLinesBetweenPositions(text, startPos, endPos) 360 | local lineCount = 0 361 | local nextNewLine = string.find(text, "\n", startPos + 1) 362 | -- handle end of file when no more newlines 363 | if nextNewLine == nil then nextNewLine = #text end 364 | while nextNewLine < endPos do 365 | lineCount = lineCount + 1 366 | nextNewLine = string.find(text, "\n", nextNewLine + 1) 367 | if nextNewLine == nil then nextNewLine = #text end 368 | end 369 | return lineCount 370 | end 371 | 372 | -- 373 | local function mapTasks(fileName, text, isTextRawFile) 374 | local fileNode = tree.getOrCreateFileNode(true, fileName) 375 | for _, pattern in ipairs(patterns) do 376 | local pattNode = nil 377 | local pattStart, pattEnd, pos, numLines = 0, 0, 0, 0 378 | while true do 379 | pos = pattStart 380 | pattStart, pattEnd = string.find(text, pattern.pattern, pattStart+1) 381 | 382 | -- pattern not found, or it's filtered so don't want to show it 383 | if pattStart == nil or not pattern.visible then 384 | if pos == 0 then 385 | -- this pattern is not found, but maybe all nodes have been deleted, so 386 | -- check if the pattern exists, so that pattNode can be used by 387 | -- tree.deleteUncheckedChildren to delete after we break from loop 388 | if pattNode == nil then 389 | if config.showNames then 390 | -- false = just try to get it, do not create if not found 391 | pattNode = tree.getOrCreatePatternNode(false, fileNode, pattern.name) 392 | else 393 | pattNode = fileNode 394 | end 395 | end 396 | end 397 | break 398 | end 399 | 400 | -- we have found a pattern, get node for pattern or 401 | -- create it if it does not exist... 402 | if pattNode == nil then 403 | if config.showNames then 404 | pattNode = tree.getOrCreatePatternNode(true, fileNode, pattern.name) 405 | else -- ... unless it's flat view... 406 | pattNode = fileNode -- ... where tasks are direct childen of fileNode 407 | end 408 | end 409 | 410 | -- there's a difference in file lengths with lua file reading and 411 | -- wx editor length, so if it's a file read adjust for line endings 412 | -- so that we have a correct position to locate 413 | if isTextRawFile then 414 | numLines = numLines + countNewLinesBetweenPositions(text, pos, pattStart) 415 | end 416 | local adj = numLines 417 | 418 | local lineEnd = string.find(text, "\n", pattStart+1) 419 | if lineEnd == nil then lineEnd = #text else lineEnd = lineEnd - 1 end -- handle EOF 420 | -- 1 is for the extra char after the task name 421 | local taskStr = string.sub(text, pattEnd + 1, lineEnd) 422 | pos = pattStart + adj 423 | 424 | -- hasTask checks if entry exists and marks as checked so we can 425 | -- remove unmarked/checked orphans 426 | if not tree.hasTask(pattNode, taskStr, pos, true) then 427 | local task = tree.insertTask(pattNode, taskStr, config.showNames, pos) 428 | tree.setDataTable(task, { file = fileName, pos = pos, isChecked = true }) 429 | end 430 | end -- while 431 | 432 | -- here we remove any children that weren't checked by hasTask 433 | if pattNode and config.showNames then 434 | tree.deleteUncheckedChildren(pattNode, true) 435 | -- if flat view, only pattNode is fileNode so only delete later 436 | -- if completely empty 437 | if tree.ctrl:GetChildrenCount(pattNode, false) == 0 then 438 | tree.ctrl:Delete(pattNode) 439 | end 440 | end 441 | end -- for 442 | 443 | -- remove file node if no children, unless we want to keep it 444 | if fileNode then 445 | -- remove unchecked here in flat view 446 | if not config.showTasks then tree.deleteUncheckedChildren(fileNode, true) end 447 | 448 | if not tree.ctrl:ItemHasChildren(fileNode) then 449 | if config.showOnlyFilesWithTasks and not config.singleFileMode then 450 | tree.ctrl:Delete(fileNode) 451 | return 452 | end 453 | else 454 | if config.singleFileMode then tree.setHighlightedFileItem(fileNode) end 455 | end 456 | tree.ctrl:ExpandAllChildren(fileNode) 457 | end 458 | end 459 | 460 | -- 461 | local highlightedFileItem 462 | tree.setHighlightedFileItem = function(item) 463 | if not tree.isFileNode(item) then return false end 464 | if highlightedFileItem then 465 | tree.ctrl:SetItemBold(highlightedFileItem, false) 466 | if highlightedFileItem:GetValue() ~= item:GetValue() then 467 | -- active file has changed so reset highlight 468 | if not tree.itemIsOrIsDescendantOf(tree.ctrl:GetSelection(), item) then 469 | pcall( function() tree.ctrl:UnselectAll() end) 470 | end 471 | end 472 | end 473 | tree.ctrl:SetItemBold(item, true) 474 | highlightedFileItem = item 475 | end 476 | 477 | -- 478 | local function updateTree(editor) 479 | mapProject(editor, config.singleFileMode) 480 | local fileItem = tree.getFileNode(fileNameFromPath(ide:GetDocument(editor):GetFilePath())) 481 | if fileItem then 482 | currentEditor = editor 483 | tree.setHighlightedFileItem(fileItem) 484 | ide:DoWhenIdle(function() tree.ensureFileNodeVisible(fileItem) end) 485 | else 486 | if highlightedFileItem ~= nil then 487 | tree.ctrl:SetItemBold(highlightedFileItem, false) 488 | tree.ctrl:UnselectAll() 489 | end 490 | end 491 | end 492 | 493 | -- called from onProjectLoad and onIdleOnce 494 | local function scanAllOpenEditorsAndMap() 495 | if config.singleFileMode then return end 496 | -- scan all open files here, in case there are non-project path files 497 | -- that remain open from last session that have tasks we want 498 | local edNum = 0 499 | local editor = ide:GetEditor(edNum) 500 | while editor do 501 | -- skip project files or current file that's already been scanned 502 | local path = ide:GetDocument(editor):GetFilePath() 503 | if path ~= nil then 504 | local treeItem = tree.getFileNode(fileNameFromPath(path)) 505 | if treeItem == nil then 506 | mapProject(editor) 507 | else 508 | tree.ensureFileNodeVisible(treeItem) 509 | end 510 | end 511 | edNum = edNum + 1 512 | editor = ide:GetEditor(edNum) 513 | end 514 | end 515 | 516 | -- main function, called from events 517 | function mapProject(editor, newTree) 518 | -- prevent UI updates in control to stop flickering 519 | local nb = getNoteBook() 520 | if nb then nb:Freeze() end 521 | -- we have frozen the whole notebook, so protect code in between freeze/thaw calls 522 | -- in case an error in this event keeps it frozen 523 | pcall( function() 524 | if newTree then tree.reset() end 525 | 526 | if editor then 527 | mapTasks(fileNameFromPath(ide:GetDocument(editor):GetFilePath()), editor:GetText()) 528 | else 529 | -- map whole project, excluding paths begining with entries in ignore list/table 530 | -- in user.lua, tasks.ignore 531 | local masks = {} 532 | for i in ipairs(config.ignoreTable) do masks[i] = "^"..path2mask(config.ignoreTable[i]) end 533 | for _, filePath in ipairs(ide:GetFileList(projectPath, true, "*.lua")) do 534 | local fileName = fileNameFromPath(filePath) 535 | local ignore = false or editor 536 | for _, spec in ipairs(masks) do 537 | -- don't ignore if it's just the beginning of a filename 538 | ignore = ignore or (fileName:find(spec) and fileName:find("[\\/]")) 539 | end 540 | if not ignore then 541 | mapTasks(fileName, FileRead(filePath) or "", true) 542 | end 543 | end 544 | end 545 | end) 546 | -- allow UI updates 547 | if nb then nb:Thaw() end 548 | end 549 | 550 | -- our plugin/package object/table 551 | local package = { 552 | name = "Tasks panel", 553 | description = "Provides project wide tasks panel.", 554 | author = "Paul Reilly", 555 | version = 0.95, 556 | dependencies = 1.61, 557 | 558 | onRegister = function(self) 559 | patterns = self:GetConfig().patterns 560 | if not patterns or not next(patterns) then 561 | patterns = { { name = "TODOs", pattern = "TODO[:;>]" }, 562 | { name = "FIXMEs", pattern = "FIXME[:;>]" } 563 | } 564 | end 565 | 566 | -- init visibility for filtering diplay of task type 567 | for _, v in pairs(patterns) do 568 | v.visible = true 569 | end 570 | 571 | config.ignoreTable = self:GetConfig().ignore or {} 572 | config.showNames = self:GetConfig().showNames or false-- flatten tree 573 | -- option to use Ensurevisible instead of Scroll, to stop jumping to top 574 | config.dontAlwaysScrollView = self:GetConfig().dontAlwaysScrollView or false 575 | -- default is true, so don't want nil being false 576 | local sOFWT = self:GetConfig().showOnlyFilesWithTasks 577 | config.showOnlyFilesWithTasks = sOFWT == nil or sOFWT == true 578 | config.singleFileMode = self:GetConfig().singleFileMode or false 579 | 580 | local w, h = 200, 600 581 | 582 | -- configure whether to show +/- buttons 583 | local hasButtons, linesAtRoot 584 | if self:GetConfig().noButtons then 585 | hasButtons, linesAtRoot = 0, 0 586 | else 587 | hasButtons, linesAtRoot = wx.wxTR_HAS_BUTTONS, wx.wxTR_LINES_AT_ROOT 588 | end 589 | 590 | tree.ctrl = ide:CreateTreeCtrl(ide:GetProjectNotebook(), wx.wxID_ANY, 591 | wx.wxDefaultPosition, wx.wxSize(w, h), 592 | wx.wxTR_HIDE_ROOT + hasButtons + wx.wxNO_BORDER + 593 | wx.wxTR_ROW_LINES + linesAtRoot) 594 | 595 | if self:GetConfig().noIcons ~= true then 596 | imglist = ide:CreateImageList("OUTLINE", "FILE-NORMAL", "VALUE-LCALL", 597 | "VALUE-GCALL", "VALUE-ACALL", "VALUE-SCALL", "VALUE-MCALL") 598 | tree.ctrl:SetImageList(imglist) 599 | end 600 | 601 | tree.reset() 602 | 603 | local conf = function(panel) 604 | panel:Dock():MinSize(w,-1):BestSize(w,-1):FloatingSize(w,h) 605 | end 606 | 607 | ide:AddPanelFlex(ide:GetProjectNotebook(), tree.ctrl, tasksPanel, TR("Tasks"), conf) 608 | 609 | -- right click menu 610 | local ID_FILESWITHTASKS = NewID() 611 | local ID_SINGLEFILEMODE = NewID() 612 | local ID_FLATMODE = NewID() 613 | 614 | local rcMenu = ide:MakeMenu { 615 | { ID_FILESWITHTASKS, TR("Show Only Files With Tasks"), "", wx.wxITEM_CHECK }, 616 | { ID_SINGLEFILEMODE, TR("Single File Mode"), "", wx.wxITEM_CHECK }, 617 | { ID_FLATMODE, TR("View With Task Names"), "", wx.wxITEM_CHECK }, 618 | } 619 | rcMenu:Check(ID_FILESWITHTASKS, config.showOnlyFilesWithTasks) 620 | rcMenu:Check(ID_SINGLEFILEMODE, config.singleFileMode) 621 | rcMenu:Check(ID_FLATMODE, config.showNames) 622 | 623 | tree.ctrl:Connect( wx.wxEVT_RIGHT_DOWN, 624 | function(event) 625 | tree.ctrl:PopupMenu(rcMenu) 626 | end 627 | ) 628 | 629 | local function remapProject() 630 | if config.singleFileMode then 631 | mapProject(ide:GetEditor(), true) 632 | else 633 | mapProject(nil, true) 634 | scanAllOpenEditorsAndMap() 635 | end 636 | end 637 | 638 | tree.ctrl:Connect(ID_FLATMODE, wx.wxEVT_COMMAND_MENU_SELECTED, 639 | function(event) 640 | config.showNames = not config.showNames 641 | remapProject() 642 | end 643 | ) 644 | 645 | tree.ctrl:Connect(ID_FILESWITHTASKS, wx.wxEVT_COMMAND_MENU_SELECTED, 646 | function(event) 647 | config.showOnlyFilesWithTasks = not config.showOnlyFilesWithTasks 648 | remapProject() 649 | end 650 | ) 651 | 652 | tree.ctrl:Connect(ID_SINGLEFILEMODE, wx.wxEVT_COMMAND_MENU_SELECTED, 653 | function(event) 654 | config.singleFileMode = not config.singleFileMode 655 | remapProject() 656 | end 657 | ) 658 | 659 | rcMenu:AppendSeparator() 660 | local tasksSubMenu = ide:MakeMenu() 661 | rcMenu:AppendSubMenu(tasksSubMenu, TR("Filter Tasks...")) 662 | 663 | -- create menu entries for filtering 664 | for _,pattern in pairs(patterns) do 665 | local menuItemID = NewID() 666 | tasksSubMenu:Append(menuItemID, TR(pattern.name), "", wx.wxITEM_CHECK) 667 | tasksSubMenu:Check(menuItemID, pattern.visible) 668 | 669 | tree.ctrl:Connect(menuItemID, wx.wxEVT_COMMAND_MENU_SELECTED, 670 | function(event) 671 | pattern.visible = not pattern.visible 672 | if config.singleFileMode then 673 | mapProject(ide:GetEditor(), true) 674 | else 675 | mapProject(nil, true) 676 | scanAllOpenEditorsAndMap() 677 | end 678 | end 679 | ) 680 | end 681 | -- end of right click menu 682 | 683 | tree.ctrl:Connect(wx.wxEVT_LEFT_DOWN, 684 | function(event) 685 | local mask = (wx.wxTREE_HITTEST_ONITEMINDENT + wx.wxTREE_HITTEST_ONITEMLABEL 686 | + wx.wxTREE_HITTEST_ONITEMICON + wx.wxTREE_HITTEST_ONITEMRIGHT) 687 | local item_id, flags = tree.ctrl:HitTest(event:GetPosition()) 688 | 689 | if not (item_id and item_id:IsOk() and bit.band(flags, mask) > 0) then 690 | event:Skip() 691 | return 692 | end 693 | --getNoteBook():Freeze() -- this makes SelectItem cause tree to scroll/jump around 694 | tree.ctrl:SelectItem(item_id) 695 | tree.setHighlightedFileItem(tree.getFileNodeOfNode(item_id)) 696 | --getNoteBook():Thaw() 697 | local item = item_id 698 | if not item then return end 699 | local data = tree.getDataTable(item) 700 | if not data then return end 701 | local filePath = projectPath .. data.file 702 | 703 | local docs = ide:GetDocuments() 704 | local editor 705 | for _, doc in ipairs(docs) do 706 | if doc:GetFilePath() == filePath then 707 | editor = doc:GetEditor() 708 | break 709 | end 710 | end 711 | if not editor then 712 | editor = ide:LoadFile(filePath) 713 | if not editor then error("Couldn't load " .. filePath) end 714 | end 715 | 716 | -- pos not stored with file nodes, just task nodes 717 | if data.pos then editor:GotoPosEnforcePolicy(data.pos - 1) end 718 | if not ide:GetEditorWithFocus(editor) then 719 | ide:GetDocument(editor):SetActive() 720 | end 721 | end 722 | ) 723 | 724 | local menu = ide:GetMenuBar():GetMenu(ide:GetMenuBar():FindMenu(TR("&Project"))) 725 | menu:InsertCheckItem(4, id, TR("Project Tasks")..KSC(id)) 726 | 727 | menu:Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, function () 728 | local uimgr = ide:GetUIManager() 729 | uimgr:GetPane(tasksPanel):Show(not uimgr:GetPane(tasksPanel):IsShown()) 730 | uimgr:Update() 731 | end) 732 | 733 | ide:GetMainFrame():Connect(id, wx.wxEVT_UPDATE_UI, function (event) 734 | local pane = ide:GetUIManager():GetPane(tasksPanel) 735 | menu:Enable(event:GetId(), pane:IsOk()) -- disable if doesn't exist 736 | menu:Check(event:GetId(), pane:IsOk() and pane:IsShown()) 737 | end) 738 | end, 739 | 740 | -- 741 | onUnRegister = function(self) 742 | ide:RemoveMenuItem(id) 743 | end, 744 | 745 | -- called in between onEditorFocusSet calls when app first opens. Called after 746 | -- on subsequent project loads. 747 | onProjectLoad = function(self, project) 748 | local newProject = projectPath == nil or projectPath ~= project 749 | projectPath = project 750 | ide:DoWhenIdle(function() 751 | if not config.singleFileMode then 752 | mapProject(nil, newProject) 753 | scanAllOpenEditorsAndMap() 754 | else 755 | mapProject(currentEditor, newProject) 756 | end 757 | end) 758 | end, 759 | 760 | -- this fires after project is completely loaded when ZBS is first opened 761 | onIdleOnce = function(self, event) 762 | zeroBraneLoaded = true 763 | end, 764 | 765 | -- 766 | onEditorClose = function(self, editor) 767 | -- remove non project file tasks from list on close 768 | -- non project files don't have path stripped so check... 769 | local fullPath = ide:GetDocument(editor):GetFilePath() 770 | local treeItem = tree.getFileNode(fullPath) 771 | if treeItem then 772 | tree.ctrl:Delete(treeItem) 773 | mapProject() 774 | end 775 | end, 776 | 777 | -- 778 | onEditorLoad = function(self, editor) 779 | if zeroBraneLoaded then 780 | mapProject(editor) 781 | end 782 | end, 783 | 784 | -- implemented for saving new file or save as, so list updates 785 | onEditorSave = function(self, editor) 786 | if tree.getFileNode(fileNameFromPath(ide:GetDocument(editor):GetFilePath())) == nil then 787 | mapProject(editor) 788 | end 789 | end, 790 | 791 | -- 792 | onEditorFocusSet = function(self, editor) 793 | if DEBUG then require('mobdebug').on() end -- start debugger for coroutine 794 | -- event called when loading file, but filename is nil then 795 | if ide:GetDocument(editor):GetFilePath() then 796 | updateTree(editor) 797 | end 798 | end, 799 | 800 | -- 801 | onEditorUpdateUI = function(self, editor, event) 802 | -- only flag update when content changes; ignore scrolling events 803 | if bit.band(event:GetUpdated(), wxstc.wxSTC_UPDATE_CONTENT) > 0 then 804 | needRefresh = editor 805 | end 806 | end, 807 | 808 | -- 809 | onIdle = function(self, event) 810 | -- limit update time to minimum of timer.interval in case of rapid events 811 | if os:clock() > timer.lastTick + timer.interval then 812 | timer.lastTick = os:clock() 813 | else 814 | return 815 | end 816 | local editor = needRefresh 817 | if not editor then return end 818 | needRefresh = nil 819 | 820 | if ide:GetDocument(editor):GetFilePath() then 821 | mapProject(editor) 822 | end 823 | end 824 | } 825 | 826 | function _DBG(...) 827 | if DEBUG then 828 | local msg = "" for _,v in ipairs{...} do msg = msg .. tostring(v) .. "\t" end ide:Print(msg) 829 | end 830 | end 831 | 832 | return package 833 | --------------------------------------------------------------------------------