Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b2241085d | ||
|
|
c9cfd42d34 | ||
|
|
c25c56069d | ||
|
|
a68e8f7bd3 | ||
|
|
fc6de036e0 | ||
|
|
08d42a2501 | ||
|
|
da32878984 | ||
|
|
3fd5d88656 | ||
|
|
7d9ad5100c | ||
|
|
6bff634446 | ||
|
|
b0de487bf0 | ||
|
|
560d56835a | ||
|
|
3c6a06f537 | ||
|
|
b1f3bf0bd9 | ||
|
|
6ea3a4708d | ||
|
|
4de0eb1dc0 | ||
|
|
c2f6ed6338 | ||
|
|
fad7ff0cc3 | ||
|
|
9afa35f9b2 | ||
|
|
3a5b46f65e | ||
|
|
26d0908c89 | ||
|
|
770db579d9 | ||
|
|
8e09ed1658 | ||
|
|
25ac96d39a | ||
|
|
22fda661ec | ||
|
|
529a0e8c9e | ||
|
|
7cc061e18d | ||
|
|
5f2226bac2 | ||
|
|
37795c2480 | ||
|
|
5e934dfd69 | ||
|
|
f970ba38a0 | ||
|
|
8af6c1b5b6 | ||
|
|
0777a5fbd8 | ||
|
|
942681e246 | ||
|
|
ce8f120e48 | ||
|
|
048fd2349a | ||
|
|
2627957e5a | ||
|
|
16b0ca0fc7 | ||
|
|
e3189ba38a | ||
|
|
ad48622f68 | ||
|
|
0a9439ad22 | ||
|
|
a7577d5054 | ||
|
|
668cdcb4db | ||
|
|
4ec3e87473 | ||
|
|
90b4f38223 | ||
|
|
c1918e5c38 | ||
|
|
34baaa9447 | ||
|
|
817938593a | ||
|
|
b944f9d01d | ||
|
|
5d4b2395b5 | ||
|
|
f9ab1546ff | ||
|
|
971e7c8316 | ||
|
|
60a89b8f32 | ||
|
|
7275f0d11a | ||
|
|
35213d7d3b | ||
|
|
cde26253e3 | ||
|
|
b45c0600c7 | ||
|
|
5784dea99a | ||
|
|
e880fdde60 | ||
|
|
b7d8512b7b | ||
|
|
a7a8f32c91 | ||
|
|
9fc4ab5e9b | ||
|
|
37434534cc | ||
|
|
fc03c2843b | ||
|
|
2abbad04ac | ||
|
|
dbb392e009 | ||
|
|
65947ff924 | ||
|
|
d8324b269d | ||
|
|
ac9c2e9a84 | ||
|
|
3b2fcfd1f9 | ||
|
|
14fe7af771 | ||
|
|
7b0405bc79 | ||
|
|
fc6b0c176d | ||
|
|
5c50a2645b | ||
|
|
11cfbfa68d | ||
|
|
e6b318c643 | ||
|
|
1351db6114 | ||
|
|
b58e931409 | ||
|
|
e0eb05b122 | ||
|
|
8ecd8dfa6b |
4301
api/lua/love2d.lua
Normal file
4301
api/lua/love2d.lua
Normal file
File diff suppressed because it is too large
Load Diff
27
cfg/user-sample.lua
Normal file
27
cfg/user-sample.lua
Normal file
@@ -0,0 +1,27 @@
|
||||
--[[-- Rename this file to `user.lua` to get loaded
|
||||
|
||||
Configuration files are loaded in the following order
|
||||
|
||||
1. <application>\config.lua
|
||||
2. cfg\user.lua
|
||||
3. -cfg commandline strings
|
||||
|
||||
-- an example of how loaded configuration can be modified from this file
|
||||
|
||||
local G = ... -- this now points to the global environment in the script
|
||||
local luaspec = G.ide.specs['lua']
|
||||
luaspec.exts[2] = "luaz"
|
||||
luaspec.keywords[1] = luaspec.keywords[1] .. ' foo'
|
||||
|
||||
-- these changes are going to be mapped to ide.config.editor...
|
||||
-- change encoding to Cyrillic
|
||||
editor.fontencoding = G.wx.wxFONTENCODING_ISO8859_5
|
||||
-- or WinCyrillic
|
||||
editor.fontencoding = G.wx.wxFONTENCODING_CP1251
|
||||
outputshell.fontencoding = G.wx.wxFONTENCODING_CP1251
|
||||
|
||||
-- specify full path to love2d executable; this is only needed
|
||||
-- if the game folder and the executable are NOT in the same folder.
|
||||
path.love2d = 'd:/lua/love/love' -- set the path of love executable
|
||||
|
||||
--]]--
|
||||
@@ -1,9 +0,0 @@
|
||||
--[[--
|
||||
|
||||
estrela loads configs in the following order
|
||||
|
||||
1. <application>\config.lua
|
||||
2. cfg\user.lua
|
||||
3. -cfg commandline strings
|
||||
|
||||
--]]--
|
||||
28
interpreters/love2d.lua
Normal file
28
interpreters/love2d.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
return {
|
||||
name = "Love2d",
|
||||
description = "Love2d game engine",
|
||||
api = {"baselib", "love2d"},
|
||||
frun = function(self,wfilename,rundebug)
|
||||
if rundebug then DebuggerAttachDefault() end
|
||||
local love2d = ide.config.path.love2d
|
||||
or wx.wxFileName(self:fprojdir(wfilename)):GetPath(wx.wxPATH_GET_VOLUME)
|
||||
.. '/love'
|
||||
local cmd = ('"%s" "%s"%s'):format(string.gsub(love2d, "\\","/"),
|
||||
self:fprojdir(wfilename), rundebug and ' -debug' or '')
|
||||
-- CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback)
|
||||
return CommandLineRun(cmd,self:fworkdir(wfilename),true,false,nil,nil,
|
||||
function() ide.debugger.pid = nil end)
|
||||
end,
|
||||
fprojdir = function(self,wfilename)
|
||||
return wfilename:GetPath(wx.wxPATH_GET_VOLUME)
|
||||
end,
|
||||
fworkdir = function (self,wfilename)
|
||||
return ide.config.path.projectdir
|
||||
or wfilename:GetPath(wx.wxPATH_GET_VOLUME)
|
||||
end,
|
||||
hasdebugger = true,
|
||||
fattachdebug = function(self)
|
||||
DebuggerAttachDefault()
|
||||
end,
|
||||
scratchextloop = true,
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
return {
|
||||
name = "Lua",
|
||||
description = "Commandline Lua interpreter",
|
||||
api = {"wxwidgets","baselib"},
|
||||
frun = function(self,wfilename)
|
||||
local mainpath = ide.editorFilename:gsub("[^/\\]+$","")
|
||||
local filepath = wfilename:GetFullPath()
|
||||
local code = ([[
|
||||
xpcall(function() dofile '%s' end,
|
||||
function(err) print(debug.traceback(err)) end)
|
||||
]]):format(filepath:gsub("\\","/"))
|
||||
local cmd = '"'..mainpath..'/bin/lua.exe" -e "'..code..'"'
|
||||
CommandLineRun(cmd,self:fworkdir(wfilename),true,false)
|
||||
end,
|
||||
fprojdir = function(self,wfilename)
|
||||
return wfilename:GetPath(wx.wxPATH_GET_VOLUME)
|
||||
end,
|
||||
fworkdir = function (self,wfilename)
|
||||
return ide.config.path.projectdir and ide.config.path.projectdir:len()>0 and
|
||||
ide.config.path.projectdir
|
||||
end,
|
||||
}
|
||||
@@ -1,23 +1,33 @@
|
||||
local mainpath = string.gsub(ide.editorFilename:gsub("[^/\\]+$",""),"\\","/")
|
||||
local os = ide.osname
|
||||
|
||||
local clibs =
|
||||
os == "Windows" and mainpath.."bin/?.dll;"..mainpath.."bin/clibs/?.dll" or
|
||||
os == "Macintosh" and mainpath.."bin/lib?.dylib;"..mainpath.."bin/clibs/?.dylib" or
|
||||
os == "Unix" and mainpath.."bin/?.so;"..mainpath.."bin/clibs/?.so" or nil
|
||||
wx.wxSetEnv("LUA_PATH", package.path .. ';'
|
||||
.. mainpath.."lualibs/?/?.lua;"..mainpath.."lualibs/?.lua")
|
||||
if clibs then wx.wxSetEnv("LUA_CPATH", package.cpath .. ';' .. clibs) end
|
||||
|
||||
local macExe = mainpath..'bin/lua.app/Contents/MacOS/lua'
|
||||
local exe = (os == "Macintosh" and wx.wxFileExists(macExe)
|
||||
and macExe or mainpath..'bin/lua')
|
||||
|
||||
return {
|
||||
name = "Lua Debug",
|
||||
description = "Commandline Lua interpreter",
|
||||
name = "Lua with Debugger",
|
||||
description = "Lua interpreter with debugger",
|
||||
api = {"wxwidgets","baselib"},
|
||||
frun = function(self,wfilename,rundebug)
|
||||
local mainpath = string.gsub(ide.editorFilename:gsub("[^/\\]+$",""),"\\","/")
|
||||
local filepath = string.gsub(wfilename:GetFullPath(), "\\","/")
|
||||
local script
|
||||
if rundebug then
|
||||
DebuggerAttachDefault()
|
||||
script = (
|
||||
"package.path=package.path..';"..mainpath.."lualibs/?/?.lua;"..mainpath.."lualibs/?.lua';"..
|
||||
"package.cpath=package.cpath..';"..mainpath.."bin/clibs/?.dll';"..
|
||||
rundebug
|
||||
)
|
||||
script = rundebug
|
||||
else
|
||||
script = ([[dofile '%s']]):format(filepath)
|
||||
end
|
||||
local code = ([[xpcall(function() io.stdout:setvbuf('no'); %s end,function(err) print(debug.traceback(err)) end)]]):format(script)
|
||||
local cmd = '"'..mainpath..'bin/lua.exe" -e "'..code..'"'
|
||||
local cmd = '"'..exe..'" -e "'..code..'"'
|
||||
-- CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback)
|
||||
return CommandLineRun(cmd,self:fworkdir(wfilename),true,false,nil,nil,
|
||||
function() ide.debugger.pid = nil end)
|
||||
|
||||
@@ -52,7 +52,7 @@ return {
|
||||
local editorDir = string.gsub(ide.editorFilename:gsub("[^/\\]+$",""),"\\","/")
|
||||
script = ""..
|
||||
"package.path=package.path..';"..editorDir.."lualibs/?/?.lua';"..
|
||||
"io.stdout:setvbuf('no'); require('mobdebug').start('" .. wx.wxGetHostName().."',"..ide.debugger.portnumber..")"
|
||||
"io.stdout:setvbuf('no'); require('mobdebug').start('" .. ide.debugger.hostname.."',"..ide.debugger.portnumber..")"
|
||||
|
||||
args = args..' -es "'..script..'"'..startargs
|
||||
else
|
||||
|
||||
@@ -28,8 +28,9 @@
|
||||
|
||||
module ("lexer", package.seeall)
|
||||
|
||||
require 'metalua.runtime'
|
||||
|
||||
-- don't load metalua.runtime as it loads metalua.base, which pollutes
|
||||
-- global namespace and overwrites pairs/ipairs -- PK 6/4/2012
|
||||
require 'metalua.table2'
|
||||
|
||||
lexer = { alpha={ }, sym={ } }
|
||||
lexer.__index=lexer
|
||||
|
||||
@@ -27,6 +27,6 @@ local keywords = {
|
||||
"...", "..", "==", ">=", "<=", "~=",
|
||||
"+{", "-{" }
|
||||
|
||||
for w in values(keywords) do mlp_lexer:add(w) end
|
||||
for _,w in pairs(keywords) do mlp_lexer:add(w) end -- PK 6/4/2012
|
||||
|
||||
_M.lexer = mlp_lexer
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
--
|
||||
-- MobDebug 0.449
|
||||
-- Copyright Paul Kulchenko 2011-2012
|
||||
-- MobDebug 0.473
|
||||
-- Copyright 2011-12 Paul Kulchenko
|
||||
-- Based on RemDebug 1.0 Copyright Kepler Project 2005
|
||||
-- (http://www.keplerproject.org/remdebug)
|
||||
--
|
||||
|
||||
local mobdebug = {
|
||||
_NAME = "mobdebug",
|
||||
_VERSION = 0.473,
|
||||
_COPYRIGHT = "Paul Kulchenko",
|
||||
_DESCRIPTION = "Mobile Remote Debugger for the Lua programming language",
|
||||
_VERSION = "0.449"
|
||||
port = 8171
|
||||
}
|
||||
|
||||
local coroutine = coroutine
|
||||
@@ -24,6 +24,7 @@ local setmetatable = setmetatable
|
||||
local string = string
|
||||
local tonumber = tonumber
|
||||
local mosync = mosync
|
||||
local jit = jit
|
||||
|
||||
-- this is a socket class that implements maConnect interface
|
||||
local function socketMobileLua()
|
||||
@@ -45,13 +46,14 @@ local function socketMobileLua()
|
||||
mosync.maWait(0)
|
||||
mosync.maGetEvent(event)
|
||||
local eventType = mosync.SysEventGetType(event)
|
||||
if (mosync.EVENT_TYPE_CLOSE == eventType) then mosync.maExit(0) end
|
||||
if (mosync.EVENT_TYPE_CONN == eventType and
|
||||
mosync.SysEventGetConnHandle(event) == connection and
|
||||
mosync.SysEventGetConnOpType(event) == mosync.CONNOP_CONNECT) then
|
||||
-- result > 0 ? success : error
|
||||
if not (mosync.SysEventGetConnResult(event) > 0) then connection = nil end
|
||||
break
|
||||
elseif mosync.EventMonitor and mosync.EventMonitor.HandleEvent then
|
||||
mosync.EventMonitor:HandleEvent(event)
|
||||
end
|
||||
end
|
||||
mosync.SysFree(event)
|
||||
@@ -84,16 +86,16 @@ local function socketMobileLua()
|
||||
while true do
|
||||
local numberOfBytes = stringToBuffer(msg, outBuffer)
|
||||
mosync.maConnWrite(connection, outBuffer, numberOfBytes)
|
||||
local result = 0
|
||||
while true do
|
||||
mosync.maWait(0)
|
||||
mosync.maGetEvent(event)
|
||||
local eventType = mosync.SysEventGetType(event)
|
||||
if (mosync.EVENT_TYPE_CLOSE == eventType) then mosync.maExit(0) end
|
||||
if (mosync.EVENT_TYPE_CONN == eventType and
|
||||
mosync.SysEventGetConnHandle(event) == connection and
|
||||
mosync.SysEventGetConnOpType(event) == mosync.CONNOP_WRITE) then
|
||||
break
|
||||
elseif mosync.EventMonitor and mosync.EventMonitor.HandleEvent then
|
||||
mosync.EventMonitor:HandleEvent(event)
|
||||
end
|
||||
end
|
||||
self, msg = coroutine.yield()
|
||||
@@ -110,7 +112,6 @@ local function socketMobileLua()
|
||||
if len ~= 0 then mosync.maWait(0) end
|
||||
mosync.maGetEvent(event)
|
||||
local eventType = mosync.SysEventGetType(event)
|
||||
if (mosync.EVENT_TYPE_CLOSE == eventType) then mosync.maExit(0) end
|
||||
if (mosync.EVENT_TYPE_CONN == eventType and
|
||||
mosync.SysEventGetConnHandle(event) == connection and
|
||||
mosync.SysEventGetConnOpType(event) == mosync.CONNOP_READ) then
|
||||
@@ -120,6 +121,8 @@ local function socketMobileLua()
|
||||
break -- got the event we wanted; now check if we have all we need
|
||||
elseif len == 0 then
|
||||
self, len = coroutine.yield(nil)
|
||||
elseif mosync.EventMonitor and mosync.EventMonitor.HandleEvent then
|
||||
mosync.EventMonitor:HandleEvent(event)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -151,18 +154,26 @@ local function socketMobileLua()
|
||||
return self
|
||||
end
|
||||
|
||||
-- overwrite RunEventLoop in MobileLua as it conflicts with the event
|
||||
-- loop that needs to run to process debugger events (socket read/write).
|
||||
-- event loop functionality is implemented by calling HandleEvent
|
||||
-- while waiting for debugger events.
|
||||
if mosync and mosync.EventMonitor then
|
||||
mosync.EventMonitor.RunEventLoop = function(self) end
|
||||
end
|
||||
|
||||
local socket = mosync and socketMobileLua() or (require "socket")
|
||||
|
||||
local debug = require "debug"
|
||||
local coro_debugger
|
||||
local events = { BREAK = 1, WATCH = 2, RESTART = 3 }
|
||||
local events = { BREAK = 1, WATCH = 2, RESTART = 3, STACK = 4 }
|
||||
local breakpoints = {}
|
||||
local watches = {}
|
||||
local lastsource
|
||||
local lastfile
|
||||
local watchescnt = 0
|
||||
local abort -- default value is nil; this is used in start/loop distinction
|
||||
local check_break = false
|
||||
local seen_hook = false
|
||||
local skip
|
||||
local skipcount = 0
|
||||
local step_into = false
|
||||
@@ -177,6 +188,137 @@ local debugee = function ()
|
||||
error(deferror)
|
||||
end
|
||||
|
||||
local serpent = (function() ---- include Serpent module for serialization
|
||||
local n, v = "serpent", 0.15 -- (C) 2012 Paul Kulchenko; MIT License
|
||||
local c, d = "Paul Kulchenko", "Serializer and pretty printer of Lua data types"
|
||||
local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'}
|
||||
local badtype = {thread = true, userdata = true}
|
||||
local keyword, globals, G = {}, {}, (_G or _ENV)
|
||||
for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false',
|
||||
'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
|
||||
'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end
|
||||
for k,v in pairs(G) do globals[v] = k end -- build func to name mapping
|
||||
for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do
|
||||
for k,v in pairs(G[g]) do globals[v] = g..'.'..k end end
|
||||
|
||||
local function s(t, opts)
|
||||
local name, indent, fatal = opts.name, opts.indent, opts.fatal
|
||||
local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge
|
||||
local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge)
|
||||
local comm = opts.comment and (tonumber(opts.comment) or math.huge)
|
||||
local seen, sref, syms, symn = {}, {}, {}, 0
|
||||
local function gensym(val) return tostring(val):gsub("[^%w]",""):gsub("(%d%w+)",
|
||||
function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return syms[s] end) end
|
||||
local function safestr(s) return type(s) == "number" and (huge and snum[tostring(s)] or s)
|
||||
or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026
|
||||
or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end
|
||||
local function comment(s,l) return comm and (l or 0) < comm and ' --[['..tostring(s)..']]' or '' end
|
||||
local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal
|
||||
and safestr(tostring(s))..comment('err',l) or error("Can't serialize "..tostring(s)) end
|
||||
local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r']
|
||||
local n = name == nil and '' or name
|
||||
local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n]
|
||||
local safe = plain and n or '['..safestr(n)..']'
|
||||
return (path or '')..(plain and path and '.' or '')..safe, safe end
|
||||
local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(o, n)
|
||||
local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'}
|
||||
local function padnum(d) return ("%0"..maxn.."d"):format(d) end
|
||||
table.sort(o, function(a,b)
|
||||
return (o[a] and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum))
|
||||
< (o[b] and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end
|
||||
local function val2str(t, name, indent, path, plainindex, level)
|
||||
local ttype, level = type(t), (level or 0)
|
||||
local spath, sname = safename(path, name)
|
||||
local tag = plainindex and
|
||||
((type(name) == "number") and '' or name..space..'='..space) or
|
||||
(name ~= nil and sname..space..'='..space or '')
|
||||
if seen[t] then
|
||||
table.insert(sref, spath..space..'='..space..seen[t])
|
||||
return tag..'nil'..comment('ref', level)
|
||||
elseif badtype[ttype] then return tag..globerr(t, level)
|
||||
elseif ttype == 'function' then
|
||||
seen[t] = spath
|
||||
local ok, res = pcall(string.dump, t)
|
||||
local func = ok and ((opts.nocode and "function() end" or
|
||||
"loadstring("..safestr(res)..",'@serialized')")..comment(t, level))
|
||||
return tag..(func or globerr(t, level))
|
||||
elseif ttype == "table" then
|
||||
if level >= maxl then return tag..'{}'..comment('max', level) end
|
||||
seen[t] = spath
|
||||
if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty
|
||||
local maxn, o, out = #t, {}, {}
|
||||
for key = 1, maxn do table.insert(o, key) end
|
||||
for key in pairs(t) do if not o[key] then table.insert(o, key) end end
|
||||
if opts.sortkeys then alphanumsort(o, opts.sortkeys) end
|
||||
for n, key in ipairs(o) do
|
||||
local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse
|
||||
if opts.ignore and opts.ignore[value] -- skip ignored values; do nothing
|
||||
or sparse and value == nil then -- skipping nils; do nothing
|
||||
elseif ktype == 'table' or ktype == 'function' then
|
||||
if not seen[key] and not globals[key] then
|
||||
table.insert(sref, 'local '..val2str(key,gensym(key),indent)) end
|
||||
table.insert(sref, seen[t]..'['..(seen[key] or globals[key] or gensym(key))
|
||||
..']'..space..'='..space..(seen[value] or val2str(value,nil,indent)))
|
||||
else
|
||||
if badtype[ktype] then plainindex, key = true, '['..globerr(key, level+1)..']' end
|
||||
table.insert(out,val2str(value,key,indent,spath,plainindex,level+1))
|
||||
end
|
||||
end
|
||||
local prefix = string.rep(indent or '', level)
|
||||
local head = indent and '{\n'..prefix..indent or '{'
|
||||
local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space))
|
||||
local tail = indent and "\n"..prefix..'}' or '}'
|
||||
return (custom and custom(tag,head,body,tail) or tag..head..body..tail)..comment(t, level)
|
||||
else return tag..safestr(t) end -- handle all other types
|
||||
end
|
||||
local sepr = indent and "\n" or ";"..space
|
||||
local body = val2str(t, name, indent) -- this call also populates sref
|
||||
local tail = #sref>0 and table.concat(sref, sepr)..sepr or ''
|
||||
return not name and body or "do local "..body..sepr..tail.."return "..name..sepr.."end"
|
||||
end
|
||||
|
||||
local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end
|
||||
return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s,
|
||||
dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end,
|
||||
line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end,
|
||||
block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end }
|
||||
end)() ---- end of Serpent module
|
||||
|
||||
local function stack(start)
|
||||
local function vars(f)
|
||||
local func = debug.getinfo(f, "f").func
|
||||
local i = 1
|
||||
local locals = {}
|
||||
while true do
|
||||
local name, value = debug.getlocal(f, i)
|
||||
if not name then break end
|
||||
if string.sub(name, 1, 1) ~= '(' then locals[name] = {value, tostring(value)} end
|
||||
i = i + 1
|
||||
end
|
||||
i = 1
|
||||
local ups = {}
|
||||
while func and true do -- check for func as it may be nil for tail calls
|
||||
local name, value = debug.getupvalue(func, i)
|
||||
if not name then break end
|
||||
ups[name] = {value, tostring(value)}
|
||||
i = i + 1
|
||||
end
|
||||
return locals, ups
|
||||
end
|
||||
|
||||
local stack = {}
|
||||
for i = (start or 0), 100 do
|
||||
local source = debug.getinfo(i, "Snl")
|
||||
if not source then break end
|
||||
table.insert(stack, {
|
||||
{source.name, source.source, source.linedefined,
|
||||
source.currentline, source.what, source.namewhat, source.short_src},
|
||||
vars(i+1)})
|
||||
if source.what == 'main' then break end
|
||||
end
|
||||
return stack
|
||||
end
|
||||
|
||||
local function set_breakpoint(file, line)
|
||||
if file == '-' and lastfile then file = lastfile end
|
||||
if not breakpoints[file] then
|
||||
@@ -262,7 +404,32 @@ local function is_safe(stack_level, conservative)
|
||||
return true
|
||||
end
|
||||
|
||||
local function in_debugger()
|
||||
local this = debug.getinfo(1, "S").source
|
||||
-- only need to check few frames as mobdebug frames should be close
|
||||
for i = 3, 7 do
|
||||
local info = debug.getinfo(i, "S")
|
||||
if not info then return false end
|
||||
if info.source == this then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function debug_hook(event, line)
|
||||
if abort and is_safe(stack_level) then error(abort) end
|
||||
|
||||
-- LuaJIT needs special treatment. Because debug_hook is set for
|
||||
-- *all* coroutines, and not just the one being debugged as in regular Lua
|
||||
-- (http://lua-users.org/lists/lua-l/2011-06/msg00513.html),
|
||||
-- need to avoid debugging mobdebug's own code as LuaJIT doesn't
|
||||
-- always correctly generate call/return hook events (there are more
|
||||
-- calls than returns, which breaks stack depth calculation and
|
||||
-- 'step' and 'step over' commands stop working; possibly because
|
||||
-- 'tail return' events are not generated by LuaJIT).
|
||||
-- the next line checks if the debugger is run under LuaJIT and if
|
||||
-- one of debugger methods is present in the stack, it simply returns.
|
||||
if jit and in_debugger() then return end
|
||||
|
||||
if event == "call" then
|
||||
stack_level = stack_level + 1
|
||||
elseif event == "return" or event == "tail return" then
|
||||
@@ -313,30 +480,52 @@ local function debug_hook(event, line)
|
||||
end
|
||||
end
|
||||
|
||||
local suspend = (check_break
|
||||
-- stack check is at least two times faster than select
|
||||
-- 1.2s vs 2.5s for 100,000 iterations on 1.6Ghz CPU
|
||||
and is_safe(stack_level)
|
||||
and (socket.select({server}, {}, 0))[server]
|
||||
)
|
||||
if suspend
|
||||
or step_into
|
||||
if step_into
|
||||
or (step_over and stack_level <= step_level)
|
||||
or has_breakpoint(file, line) then
|
||||
or has_breakpoint(file, line)
|
||||
or (socket.select({server}, {}, 0))[server] then
|
||||
vars = vars or capture_vars()
|
||||
check_break = true -- this is only needed to avoid breaking too early when debugging is starting
|
||||
seen_hook = true
|
||||
step_into = false
|
||||
step_over = false
|
||||
local status, res = coroutine.resume(coro_debugger, events.BREAK, vars, file, line)
|
||||
if status and res then
|
||||
|
||||
-- handle 'stack' command that provides stack() information to the debugger
|
||||
if status and res == 'stack' then
|
||||
while status and res == 'stack' do
|
||||
-- resume with the stack trace and variables
|
||||
if vars then restore_vars(vars) end -- restore vars so they are reflected in stack values
|
||||
status, res = coroutine.resume(coro_debugger, events.STACK, stack(3), file, line)
|
||||
end
|
||||
end
|
||||
|
||||
-- need to recheck once more as resume after 'stack' command may
|
||||
-- return something else (for example, 'exit'), which needs to be handled
|
||||
if status and res and res ~= 'stack' then
|
||||
if abort == nil and res == "exit" then os.exit(1) end
|
||||
abort = res
|
||||
error(abort)
|
||||
end -- abort execution if requested
|
||||
-- only abort if safe; if not, there is another (earlier) check inside
|
||||
-- debug_hook, which will abort execution at the first safe opportunity
|
||||
if is_safe(stack_level) then error(abort) end
|
||||
end
|
||||
end
|
||||
if vars then restore_vars(vars) end
|
||||
end
|
||||
end
|
||||
|
||||
local function stringify_results(status, ...)
|
||||
if not status then return status, ... end -- on error report as it
|
||||
|
||||
local t = {...}
|
||||
for i,v in pairs(t) do -- stringify each of the returned values
|
||||
t[i] = serpent.line(v, {nocode = true, comment = 1})
|
||||
end
|
||||
-- stringify table with all returned values
|
||||
-- this is done to allow each returned value to be used (serialized or not)
|
||||
-- intependently and to preserve "original" comments
|
||||
return status, serpent.dump(t, {sparse = false})
|
||||
end
|
||||
|
||||
local function debugger_loop(sfile, sline)
|
||||
local command
|
||||
local app
|
||||
@@ -358,7 +547,7 @@ local function debugger_loop(sfile, sline)
|
||||
if win then
|
||||
-- process messages in a regular way
|
||||
-- and exit as soon as the event loop is idle
|
||||
win:Connect(wx.wxEVT_IDLE, function(event)
|
||||
win:Connect(wx.wxEVT_IDLE, function()
|
||||
win:Disconnect(wx.wxID_ANY, wx.wxID_ANY, wx.wxEVT_IDLE)
|
||||
app:ExitMainLoop()
|
||||
end)
|
||||
@@ -380,7 +569,7 @@ local function debugger_loop(sfile, sline)
|
||||
server:send("400 Bad Request\n")
|
||||
end
|
||||
elseif command == "DELB" then
|
||||
_, _, _, file, line = string.find(line, "^([A-Z]+)%s+([%w%p%s]+)%s+(%d+)%s*$")
|
||||
local _, _, _, file, line = string.find(line, "^([A-Z]+)%s+([%w%p%s]+)%s+(%d+)%s*$")
|
||||
if file and line then
|
||||
remove_breakpoint(file, tonumber(line))
|
||||
server:send("200 OK\n")
|
||||
@@ -394,9 +583,8 @@ local function debugger_loop(sfile, sline)
|
||||
local status
|
||||
if func then
|
||||
setfenv(func, eval_env)
|
||||
status, res = pcall(func)
|
||||
status, res = stringify_results(pcall(func))
|
||||
end
|
||||
res = tostring(res)
|
||||
if status then
|
||||
server:send("200 OK " .. string.len(res) .. "\n")
|
||||
server:send(res)
|
||||
@@ -524,6 +712,23 @@ local function debugger_loop(sfile, sline)
|
||||
end
|
||||
elseif command == "SUSPEND" then
|
||||
-- do nothing; it already fulfilled its role
|
||||
elseif command == "STACK" then
|
||||
-- first check if we can execute the stack command
|
||||
-- as it requires yielding back to debug_hook it cannot be executed
|
||||
-- if we have not seen the hook yet as happens after start().
|
||||
-- in this case we simply return an empty result
|
||||
if not seen_hook then
|
||||
server:send("200 OK " .. serpent.dump({}) .. "\n")
|
||||
else
|
||||
-- yield back to debug hook to get stack information
|
||||
local ev, vars = coroutine.yield("stack")
|
||||
if ev == events.STACK then
|
||||
server:send("200 OK " ..
|
||||
serpent.dump(vars, {nocode = true, sparse = false}) .. "\n")
|
||||
else
|
||||
server:send("401 Error in Expression 0\n")
|
||||
end
|
||||
end
|
||||
elseif command == "EXIT" then
|
||||
server:send("200 OK\n")
|
||||
coroutine.yield("exit")
|
||||
@@ -537,8 +742,18 @@ local function connect(controller_host, controller_port)
|
||||
return socket.connect(controller_host, controller_port)
|
||||
end
|
||||
|
||||
local function isrunning()
|
||||
return coro_debugger and coroutine.status(coro_debugger) == 'suspended'
|
||||
end
|
||||
|
||||
-- Starts a debug session by connecting to a controller
|
||||
local function start(controller_host, controller_port)
|
||||
-- only one debugging session can be run (as there is only one debug hook)
|
||||
if isrunning() then return end
|
||||
|
||||
controller_host = controller_host or "localhost"
|
||||
controller_port = controller_port or mobdebug.port
|
||||
|
||||
server = socket.connect(controller_host, controller_port)
|
||||
if server then
|
||||
local info = debug.getinfo(2, "Sl")
|
||||
@@ -546,6 +761,12 @@ local function start(controller_host, controller_port)
|
||||
if string.find(file, "@") == 1 then file = string.sub(file, 2) end
|
||||
if string.find(file, "%.[/\\]") == 1 then file = string.sub(file, 3) end
|
||||
|
||||
-- correct stack depth which already has some calls on it
|
||||
-- so it doesn't go into negative when those calls return
|
||||
-- as this breaks subsequence checks in stack_depth().
|
||||
-- start from 16th frame, which is sufficiently large for this check.
|
||||
stack_level = stack_depth(16)
|
||||
|
||||
debug.sethook(debug_hook, "lcr")
|
||||
coro_debugger = coroutine.create(debugger_loop)
|
||||
return coroutine.resume(coro_debugger, file, info.currentline)
|
||||
@@ -555,6 +776,12 @@ local function start(controller_host, controller_port)
|
||||
end
|
||||
|
||||
local function controller(controller_host, controller_port)
|
||||
-- only one debugging session can be run (as there is only one debug hook)
|
||||
if isrunning() then return end
|
||||
|
||||
controller_host = controller_host or "localhost"
|
||||
controller_port = controller_port or mobdebug.port
|
||||
|
||||
local exitonerror = not skip -- exit if not running a scratchpad
|
||||
server = socket.connect(controller_host, controller_port)
|
||||
if server then
|
||||
@@ -583,8 +810,10 @@ local function controller(controller_host, controller_port)
|
||||
else
|
||||
if status then -- normal execution is done
|
||||
break
|
||||
elseif err and not err:find(deferror) then -- report the error
|
||||
report(debug.traceback(coro_debugee), err)
|
||||
elseif err and not tostring(err):find(deferror) then
|
||||
-- report the error back
|
||||
-- err is not necessarily a string, so convert to string to report
|
||||
report(debug.traceback(coro_debugee), tostring(err))
|
||||
if exitonerror then break end
|
||||
-- resume once more to clear the response the debugger wants to send
|
||||
local status, err = coroutine.resume(coro_debugger, events.RESTART)
|
||||
@@ -644,12 +873,12 @@ local function handle(params, client)
|
||||
if size then
|
||||
local msg = client:receive(tonumber(size))
|
||||
print("Error in remote application: " .. msg)
|
||||
os.exit()
|
||||
os.exit(1)
|
||||
return nil, nil, msg -- use return here for those cases where os.exit() is not wanted
|
||||
end
|
||||
else
|
||||
print("Unknown error")
|
||||
os.exit()
|
||||
os.exit(1)
|
||||
-- use return here for those cases where os.exit() is not wanted
|
||||
return nil, nil, "Debugger error: unexpected response '" .. breakpoint .. "'"
|
||||
end
|
||||
@@ -753,7 +982,8 @@ local function handle(params, client)
|
||||
else
|
||||
local file = io.open(exp, "r")
|
||||
if not file then print("Cannot open file " .. exp); return end
|
||||
local lines = file:read("*all")
|
||||
-- read the file and remove the shebang line as it causes a compilation error
|
||||
local lines = file:read("*all"):gsub("^#!.-\n", "\n")
|
||||
file:close()
|
||||
|
||||
local file = string.gsub(exp, "\\", "/") -- convert slash
|
||||
@@ -769,9 +999,23 @@ local function handle(params, client)
|
||||
if status == "200" then
|
||||
len = tonumber(len)
|
||||
if len > 0 then
|
||||
local res = client:receive(len)
|
||||
print(res)
|
||||
return res
|
||||
local status, res
|
||||
local str = client:receive(len)
|
||||
-- handle serialized table with results
|
||||
local func, err = loadstring(str)
|
||||
if func then
|
||||
status, res = pcall(func)
|
||||
if not status then err = res
|
||||
elseif type(res) ~= "table" then
|
||||
err = "received "..type(res).." instead of expected 'table'"
|
||||
end
|
||||
end
|
||||
if err then
|
||||
print("Error in processing results: " .. err)
|
||||
return nil, nil, "Error in processing results: " .. err
|
||||
end
|
||||
print((table.unpack or unpack)(res))
|
||||
return res[1], res
|
||||
end
|
||||
elseif status == "201" then
|
||||
_, _, file, line = string.find(params, "^201 Started%s+([%w%p%s]+)%s+(%d+)%s*$")
|
||||
@@ -793,7 +1037,7 @@ local function handle(params, client)
|
||||
elseif command == "listb" then
|
||||
for k, v in pairs(breakpoints) do
|
||||
local b = k .. ": " -- get filename
|
||||
for k, v in pairs(v) do
|
||||
for k in pairs(v) do
|
||||
b = b .. k .. " " -- get line numbers
|
||||
end
|
||||
print(b)
|
||||
@@ -804,6 +1048,34 @@ local function handle(params, client)
|
||||
end
|
||||
elseif command == "suspend" then
|
||||
client:send("SUSPEND\n")
|
||||
elseif command == "stack" then
|
||||
client:send("STACK\n")
|
||||
local resp = client:receive()
|
||||
local _, _, status, res = string.find(resp, "^(%d+)%s+%w+%s+(.+)%s*$")
|
||||
if status == "200" then
|
||||
local func, err = loadstring(res)
|
||||
if func == nil then
|
||||
print("Error in stack information: " .. err)
|
||||
return nil, nil, err
|
||||
end
|
||||
local stack = func()
|
||||
for _,frame in ipairs(stack) do
|
||||
-- remove basedir from short_src or source
|
||||
local src = string.gsub(frame[1][2], "\\", "/") -- convert slash
|
||||
if string.find(src, "@") == 1 then src = string.sub(src, 2) end
|
||||
if string.find(src, "%./") == 1 then src = string.sub(src, 3) end
|
||||
frame[1][2] = string.gsub(src, basedir, '') -- remove basedir
|
||||
print(serpent.line(frame[1], {comment = false}))
|
||||
end
|
||||
return stack
|
||||
elseif status == "401" then
|
||||
local res = "Error in stack result"
|
||||
print(res)
|
||||
return nil, nil, res
|
||||
else
|
||||
print("Unknown error")
|
||||
return nil, nil, "Debugger error: unexpected response after STACK"
|
||||
end
|
||||
elseif command == "basedir" then
|
||||
local _, _, dir = string.find(params, "^[a-z]+%s+(.+)$")
|
||||
if dir then
|
||||
@@ -821,16 +1093,17 @@ local function handle(params, client)
|
||||
print("setw <exp> -- adds a new watch expression")
|
||||
print("delw <index> -- removes the watch expression at index")
|
||||
print("delallw -- removes all watch expressions")
|
||||
print("run -- run until next breakpoint")
|
||||
print("step -- run until next line, stepping into function calls")
|
||||
print("over -- run until next line, stepping over function calls")
|
||||
print("out -- run until line after returning from current function")
|
||||
print("run -- runs until next breakpoint")
|
||||
print("step -- runs until next line, stepping into function calls")
|
||||
print("over -- runs until next line, stepping over function calls")
|
||||
print("out -- runs until line after returning from current function")
|
||||
print("listb -- lists breakpoints")
|
||||
print("listw -- lists watch expressions")
|
||||
print("eval <exp> -- evaluates expression on the current context and returns its value")
|
||||
print("exec <stmt> -- executes statement on the current context")
|
||||
print("load <file> -- loads a local file for debugging")
|
||||
print("reload -- restarts the current debugging session")
|
||||
print("stack -- reports stack trace")
|
||||
print("basedir [<path>] -- sets the base path of the remote application, or shows the current one")
|
||||
print("exit -- exits debugger")
|
||||
else
|
||||
@@ -845,6 +1118,8 @@ end
|
||||
|
||||
-- Starts debugging server
|
||||
local function listen(host, port)
|
||||
host = host or "*"
|
||||
port = port or mobdebug.port
|
||||
|
||||
local socket = require "socket"
|
||||
|
||||
@@ -884,6 +1159,8 @@ mobdebug.scratchpad = scratchpad
|
||||
mobdebug.handle = handle
|
||||
mobdebug.connect = connect
|
||||
mobdebug.start = start
|
||||
mobdebug.line = serpent.line
|
||||
mobdebug.dump = serpent.dump
|
||||
|
||||
-- this is needed to make "require 'modebug'" to work when mobdebug
|
||||
-- module is loaded manually
|
||||
|
||||
315
lualibs/testwell.lua
Normal file
315
lualibs/testwell.lua
Normal file
@@ -0,0 +1,315 @@
|
||||
--
|
||||
-- Copyright (C) 2012 Paul Kulchenko
|
||||
-- A simple testing library
|
||||
-- Based on lua-TestMore : <http://fperrad.github.com/lua-TestMore/>
|
||||
-- Copyright (c) 2009-2011 Francois Perrad
|
||||
-- This library is licensed under the terms of the MIT/X11 license,
|
||||
-- like Lua itself.
|
||||
--
|
||||
|
||||
local pairs = pairs
|
||||
local tostring = tostring
|
||||
local type = type
|
||||
local _G = _G or _ENV
|
||||
|
||||
-----------------------------------------------------------
|
||||
|
||||
local tb = {
|
||||
curr_test = 0,
|
||||
good_test = 0,
|
||||
skip_test = 0,
|
||||
}
|
||||
|
||||
function tb:print(...)
|
||||
print(...)
|
||||
end
|
||||
|
||||
function tb:note(...)
|
||||
self:print(...)
|
||||
end
|
||||
|
||||
function tb:diag(...)
|
||||
local arg = {...}
|
||||
for k, v in pairs(arg) do
|
||||
arg[k] = tostring(v)
|
||||
end
|
||||
local msg = table.concat(arg)
|
||||
msg = msg:gsub("\n", "\n# ")
|
||||
msg = msg:gsub("\n# \n", "\n#\n")
|
||||
msg = msg:gsub("\n# $", '')
|
||||
self:print("# " .. msg)
|
||||
end
|
||||
|
||||
function tb:ok(test, name, more)
|
||||
self.curr_test = self.curr_test + 1
|
||||
self.good_test = self.good_test + (test and 1 or 0)
|
||||
self.skip_test = self.skip_test + (test == nil and 1 or 0)
|
||||
name = tostring(name or '')
|
||||
local out = ''
|
||||
if not test then
|
||||
out = "not "
|
||||
end
|
||||
out = out .. "ok " .. self.curr_test
|
||||
if name ~= '' then
|
||||
out = out .. " - " .. name
|
||||
end
|
||||
self:print(out)
|
||||
if test == false then
|
||||
self:diag(" Failed test " .. (name and ("'" .. name .. "'") or ''))
|
||||
if debug then
|
||||
local info = debug.getinfo(3)
|
||||
local file = info.short_src
|
||||
local line = info.currentline
|
||||
self:diag(" in " .. file .. " at line " .. line .. ".")
|
||||
end
|
||||
self:diag(more)
|
||||
end
|
||||
end
|
||||
|
||||
function tb:done_testing(reset)
|
||||
local c, g, s = self.curr_test, self.good_test, self.skip_test
|
||||
if reset then
|
||||
self.curr_test = 0
|
||||
self.good_test = 0
|
||||
self.skip_test = 0
|
||||
end
|
||||
return c, g, s
|
||||
end
|
||||
|
||||
-----------------------------------------------------------
|
||||
|
||||
local serpent = (function() ---- include Serpent module for serialization
|
||||
local n, v = "serpent", 0.15 -- (C) 2012 Paul Kulchenko; MIT License
|
||||
local c, d = "Paul Kulchenko", "Serializer and pretty printer of Lua data types"
|
||||
local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'}
|
||||
local badtype = {thread = true, userdata = true}
|
||||
local keyword, globals, G = {}, {}, (_G or _ENV)
|
||||
for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false',
|
||||
'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
|
||||
'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end
|
||||
for k,v in pairs(G) do globals[v] = k end -- build func to name mapping
|
||||
for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do
|
||||
for k,v in pairs(G[g]) do globals[v] = g..'.'..k end end
|
||||
|
||||
local function s(t, opts)
|
||||
local name, indent, fatal = opts.name, opts.indent, opts.fatal
|
||||
local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge
|
||||
local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge)
|
||||
local comm = opts.comment and (tonumber(opts.comment) or math.huge)
|
||||
local seen, sref, syms, symn = {}, {}, {}, 0
|
||||
local function gensym(val) return tostring(val):gsub("[^%w]",""):gsub("(%d%w+)",
|
||||
function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return syms[s] end) end
|
||||
local function safestr(s) return type(s) == "number" and (huge and snum[tostring(s)] or s)
|
||||
or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026
|
||||
or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end
|
||||
local function comment(s,l) return comm and (l or 0) < comm and ' --[['..tostring(s)..']]' or '' end
|
||||
local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal
|
||||
and safestr(tostring(s))..comment('err',l) or error("Can't serialize "..tostring(s)) end
|
||||
local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r']
|
||||
local n = name == nil and '' or name
|
||||
local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n]
|
||||
local safe = plain and n or '['..safestr(n)..']'
|
||||
return (path or '')..(plain and path and '.' or '')..safe, safe end
|
||||
local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(o, n)
|
||||
local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'}
|
||||
local function padnum(d) return ("%0"..maxn.."d"):format(d) end
|
||||
table.sort(o, function(a,b)
|
||||
return (o[a] and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum))
|
||||
< (o[b] and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end
|
||||
local function val2str(t, name, indent, path, plainindex, level)
|
||||
local ttype, level = type(t), (level or 0)
|
||||
local spath, sname = safename(path, name)
|
||||
local tag = plainindex and
|
||||
((type(name) == "number") and '' or name..space..'='..space) or
|
||||
(name ~= nil and sname..space..'='..space or '')
|
||||
if seen[t] then
|
||||
table.insert(sref, spath..space..'='..space..seen[t])
|
||||
return tag..'nil'..comment('ref', level)
|
||||
elseif badtype[ttype] then return tag..globerr(t, level)
|
||||
elseif ttype == 'function' then
|
||||
seen[t] = spath
|
||||
local ok, res = pcall(string.dump, t)
|
||||
local func = ok and ((opts.nocode and "function() end" or
|
||||
"loadstring("..safestr(res)..",'@serialized')")..comment(t, level))
|
||||
return tag..(func or globerr(t, level))
|
||||
elseif ttype == "table" then
|
||||
if level >= maxl then return tag..'{}'..comment('max', level) end
|
||||
seen[t] = spath
|
||||
if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty
|
||||
local maxn, o, out = #t, {}, {}
|
||||
for key = 1, maxn do table.insert(o, key) end
|
||||
for key in pairs(t) do if not o[key] then table.insert(o, key) end end
|
||||
if opts.sortkeys then alphanumsort(o, opts.sortkeys) end
|
||||
for n, key in ipairs(o) do
|
||||
local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse
|
||||
if opts.ignore and opts.ignore[value] -- skip ignored values; do nothing
|
||||
or sparse and value == nil then -- skipping nils; do nothing
|
||||
elseif ktype == 'table' or ktype == 'function' then
|
||||
if not seen[key] and not globals[key] then
|
||||
table.insert(sref, 'local '..val2str(key,gensym(key),indent)) end
|
||||
table.insert(sref, seen[t]..'['..(seen[key] or globals[key] or gensym(key))
|
||||
..']'..space..'='..space..(seen[value] or val2str(value,nil,indent)))
|
||||
else
|
||||
if badtype[ktype] then plainindex, key = true, '['..globerr(key, level+1)..']' end
|
||||
table.insert(out,val2str(value,key,indent,spath,plainindex,level+1))
|
||||
end
|
||||
end
|
||||
local prefix = string.rep(indent or '', level)
|
||||
local head = indent and '{\n'..prefix..indent or '{'
|
||||
local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space))
|
||||
local tail = indent and "\n"..prefix..'}' or '}'
|
||||
return (custom and custom(tag,head,body,tail) or tag..head..body..tail)..comment(t, level)
|
||||
else return tag..safestr(t) end -- handle all other types
|
||||
end
|
||||
local sepr = indent and "\n" or ";"..space
|
||||
local body = val2str(t, name, indent) -- this call also populates sref
|
||||
local tail = #sref>0 and table.concat(sref, sepr)..sepr or ''
|
||||
return not name and body or "do local "..body..sepr..tail.."return "..name..sepr.."end"
|
||||
end
|
||||
|
||||
local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end
|
||||
return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s,
|
||||
dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end,
|
||||
line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end,
|
||||
block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end }
|
||||
end)() ---- end of Serpent module
|
||||
|
||||
-----------------------------------------------------------
|
||||
|
||||
local m = {}
|
||||
|
||||
function m.ok(test, name)
|
||||
tb:ok(test, name)
|
||||
end
|
||||
|
||||
local parms = {comment = false}
|
||||
function m.is(got, expected, name)
|
||||
if type(got) == 'table' then got = serpent.line(got, parms) end
|
||||
if type(expected) == 'table' then expected = serpent.line(expected, parms) end
|
||||
local pass = got == expected
|
||||
if got == nil then pass = nil end
|
||||
tb:ok(pass, name, not pass and
|
||||
" got: " .. tostring(got) ..
|
||||
"\n expected: " .. tostring(expected))
|
||||
end
|
||||
|
||||
function m.isnt(got, expected, name)
|
||||
if type(got) == 'table' then got = serpent.line(got, parms) end
|
||||
if type(expected) == 'table' then expected = serpent.line(expected, parms) end
|
||||
local pass = got ~= expected
|
||||
if got == nil then pass = nil end
|
||||
tb:ok(pass, name, not pass and
|
||||
" got: " .. tostring(got) ..
|
||||
"\n expected: anything else")
|
||||
end
|
||||
|
||||
function m.like(got, pattern, name)
|
||||
if type(pattern) ~= 'string' then
|
||||
return tb:ok(false, name, "pattern isn't a string : " .. tostring(pattern))
|
||||
end
|
||||
|
||||
local pass = tostring(got):match(pattern)
|
||||
if got == nil then pass = nil end
|
||||
tb:ok(pass, name, not pass and
|
||||
" '" .. tostring(got) .. "'" ..
|
||||
"\n doesn't match '" .. pattern .. "'")
|
||||
end
|
||||
|
||||
function m.unlike(got, pattern, name)
|
||||
if type(pattern) ~= 'string' then
|
||||
return tb:ok(false, name, "pattern isn't a string : " .. tostring(pattern))
|
||||
end
|
||||
|
||||
local pass = not tostring(got):match(pattern)
|
||||
if got == nil then pass = nil end
|
||||
tb:ok(pass, name, not pass and
|
||||
" '" .. tostring(got) .. "'" ..
|
||||
"\n matches '" .. pattern .. "'")
|
||||
end
|
||||
|
||||
local cmp = {
|
||||
['<'] = function (a, b) return a < b end,
|
||||
['<='] = function (a, b) return a <= b end,
|
||||
['>'] = function (a, b) return a > b end,
|
||||
['>='] = function (a, b) return a >= b end,
|
||||
['=='] = function (a, b) return a == b end,
|
||||
['~='] = function (a, b) return a ~= b end,
|
||||
}
|
||||
|
||||
function m.cmp_ok(this, op, that, name)
|
||||
local f = cmp[op]
|
||||
if not f then
|
||||
return tb:ok(false, name, "unknown operator : " .. tostring(op))
|
||||
end
|
||||
|
||||
local pass = f(this, that)
|
||||
if this == nil then pass = nil end
|
||||
tb:ok(pass, name, not pass and
|
||||
" " .. tostring(this) ..
|
||||
"\n " .. op ..
|
||||
"\n " .. tostring(that))
|
||||
end
|
||||
|
||||
function m.type_ok(val, t, name)
|
||||
if type(t) ~= 'string' then
|
||||
return tb:ok(false, name, "type isn't a string : " .. tostring(t))
|
||||
end
|
||||
|
||||
if type(val) == t then
|
||||
tb:ok(true, name)
|
||||
else
|
||||
tb:ok(false, name,
|
||||
" " .. tostring(val) .. " isn't a '" .. t .."', it's a '" .. type(val) .. "'")
|
||||
end
|
||||
end
|
||||
|
||||
function m.diag(...)
|
||||
tb:diag(...)
|
||||
end
|
||||
|
||||
function m.report()
|
||||
local total, good, skipped = tb:done_testing(true)
|
||||
if total == 0 then return end
|
||||
local failed = total - good - skipped
|
||||
local sum = ("(%d/%d/%d)."):format(good, skipped, total)
|
||||
local num, msg = 0, ""
|
||||
if good > 0 then
|
||||
num, msg = good, msg .. "passed " .. good
|
||||
end
|
||||
if failed > 0 then
|
||||
num, msg = failed, msg .. (#msg > 0 and (skipped > 0 and ", " or " and ") or "")
|
||||
.. "failed " .. failed
|
||||
end
|
||||
if skipped > 0 then
|
||||
num, msg = skipped, msg .. (#msg > 0 and ((good > 0 and failed > 0 and ',' or '') .." and ") or "")
|
||||
.. "skipped " .. skipped
|
||||
end
|
||||
msg = ("Looks like you %s test%s of %d %s"):format(msg, (num > 1 and 's' or ''), total, sum)
|
||||
if skipped == total then msg = "Looks like you skipped all tests " .. sum end
|
||||
if good == total then msg = "All tests passed " .. sum end
|
||||
tb:note(("1..%d # %s"):format(total, msg))
|
||||
end
|
||||
|
||||
function m.ismain()
|
||||
for l = 3, 64 do -- only check up to 64 level; no more needed
|
||||
local info = debug.getinfo(l)
|
||||
if not info then return true end
|
||||
if info.func == require then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- this is needed to call report() when the test object is destroyed
|
||||
if _VERSION >= "Lua 5.2" then
|
||||
setmetatable(m, {__gc = m.report})
|
||||
else
|
||||
-- keep sentinel alive until 'm' is garbage collected
|
||||
m.sentinel = newproxy(true)
|
||||
getmetatable(m.sentinel).__gc = m.report
|
||||
end
|
||||
|
||||
for k, v in pairs(m) do -- injection
|
||||
_G[k] = v
|
||||
end
|
||||
|
||||
return m
|
||||
15
spec/lua.lua
15
spec/lua.lua
@@ -1,6 +1,8 @@
|
||||
-- authors: Luxinia Dev (Eike Decker & Christoph Kubisch)
|
||||
---------------------------------------------------------
|
||||
|
||||
local funcdef = "([A-Za-z_][A-Za-z0-9_%.%:]*)%s*"
|
||||
local funccall = "([A-Za-z_][A-Za-z0-9_]*)%s*"
|
||||
return {
|
||||
exts = {"lua"},
|
||||
lexer = wxstc.wxSTC_LEX_LUA,
|
||||
@@ -8,23 +10,18 @@ return {
|
||||
linecomment = "--",
|
||||
sep = "%.:",
|
||||
isfncall = function(str)
|
||||
return string.find(str,"([A-Za-z0-9_]+)%s*%(")
|
||||
return string.find(str, funccall .. "%(")
|
||||
end,
|
||||
isfndef = function(str)
|
||||
local l
|
||||
local s,e,cap,par = string.find(str,"function%s+([A-Za-z0-9_]+%s-[%.%:]?%s-[A-Za-z0-9_]*)%s*(%(.-%))")
|
||||
local s,e,cap,par = string.find(str, "function%s+" .. funcdef .. "(%(.-%))")
|
||||
-- try to match without brackets now, but only at the beginning of the line
|
||||
if (not s) then
|
||||
s,e,cap = string.find(str,"^%s*function%s+([A-Za-z0-9_]+%s-[%.%:]?%s-[A-Za-z0-9_]*)%s*")
|
||||
s,e,cap = string.find(str, "^%s*function%s+" .. funcdef)
|
||||
end
|
||||
-- try to match "foo = function()"
|
||||
if (not s) then
|
||||
s,e,cap,cap1,cap2,par = string.find(str,"(([A-Za-z0-9_]+%s-[%.%:]?%s-)([A-Za-z0-9_]*))%s*=%s*function%s*(%(.-%))")
|
||||
-- check if we captured 'local foo =' instead of 'foo.bar ='
|
||||
if cap1 and cap2 and string.len(cap2) > 0 and not string.find(cap1,'[%.%:]') then
|
||||
cap = cap2
|
||||
s = s + string.len(cap1)
|
||||
end
|
||||
s,e,cap,par = string.find(str, funcdef .. "=%s*function%s*(%(.-%))")
|
||||
end
|
||||
if (s) then
|
||||
l = string.find(string.sub(str,1,s-1),"local%s+$")
|
||||
|
||||
16
src/defs.lua
16
src/defs.lua
@@ -166,6 +166,10 @@ config = {
|
||||
|
||||
singleinstanceport = 0xe493,
|
||||
-- UDP port for single instance communication
|
||||
|
||||
activateoutput = false, -- activate output/console on Run/Debug/Compile
|
||||
unhidewxwindow = false, -- try to unhide a wx window
|
||||
allowinteractivescript = false, -- allow interaction in the output window
|
||||
}
|
||||
|
||||
-- application engine
|
||||
@@ -306,12 +310,10 @@ debuginterface = {
|
||||
interpreter = {
|
||||
name = "",
|
||||
description = "",
|
||||
api = {"apifile_without_extension"} -- optional to limit loaded lua apis
|
||||
frun = function(self,wfilename,withdebugger)
|
||||
end,
|
||||
fprojdir = function(self,wfilename)
|
||||
return "projpath_from_filename" -- optional
|
||||
end,
|
||||
fattachdebug = function(self) end, -- optional
|
||||
api = {"apifile_without_extension"} -- (opt) to limit loaded lua apis
|
||||
frun = function(self,wfilename,withdebugger) end,
|
||||
fprojdir = function(self,wfilename) return "projpath_from_filename" end, -- (opt)
|
||||
fattachdebug = function(self) end, -- (opt)
|
||||
hasdebugger = false, -- if debugging is available
|
||||
scratchextloop = false, -- (opt) if scratchpad requires handling for external loop
|
||||
}
|
||||
|
||||
@@ -89,8 +89,8 @@ local function addAPI(apifile,only,subapis,known) -- relative to API directory
|
||||
end
|
||||
|
||||
local function loadallAPIs (only,subapis,known)
|
||||
for i,dir in ipairs(FileSysGet(".\\api\\*.*",wx.wxDIR)) do
|
||||
local files = FileSysGet(dir.."\\*.*",wx.wxFILE)
|
||||
for i,dir in ipairs(FileSysGet("./api/*",wx.wxDIR)) do
|
||||
local files = FileSysGet(dir.."/*.*",wx.wxFILE)
|
||||
for i,file in ipairs(files) do
|
||||
if file:match "%.lua$" then
|
||||
addAPI(file,only,subapis,known)
|
||||
@@ -239,7 +239,7 @@ end
|
||||
|
||||
function GetTipInfo(editor, content, short)
|
||||
local caller = content:match("([%w_]+)%(%s*$")
|
||||
local class = caller and content:match("([%w_%.]+)[%.:]"..caller.."%(%s*$")
|
||||
local class = caller and content:match("([%w_]+)[%.:]"..caller.."%(%s*$")
|
||||
local tip = editor.api.tip
|
||||
|
||||
local classtab = short and tip.shortfinfoclass or tip.finfoclass
|
||||
|
||||
@@ -29,7 +29,7 @@ end
|
||||
function LoadFile(filePath, editor, file_must_exist)
|
||||
filePath = wx.wxFileName(filePath):GetFullPath()
|
||||
local cmpName = string.lower(string.gsub(filePath, "\\", "/"))
|
||||
|
||||
|
||||
-- prevent files from being reopened again
|
||||
if (not editor) then
|
||||
for id, doc in pairs(openDocuments) do
|
||||
@@ -54,6 +54,7 @@ function LoadFile(filePath, editor, file_must_exist)
|
||||
return nil
|
||||
end
|
||||
|
||||
local current = editor and editor:GetCurrentPos()
|
||||
editor = editor
|
||||
or findDocumentToReuse()
|
||||
or CreateEditor(wx.wxFileName(filePath):GetFullName() or "untitled.lua")
|
||||
@@ -64,6 +65,7 @@ function LoadFile(filePath, editor, file_must_exist)
|
||||
editor:MarkerDeleteAll(BREAKPOINT_MARKER)
|
||||
editor:MarkerDeleteAll(CURRENT_LINE_MARKER)
|
||||
editor:AppendText(file_text)
|
||||
if current then editor:GotoPos(current) end
|
||||
if (ide.config.editor.autotabs) then
|
||||
local found = string.find(file_text,"\t") ~= nil
|
||||
editor:SetUseTabs(found)
|
||||
@@ -80,8 +82,10 @@ function LoadFile(filePath, editor, file_must_exist)
|
||||
IndicateFunctions(editor)
|
||||
|
||||
SettingsAppendFileToHistory(filePath)
|
||||
|
||||
SetEditorSelection(nil)
|
||||
|
||||
-- activate the editor; this is needed for those cases when the editor is
|
||||
-- created from some other element, for example, from a project tree.
|
||||
SetEditorSelection()
|
||||
|
||||
return editor
|
||||
end
|
||||
@@ -111,7 +115,7 @@ function OpenFile(event)
|
||||
"",
|
||||
"",
|
||||
exts,
|
||||
wx.wxOPEN + wx.wxFILE_MUST_EXIST)
|
||||
wx.wxFD_OPEN + wx.wxFD_FILE_MUST_EXIST)
|
||||
if fileDialog:ShowModal() == wx.wxID_OK then
|
||||
if not LoadFile(fileDialog:GetPath(), nil, true) then
|
||||
wx.wxMessageBox("Unable to load file '"..fileDialog:GetPath().."'.",
|
||||
@@ -178,12 +182,15 @@ function SaveFileAs(editor)
|
||||
fn:GetPath(wx.wxPATH_GET_VOLUME),
|
||||
fn:GetFullName(),
|
||||
exts,
|
||||
wx.wxSAVE)
|
||||
wx.wxFD_SAVE)
|
||||
|
||||
if fileDialog:ShowModal() == wx.wxID_OK then
|
||||
local filePath = fileDialog:GetPath()
|
||||
|
||||
if SaveFile(editor, filePath) then
|
||||
SetEditorSelection() -- update title of the editor
|
||||
FileTreeRefresh() -- refresh the tree to reflect the new file
|
||||
FileTreeMarkSelected(filePath)
|
||||
SetupKeywords(editor, GetFileExt(filePath))
|
||||
IndicateFunctions(editor)
|
||||
if MarkupStyle then MarkupStyle(editor) end
|
||||
@@ -250,7 +257,7 @@ local function removePage(index)
|
||||
notebook:SetSelection(prevIndex)
|
||||
end
|
||||
|
||||
SetEditorSelection(nil) -- will use notebook GetSelection to update
|
||||
SetEditorSelection() -- will use notebook GetSelection to update
|
||||
end
|
||||
|
||||
function ClosePage(selection)
|
||||
@@ -307,8 +314,13 @@ function SaveOnExit(allow_cancel)
|
||||
if (SaveModifiedDialog(document.editor, allow_cancel) == wx.wxID_CANCEL) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
document.isModified = false
|
||||
-- if all documents have been saved or refused to save, then mark those that
|
||||
-- are still modified as not modified (they don't need to be saved)
|
||||
-- to keep their tab names correct
|
||||
for id, document in pairs(openDocuments) do
|
||||
if document.isModified then SetDocumentModified(id, false) end
|
||||
end
|
||||
|
||||
return true
|
||||
@@ -394,7 +406,10 @@ function ClearAllCurrentLineMarkers()
|
||||
end
|
||||
|
||||
function CompileProgram(editor, quiet)
|
||||
local editorText = editor:GetText()
|
||||
-- remove shebang line (#!) as it throws a compilation error as
|
||||
-- loadstring() doesn't allow it even though lua/loadfile accepts it.
|
||||
-- replace with a new line to keep the number of lines the same.
|
||||
local editorText = editor:GetText():gsub("^#!.-\n", "\n")
|
||||
local id = editor:GetId()
|
||||
local filePath = DebuggerMakeFileName(editor, openDocuments[id].filePath)
|
||||
local _, errMsg, line_num = wxlua.CompileLuaScript(editorText, filePath)
|
||||
@@ -492,7 +507,8 @@ function ShowFullScreen(setFullScreen)
|
||||
|
||||
uimgr:GetPane("toolBar"):Show(not setFullScreen)
|
||||
uimgr:Update()
|
||||
frame:ShowFullScreen(setFullScreen)
|
||||
-- protect from systems that don't have ShowFullScreen (GTK on linux?)
|
||||
pcall(function() frame:ShowFullScreen(setFullScreen) end)
|
||||
end
|
||||
|
||||
function CloseWindow(event)
|
||||
@@ -510,9 +526,14 @@ function CloseWindow(event)
|
||||
SettingsSaveView()
|
||||
SettingsSaveFramePosition(ide.frame, "MainFrame")
|
||||
SettingsSaveEditorSettings()
|
||||
DebuggerCloseWatchWindow()
|
||||
DebuggerKillClient()
|
||||
if DebuggerCloseWatchWindow then DebuggerCloseWatchWindow() end
|
||||
if DebuggerCloseStackWindow then DebuggerCloseStackWindow() end
|
||||
if DebuggerShutdown then DebuggerShutdown() end
|
||||
ide.settings:delete() -- always delete the config
|
||||
event:Skip()
|
||||
|
||||
-- without explicit exit() the IDE crashes with SIGILL exception when closed
|
||||
-- on MacOS compiled under 64bit with wxwidgets 2.9.3
|
||||
if ide.osname == "Macintosh" then os.exit() end
|
||||
end
|
||||
frame:Connect(wx.wxEVT_CLOSE_WINDOW, CloseWindow)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-- Integration with MobDebug
|
||||
-- Copyright Paul Kulchenko 2011
|
||||
-- Copyright 2011-12 Paul Kulchenko
|
||||
-- Original authors: Lomtik Software (J. Winwood & John Labenski)
|
||||
-- Luxinia Dev (Eike Decker & Christoph Kubisch)
|
||||
|
||||
@@ -12,26 +12,127 @@ local debugger = ide.debugger
|
||||
debugger.server = nil -- DebuggerServer object when debugging, else nil
|
||||
debugger.running = false -- true when the debuggee is running
|
||||
debugger.listening = false -- true when the debugger is listening for a client
|
||||
debugger.portnumber = 8171 -- the port # to use for debugging
|
||||
debugger.portnumber = mobdebug.port or 8171 -- the port # to use for debugging
|
||||
debugger.watchWindow = nil -- the watchWindow, nil when not created
|
||||
debugger.watchListCtrl = nil -- the child listctrl in the watchWindow
|
||||
debugger.watchCtrl = nil -- the child ctrl in the watchWindow
|
||||
debugger.stackWindow = nil -- the stackWindow, nil when not created
|
||||
debugger.stackCtrl = nil -- the child ctrl in the stackWindow
|
||||
debugger.hostname = (function() -- check what address is resolvable
|
||||
local addr = wx.wxIPV4address()
|
||||
for _, host in ipairs({wx.wxGetHostName(), wx.wxGetFullHostName()}) do
|
||||
if addr:Hostname(host) then return host end
|
||||
end
|
||||
return "localhost" -- last resort; no known good hostname
|
||||
end)()
|
||||
|
||||
local notebook = ide.frame.notebook
|
||||
|
||||
local function updateWatchesSync()
|
||||
local watchListCtrl = debugger.watchListCtrl
|
||||
if watchListCtrl and debugger.server and not debugger.running then
|
||||
for idx = 0, watchListCtrl:GetItemCount() - 1 do
|
||||
local expression = watchListCtrl:GetItemText(idx)
|
||||
local value, _, error = debugger.evaluate(expression)
|
||||
watchListCtrl:SetItem(idx, 1, value or ('error: ' .. error))
|
||||
local watchCtrl = debugger.watchCtrl
|
||||
if watchCtrl and debugger.server
|
||||
and not debugger.running and not debugger.scratchpad then
|
||||
for idx = 0, watchCtrl:GetItemCount() - 1 do
|
||||
local expression = watchCtrl:GetItemText(idx)
|
||||
local _, values, error = debugger.evaluate(expression)
|
||||
if error then error = error:gsub("%[.-%]:%d+:%s+","")
|
||||
elseif #values == 0 then values = {'nil'} end
|
||||
watchCtrl:SetItem(idx, 1, error and ('error: '..error) or values[1])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local simpleType = {['nil'] = true, ['string'] = true, ['number'] = true, ['boolean'] = true}
|
||||
local stackItemValue = {}
|
||||
local function checkIfExpandable(value, item)
|
||||
local expandable = type(value) == 'table' and next(value) ~= nil
|
||||
and not stackItemValue[value] -- only expand first time
|
||||
if expandable then -- cache table value to expand when requested
|
||||
stackItemValue[item:GetValue()] = value
|
||||
stackItemValue[value] = item:GetValue() -- to avoid circular refs
|
||||
end
|
||||
return expandable
|
||||
end
|
||||
|
||||
local function updateStackSync()
|
||||
local stackCtrl = debugger.stackCtrl
|
||||
if stackCtrl and debugger.server
|
||||
and not debugger.running and not debugger.scratchpad then
|
||||
local stack = debugger.stack()
|
||||
if not stack or #stack == 0 then stackCtrl:DeleteAllItems(); return end
|
||||
stackCtrl:Freeze()
|
||||
stackCtrl:DeleteAllItems()
|
||||
local params = {comment = false, nocode = true}
|
||||
local root = stackCtrl:AddRoot("Stack")
|
||||
stackItemValue = {} -- reset cache of items in the stack
|
||||
for _,frame in ipairs(stack) do
|
||||
-- "main chunk at line 24"
|
||||
-- "foo() at line 13 (defined at foobar.lua:11)"
|
||||
-- call = { source.name, source.source, source.linedefined,
|
||||
-- source.currentline, source.what, source.namewhat, source.short_src }
|
||||
local call = frame[1]
|
||||
local func = call[5] == "main" and "main chunk"
|
||||
or call[5] == "C" and (call[1] or "C function")
|
||||
or call[5] == "tail" and "tail call"
|
||||
or (call[1] or "anonymous function")
|
||||
local text = func ..
|
||||
(call[4] == -1 and '' or " at line "..call[4]) ..
|
||||
(call[5] ~= "main" and call[5] ~= "Lua" and ''
|
||||
or (call[3] > 0 and " (defined at "..call[2]..":"..call[3]..")"
|
||||
or " (defined in "..call[2]..")"))
|
||||
local callitem = stackCtrl:AppendItem(root, text, 0)
|
||||
for name,val in pairs(frame[2]) do
|
||||
local value, comment = val[1], val[2]
|
||||
local text = ("%s = %s%s"):
|
||||
format(name, mobdebug.line(value, params),
|
||||
simpleType[type(value)] and "" or (" --[["..comment.."]]"))
|
||||
local item = stackCtrl:AppendItem(callitem, text, 1)
|
||||
if checkIfExpandable(value, item) then
|
||||
stackCtrl:SetItemHasChildren(item, true)
|
||||
end
|
||||
end
|
||||
for name,val in pairs(frame[3]) do
|
||||
local value, comment = val[1], val[2]
|
||||
local text = ("%s = %s%s"):
|
||||
format(name, mobdebug.line(value, params),
|
||||
simpleType[type(value)] and "" or (" --[["..comment.."]]"))
|
||||
local item = stackCtrl:AppendItem(callitem, text, 2)
|
||||
if checkIfExpandable(value, item) then
|
||||
stackCtrl:SetItemHasChildren(item, true)
|
||||
end
|
||||
end
|
||||
stackCtrl:SortChildren(callitem)
|
||||
stackCtrl:Expand(callitem)
|
||||
end
|
||||
stackCtrl:EnsureVisible(stackCtrl:GetFirstChild(root))
|
||||
stackCtrl:Thaw()
|
||||
end
|
||||
end
|
||||
|
||||
local function updateStackAndWatches()
|
||||
if debugger.server and not debugger.running then
|
||||
copas.addthread(function() updateStackSync() updateWatchesSync() end)
|
||||
end
|
||||
end
|
||||
|
||||
local function updateWatches()
|
||||
if debugger.watchListCtrl and debugger.server and not debugger.running then
|
||||
copas.addthread(updateWatchesSync)
|
||||
if debugger.server and not debugger.running then
|
||||
copas.addthread(function() updateWatchesSync() end)
|
||||
end
|
||||
end
|
||||
|
||||
local function killClient()
|
||||
if (debugger.pid) then
|
||||
-- using SIGTERM for some reason kills not only the debugee process,
|
||||
-- but also some system processes, which leads to a blue screen crash
|
||||
-- (at least on Windows Vista SP2)
|
||||
local ret = wx.wxProcess.Kill(debugger.pid, wx.wxSIGKILL, wx.wxKILL_CHILDREN)
|
||||
if ret == wx.wxKILL_OK then
|
||||
DisplayOutput(("Program stopped (pid: %d).\n"):format(debugger.pid))
|
||||
elseif ret ~= wx.wxKILL_NO_PROCESS then
|
||||
DisplayOutput(("Unable to stop program (pid: %d), code %d.\n")
|
||||
:format(debugger.pid, ret))
|
||||
end
|
||||
debugger.pid = nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,7 +151,7 @@ local function activateDocument(fileName, line)
|
||||
for _, document in pairs(ide.openDocuments) do
|
||||
local editor = document.editor
|
||||
-- for running in cygwin, use same type of separators
|
||||
filePath = string.gsub(document.filePath, "\\", "/")
|
||||
local filePath = string.gsub(document.filePath, "\\", "/")
|
||||
local fileName = string.gsub(fileName, "\\", "/")
|
||||
if string.upper(filePath) == string.upper(fileName) then
|
||||
local selection = document.index
|
||||
@@ -69,23 +170,56 @@ local function activateDocument(fileName, line)
|
||||
return activated ~= nil, activated
|
||||
end
|
||||
|
||||
debugger.shell = function(expression)
|
||||
local function reSetBreakpoints()
|
||||
-- remove all breakpoints that may still be present from the last session
|
||||
-- this only matters for those remote clients that reload scripts
|
||||
-- without resetting their breakpoints
|
||||
debugger.handle("delallb")
|
||||
|
||||
-- go over all windows and find all breakpoints
|
||||
if (not debugger.scratchpad) then
|
||||
for _, document in pairs(ide.openDocuments) do
|
||||
local editor = document.editor
|
||||
local filePath = document.filePath
|
||||
local line = editor:MarkerNext(0, BREAKPOINT_MARKER_VALUE)
|
||||
while line ~= -1 do
|
||||
debugger.handle("setb " .. filePath .. " " .. (line+1))
|
||||
line = editor:MarkerNext(line + 1, BREAKPOINT_MARKER_VALUE)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
debugger.shell = function(expression, isstatement)
|
||||
if debugger.server and not debugger.running then
|
||||
copas.addthread(function ()
|
||||
local addedret = false
|
||||
local value, _, err = debugger.handle('exec ' .. expression)
|
||||
if err and (err:find("'=' expected near '<eof>'") or
|
||||
err:find("syntax error near '") or
|
||||
err:find("unexpected symbol near '")) then
|
||||
value, _, err = debugger.handle('eval ' .. expression:gsub("^%s*=%s*",""))
|
||||
addedret = true
|
||||
-- exec command is not expected to return anything.
|
||||
-- eval command returns 0 or more results.
|
||||
-- 'values' has a list of serialized results returned.
|
||||
-- as it is not possible to distinguish between 0 and nil returned,
|
||||
-- 'nil' is always returned in this case.
|
||||
-- the first value returned by eval command is not used;
|
||||
-- this may need to be taken into account by other debuggers.
|
||||
local addedret, forceexpression = true, expression:match("^%s*=%s*")
|
||||
expression = expression:gsub("^%s*=%s*","")
|
||||
local _, values, err = debugger.evaluate(expression)
|
||||
if not forceexpression and err and
|
||||
(err:find("'<eof>' expected near '") or
|
||||
err:find("'%(' expected near") or
|
||||
err:find("unexpected symbol near '")) then
|
||||
_, values, err = debugger.execute(expression)
|
||||
addedret = false
|
||||
end
|
||||
|
||||
if err then
|
||||
if addedret then err = err:gsub('^%[string "return ', '[string "') end
|
||||
DisplayShellErr(err)
|
||||
elseif addedret or (value ~= nil and value ~= 'nil') then
|
||||
DisplayShell(value)
|
||||
elseif addedret or #values > 0 then
|
||||
-- if empty table is returned, then show nil if this was an expression
|
||||
if #values == 0 and (forceexpression or not isstatement) then
|
||||
values = {'nil'}
|
||||
end
|
||||
DisplayShell((table.unpack or unpack)(values))
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -93,9 +227,15 @@ end
|
||||
|
||||
debugger.listen = function()
|
||||
local server = socket.bind("*", debugger.portnumber)
|
||||
DisplayOutput("Started debugger server; clients can connect to "..wx.wxGetHostName()..":"..debugger.portnumber..".\n")
|
||||
DisplayOutput(("Debugger server started at %s:%d.\n")
|
||||
:format(debugger.hostname, debugger.portnumber))
|
||||
copas.autoclose = false
|
||||
copas.addserver(server, function (skt)
|
||||
if debugger.server then
|
||||
DisplayOutput("Refused a request to start a new debugging session as there is one in progress already.\n")
|
||||
return
|
||||
end
|
||||
|
||||
local options = debugger.options or {}
|
||||
if not debugger.scratchpad then SetAllEditorsReadOnly(true) end
|
||||
local wxfilepath = GetEditorFileAndCurInfo()
|
||||
@@ -109,28 +249,13 @@ debugger.listen = function()
|
||||
debugger.socket = skt
|
||||
debugger.loop = false
|
||||
debugger.scratchable = false
|
||||
debugger.stats = {line = 0}
|
||||
|
||||
-- load the remote file into the debugger
|
||||
-- set basedir first, before loading to make sure that the path is correct
|
||||
debugger.handle("basedir " .. debugger.basedir)
|
||||
|
||||
-- remove all breakpoints that may still be present from the last session
|
||||
-- this only matters for those remote clients that reload scripts
|
||||
-- without resetting their breakpoints
|
||||
debugger.handle("delallb")
|
||||
|
||||
-- go over all windows and find all breakpoints
|
||||
if (not debugger.scratchpad) then
|
||||
for _, document in pairs(ide.openDocuments) do
|
||||
local editor = document.editor
|
||||
local filePath = document.filePath
|
||||
line = editor:MarkerNext(0, BREAKPOINT_MARKER_VALUE)
|
||||
while line ~= -1 do
|
||||
debugger.handle("setb " .. filePath .. " " .. (line+1))
|
||||
line = editor:MarkerNext(line + 1, BREAKPOINT_MARKER_VALUE)
|
||||
end
|
||||
end
|
||||
end
|
||||
reSetBreakpoints()
|
||||
|
||||
if (options.run) then
|
||||
local file, line = debugger.handle("run")
|
||||
@@ -162,16 +287,20 @@ debugger.listen = function()
|
||||
if activated then
|
||||
debugger.basedir = path
|
||||
debugger.handle("basedir " .. debugger.basedir)
|
||||
-- reset breakpoints again as basedir has changed
|
||||
reSetBreakpoints()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not activated then
|
||||
DisplayOutput("Can't find file '" .. file .. "' to activate for debugging; open the file before debugging.\n")
|
||||
DisplayOutput(("Can't find file '%s' to activate for debugging; open the file in the editor before debugging.\n")
|
||||
:format(file))
|
||||
return debugger.terminate()
|
||||
end
|
||||
elseif err then
|
||||
DisplayOutput("Can't debug the script in the active editor window. Compilation error:\n" .. err .. "\n")
|
||||
DisplayOutput(("Can't debug the script in the active editor window. Compilation error:\n%s\n")
|
||||
:format(err))
|
||||
return debugger.terminate()
|
||||
else
|
||||
debugger.scratchable = true
|
||||
@@ -180,11 +309,14 @@ debugger.listen = function()
|
||||
end
|
||||
|
||||
if (not options.noshell and not debugger.scratchpad) then
|
||||
ShellSupportRemote(debugger.shell, debugger.pid)
|
||||
ShellSupportRemote(debugger.shell)
|
||||
end
|
||||
|
||||
DisplayOutput("Started remote debugging session (base directory: '" .. debugger.basedir .. "').\n")
|
||||
updateStackSync()
|
||||
updateWatchesSync()
|
||||
|
||||
DisplayOutput(("Debugging session started in '%s'.\n")
|
||||
:format(debugger.basedir))
|
||||
end)
|
||||
debugger.listening = true
|
||||
end
|
||||
@@ -201,7 +333,7 @@ debugger.handle = function(command, server)
|
||||
end
|
||||
|
||||
debugger.running = true
|
||||
local file, line, err = mobdebug.handle(command, server and server or debugger.server)
|
||||
local file, line, err = mobdebug.handle(command, server or debugger.server)
|
||||
debugger.running = false
|
||||
|
||||
return file, line, err
|
||||
@@ -223,10 +355,12 @@ debugger.exec = function(command)
|
||||
file = debugger.basedir .. file
|
||||
end
|
||||
if activateDocument(file, line) then
|
||||
debugger.stats.line = debugger.stats.line + 1
|
||||
if debugger.loop then
|
||||
updateStackSync()
|
||||
updateWatchesSync()
|
||||
else
|
||||
updateWatches()
|
||||
updateStackAndWatches()
|
||||
return
|
||||
end
|
||||
else
|
||||
@@ -254,7 +388,7 @@ debugger.update = function() copas.step(0) end
|
||||
debugger.terminate = function()
|
||||
if debugger.server then
|
||||
if debugger.pid then -- if there is PID, try local kill
|
||||
DebuggerKillClient()
|
||||
killClient()
|
||||
else -- otherwise, try graceful exit for the remote process
|
||||
debugger.breaknow("exit")
|
||||
end
|
||||
@@ -271,6 +405,7 @@ debugger.out = function() debugger.exec("out") end
|
||||
debugger.run = function() debugger.exec("run") end
|
||||
debugger.evaluate = function(expression) return debugger.handle('eval ' .. expression) end
|
||||
debugger.execute = function(expression) return debugger.handle('exec ' .. expression) end
|
||||
debugger.stack = function() return debugger.handle('stack') end
|
||||
debugger.breaknow = function(command)
|
||||
-- stop if we're running a "trace" command
|
||||
debugger.loop = false
|
||||
@@ -295,6 +430,17 @@ end
|
||||
debugger.breakpoint = function(file, line, state)
|
||||
debugger.handleAsync((state and "setb " or "delb ") .. file .. " " .. line)
|
||||
end
|
||||
debugger.quickeval = function(var, callback)
|
||||
if debugger.server and not debugger.running then
|
||||
copas.addthread(function ()
|
||||
local _, values, err = debugger.evaluate(var)
|
||||
local val = err
|
||||
and err:gsub("%[.-%]:%d+:%s*","error: ")
|
||||
or (var .. " = " .. (#values > 0 and values[1] or 'nil'))
|
||||
if callback then callback(val) end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------------------------
|
||||
-- public api
|
||||
@@ -305,19 +451,9 @@ function DebuggerAttachDefault(options)
|
||||
debugger.listen()
|
||||
end
|
||||
|
||||
function DebuggerKillClient()
|
||||
if (debugger.pid) then
|
||||
-- using SIGTERM for some reason kills not only the debugee process,
|
||||
-- but also some system processes, which leads to a blue screen crash
|
||||
-- (at least on Windows Vista SP2)
|
||||
local ret = wx.wxProcess.Kill(debugger.pid, wx.wxSIGKILL, wx.wxKILL_CHILDREN)
|
||||
if ret == wx.wxKILL_OK then
|
||||
DisplayOutput("Stopped process (pid: "..debugger.pid..").\n")
|
||||
elseif ret ~= wx.wxKILL_NO_PROCESS then
|
||||
DisplayOutput("Unable to stop process (pid: "..debugger.pid.."), code "..tostring(ret)..".\n")
|
||||
end
|
||||
debugger.pid = nil
|
||||
end
|
||||
function DebuggerShutdown()
|
||||
if debugger.server then debugger.terminate() end
|
||||
if debugger.pid then killClient() end
|
||||
end
|
||||
|
||||
function DebuggerStop()
|
||||
@@ -325,34 +461,108 @@ function DebuggerStop()
|
||||
debugger.server = nil
|
||||
debugger.pid = nil
|
||||
SetAllEditorsReadOnly(false)
|
||||
ShellSupportRemote(nil, 0)
|
||||
ShellSupportRemote(nil)
|
||||
ClearAllCurrentLineMarkers()
|
||||
DebuggerScratchpadOff()
|
||||
DisplayOutput("Completed debugging session.\n")
|
||||
DisplayOutput(("Debugging session completed (traced %d instruction%s).\n")
|
||||
:format(debugger.stats.line, debugger.stats.line == 1 and '' or 's'))
|
||||
end
|
||||
end
|
||||
|
||||
function DebuggerCreateStackWindow()
|
||||
DisplayOutput("Not Yet Implemented\n")
|
||||
end
|
||||
|
||||
function DebuggerCloseStackWindow()
|
||||
|
||||
if (debugger.stackWindow) then
|
||||
SettingsSaveFramePosition(debugger.stackWindow, "StackWindow")
|
||||
debugger.stackCtrl = nil
|
||||
debugger.stackWindow = nil
|
||||
end
|
||||
end
|
||||
|
||||
function DebuggerCloseWatchWindow()
|
||||
if (debugger.watchWindow) then
|
||||
SettingsSaveFramePosition(debugger.watchWindow, "WatchWindow")
|
||||
debugger.watchListCtrl = nil
|
||||
debugger.watchCtrl = nil
|
||||
debugger.watchWindow = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- need imglist to be a file local variable as SetImageList takes ownership
|
||||
-- of it and if done inside a function, icons do not work as expected
|
||||
local imglist = wx.wxImageList(16,16)
|
||||
do
|
||||
local getBitmap = (ide.app.createbitmap or wx.wxArtProvider.GetBitmap)
|
||||
local size = wx.wxSize(16,16)
|
||||
-- 0 = stack call
|
||||
imglist:Add(getBitmap(wx.wxART_GO_FORWARD, wx.wxART_OTHER, size))
|
||||
-- 1 = local variables
|
||||
imglist:Add(getBitmap(wx.wxART_LIST_VIEW, wx.wxART_OTHER, size))
|
||||
-- 2 = upvalues
|
||||
imglist:Add(getBitmap(wx.wxART_REPORT_VIEW, wx.wxART_OTHER, size))
|
||||
end
|
||||
|
||||
function DebuggerCreateStackWindow()
|
||||
if (debugger.stackWindow) then return updateStackAndWatches() end
|
||||
local width = 360
|
||||
local stackWindow = wx.wxFrame(ide.frame, wx.wxID_ANY,
|
||||
"Stack Window",
|
||||
wx.wxDefaultPosition, wx.wxSize(width, 200),
|
||||
wx.wxDEFAULT_FRAME_STYLE + wx.wxFRAME_FLOAT_ON_PARENT)
|
||||
|
||||
debugger.stackWindow = stackWindow
|
||||
|
||||
local stackCtrl = wx.wxTreeCtrl(stackWindow, ID "debug.stack",
|
||||
wx.wxDefaultPosition, wx.wxDefaultSize,
|
||||
wx.wxTR_LINES_AT_ROOT + wx.wxTR_HAS_BUTTONS + wx.wxTR_SINGLE + wx.wxTR_HIDE_ROOT)
|
||||
|
||||
debugger.stackCtrl = stackCtrl
|
||||
|
||||
stackCtrl:SetImageList(imglist)
|
||||
stackWindow:CentreOnParent()
|
||||
SettingsRestoreFramePosition(stackWindow, "StackWindow")
|
||||
stackWindow:Show(true)
|
||||
|
||||
stackWindow:Connect(wx.wxEVT_CLOSE_WINDOW,
|
||||
function (event)
|
||||
DebuggerCloseStackWindow()
|
||||
stackWindow = nil
|
||||
stackCtrl = nil
|
||||
event:Skip()
|
||||
end)
|
||||
|
||||
stackCtrl:Connect( wx.wxEVT_COMMAND_TREE_ITEM_EXPANDING,
|
||||
function (event)
|
||||
local item_id = event:GetItem()
|
||||
local count = stackCtrl:GetChildrenCount(item_id, false)
|
||||
if count > 0 then return true end
|
||||
|
||||
local image = stackCtrl:GetItemImage(item_id)
|
||||
local num = 1
|
||||
for name,value in pairs(stackItemValue[item_id:GetValue()]) do
|
||||
local strval = mobdebug.line(value, {comment = false, nocode = true})
|
||||
local text = type(name) == "number"
|
||||
and (num == name and strval or ("[%s] = %s"):format(name, strval))
|
||||
or ("%s = %s"):format(name, strval)
|
||||
local item = stackCtrl:AppendItem(item_id, text, image)
|
||||
if checkIfExpandable(value, item) then
|
||||
stackCtrl:SetItemHasChildren(item, true)
|
||||
end
|
||||
num = num + 1
|
||||
end
|
||||
|
||||
stackCtrl:SortChildren(item_id)
|
||||
return true
|
||||
end)
|
||||
stackCtrl:Connect( wx.wxEVT_COMMAND_TREE_ITEM_COLLAPSED,
|
||||
function() return true end)
|
||||
|
||||
updateStackAndWatches()
|
||||
end
|
||||
|
||||
function DebuggerCreateWatchWindow()
|
||||
local width = 200
|
||||
if (debugger.watchWindow) then return updateWatches() end
|
||||
local width = 360
|
||||
local watchWindow = wx.wxFrame(ide.frame, wx.wxID_ANY,
|
||||
"Watch Window",
|
||||
wx.wxDefaultPosition, wx.wxSize(width, 160),
|
||||
wx.wxDefaultPosition, wx.wxSize(width, 200),
|
||||
wx.wxDEFAULT_FRAME_STYLE + wx.wxFRAME_FLOAT_ON_PARENT)
|
||||
|
||||
debugger.watchWindow = watchWindow
|
||||
@@ -367,31 +577,31 @@ function DebuggerCreateWatchWindow()
|
||||
watchMenuBar:Append(watchMenu, "&Watches")
|
||||
watchWindow:SetMenuBar(watchMenuBar)
|
||||
|
||||
local watchListCtrl = wx.wxListCtrl(watchWindow, ID_WATCH_LISTCTRL,
|
||||
local watchCtrl = wx.wxListCtrl(watchWindow, ID_WATCH_LISTCTRL,
|
||||
wx.wxDefaultPosition, wx.wxDefaultSize,
|
||||
wx.wxLC_REPORT + wx.wxLC_EDIT_LABELS)
|
||||
|
||||
debugger.watchListCtrl = watchListCtrl
|
||||
debugger.watchCtrl = watchCtrl
|
||||
|
||||
local info = wx.wxListItem()
|
||||
info:SetMask(wx.wxLIST_MASK_TEXT + wx.wxLIST_MASK_WIDTH)
|
||||
info:SetText("Expression")
|
||||
info:SetWidth(width * 0.45)
|
||||
watchListCtrl:InsertColumn(0, info)
|
||||
info:SetWidth(width * 0.32)
|
||||
watchCtrl:InsertColumn(0, info)
|
||||
|
||||
info:SetText("Value")
|
||||
info:SetWidth(width * 0.45)
|
||||
watchListCtrl:InsertColumn(1, info)
|
||||
info:SetWidth(width * 0.56)
|
||||
watchCtrl:InsertColumn(1, info)
|
||||
|
||||
watchWindow:CentreOnParent()
|
||||
SettingsRestoreFramePosition(watchWindow, "WatchWindow")
|
||||
watchWindow:Show(true)
|
||||
|
||||
local function findSelectedWatchItem()
|
||||
local count = watchListCtrl:GetSelectedItemCount()
|
||||
local count = watchCtrl:GetSelectedItemCount()
|
||||
if count > 0 then
|
||||
for idx = 0, watchListCtrl:GetItemCount() - 1 do
|
||||
if watchListCtrl:GetItemState(idx, wx.wxLIST_STATE_FOCUSED) ~= 0 then
|
||||
for idx = 0, watchCtrl:GetItemCount() - 1 do
|
||||
if watchCtrl:GetItemState(idx, wx.wxLIST_STATE_FOCUSED) ~= 0 then
|
||||
return idx
|
||||
end
|
||||
end
|
||||
@@ -403,62 +613,61 @@ function DebuggerCreateWatchWindow()
|
||||
function (event)
|
||||
DebuggerCloseWatchWindow()
|
||||
watchWindow = nil
|
||||
watchListCtrl = nil
|
||||
watchCtrl = nil
|
||||
event:Skip()
|
||||
end)
|
||||
|
||||
watchWindow:Connect(ID_ADDWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
function ()
|
||||
local row = watchListCtrl:InsertItem(watchListCtrl:GetItemCount(), "Expr")
|
||||
watchListCtrl:SetItem(row, 0, "Expr")
|
||||
watchListCtrl:SetItem(row, 1, "Value")
|
||||
watchListCtrl:EditLabel(row)
|
||||
local row = watchCtrl:InsertItem(watchCtrl:GetItemCount(), "Expr")
|
||||
watchCtrl:SetItem(row, 0, "Expr")
|
||||
watchCtrl:SetItem(row, 1, "Value")
|
||||
watchCtrl:EditLabel(row)
|
||||
end)
|
||||
|
||||
watchWindow:Connect(ID_EDITWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
function ()
|
||||
local row = findSelectedWatchItem()
|
||||
if row >= 0 then
|
||||
watchListCtrl:EditLabel(row)
|
||||
watchCtrl:EditLabel(row)
|
||||
end
|
||||
end)
|
||||
watchWindow:Connect(ID_EDITWATCH, wx.wxEVT_UPDATE_UI,
|
||||
function (event)
|
||||
event:Enable(watchListCtrl:GetSelectedItemCount() > 0)
|
||||
event:Enable(watchCtrl:GetSelectedItemCount() > 0)
|
||||
end)
|
||||
|
||||
watchWindow:Connect(ID_REMOVEWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
function ()
|
||||
local row = findSelectedWatchItem()
|
||||
if row >= 0 then
|
||||
watchListCtrl:DeleteItem(row)
|
||||
watchCtrl:DeleteItem(row)
|
||||
end
|
||||
end)
|
||||
watchWindow:Connect(ID_REMOVEWATCH, wx.wxEVT_UPDATE_UI,
|
||||
function (event)
|
||||
event:Enable(watchListCtrl:GetSelectedItemCount() > 0)
|
||||
event:Enable(watchCtrl:GetSelectedItemCount() > 0)
|
||||
end)
|
||||
|
||||
watchWindow:Connect(ID_EVALUATEWATCH, wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
function () updateWatches() end)
|
||||
watchWindow:Connect(ID_EVALUATEWATCH, wx.wxEVT_UPDATE_UI,
|
||||
function (event)
|
||||
event:Enable(watchListCtrl:GetItemCount() > 0)
|
||||
event:Enable(watchCtrl:GetItemCount() > 0)
|
||||
end)
|
||||
|
||||
watchListCtrl:Connect(wx.wxEVT_COMMAND_LIST_END_LABEL_EDIT,
|
||||
watchCtrl:Connect(wx.wxEVT_COMMAND_LIST_END_LABEL_EDIT,
|
||||
function (event)
|
||||
watchListCtrl:SetItem(event:GetIndex(), 0, event:GetText())
|
||||
updateWatches()
|
||||
if #(event:GetText()) > 0 then
|
||||
watchCtrl:SetItem(event:GetIndex(), 0, event:GetText())
|
||||
updateWatches()
|
||||
end
|
||||
event:Skip()
|
||||
end)
|
||||
end
|
||||
|
||||
function DebuggerMakeFileName(editor, filePath)
|
||||
if not filePath then
|
||||
filePath = "file"..tostring(editor)
|
||||
end
|
||||
return filePath
|
||||
return filePath or editor:GetText()
|
||||
end
|
||||
|
||||
function DebuggerToggleBreakpoint(editor, line)
|
||||
@@ -496,7 +705,8 @@ function DebuggerRefreshScratchpad()
|
||||
else
|
||||
local clear = ide.frame.menuBar:IsChecked(ID_CLEAROUTPUT)
|
||||
local scratchpadEditor = debugger.scratchpad.editor
|
||||
local code = scratchpadEditor:GetText()
|
||||
-- take editor text and remove shebang line
|
||||
local code = scratchpadEditor:GetText():gsub("^#!.-\n", "\n")
|
||||
local filePath = DebuggerMakeFileName(scratchpadEditor,
|
||||
ide.openDocuments[scratchpadEditor:GetId()].filePath)
|
||||
|
||||
@@ -505,12 +715,27 @@ function DebuggerRefreshScratchpad()
|
||||
-- these errors are handled and not reported to the user
|
||||
local errormsg = 'execution suspended at ' .. os.clock()
|
||||
local stopper = "\ndo error('" .. errormsg .. "') end"
|
||||
-- store if interpreter requires a special handling for external loop
|
||||
local extloop = ide.interpreter.scratchextloop
|
||||
|
||||
local function reloadScratchpadCode()
|
||||
debugger.scratchpad.running = os.clock()
|
||||
debugger.scratchpad.updated = false
|
||||
debugger.scratchpad.runs = (debugger.scratchpad.runs or 0) + 1
|
||||
|
||||
-- the code can be running in two ways under scratchpad:
|
||||
-- 1. controlled by the application, requires stopper (most apps)
|
||||
-- 2. controlled by some external loop (for example, love2d).
|
||||
-- in the first case we need to reload the app after each change
|
||||
-- in the second case, we need to load the app once and then
|
||||
-- "execute" new code to reflect the changes (with some limitations).
|
||||
local _, _, err
|
||||
if extloop then -- if the execution is controlled by an external loop
|
||||
if debugger.scratchpad.runs == 1
|
||||
then _, _, err = debugger.loadstring(filePath, code)
|
||||
else _, _, err = debugger.execute(code) end
|
||||
else _, _, err = debugger.loadstring(filePath, code .. stopper) end
|
||||
|
||||
local _, _, err = debugger.loadstring(filePath, code .. stopper)
|
||||
local prefix = "Compilation error"
|
||||
|
||||
if clear then ClearOutput() end
|
||||
@@ -551,7 +776,7 @@ function DebuggerScratchpadOn(editor)
|
||||
debugger.scratchpad.updated = true
|
||||
ClearAllCurrentLineMarkers()
|
||||
SetAllEditorsReadOnly(false)
|
||||
ShellSupportRemote(nil, 0) -- disable remote shell
|
||||
ShellSupportRemote(nil) -- disable remote shell
|
||||
DebuggerRefreshScratchpad()
|
||||
elseif not ProjectDebug(true, "scratchpad") then
|
||||
debugger.scratchpad = nil
|
||||
|
||||
@@ -18,7 +18,7 @@ local projcombobox = ide.frame.projpanel.projcombobox
|
||||
local statusTextTable = { "OVR?", "R/O?", "Cursor Pos" }
|
||||
|
||||
-- set funclist font to be the same as the combobox in the project dropdown
|
||||
funclist:SetFont(projcombobox:GetFont())
|
||||
funclist:SetFont(ide.font.fNormal)
|
||||
|
||||
local function updateStatusText(editor)
|
||||
local texts = { "", "", "" }
|
||||
@@ -105,7 +105,7 @@ local function isFileAlteredOnDisk(editor)
|
||||
wx.wxMessageBox(fileName.." is no longer on the disk.",
|
||||
GetIDEString("editormessage"),
|
||||
wx.wxOK + wx.wxCENTRE, ide.frame)
|
||||
elseif modTime:IsValid() and oldModTime:IsEarlierThan(modTime) then
|
||||
elseif not editor:GetReadOnly() and modTime:IsValid() and oldModTime:IsEarlierThan(modTime) then
|
||||
local ret = wx.wxMessageBox(fileName.." has been modified on disk.\nDo you want to reload it?",
|
||||
GetIDEString("editormessage"),
|
||||
wx.wxYES_NO + wx.wxCENTRE, ide.frame)
|
||||
@@ -125,7 +125,8 @@ function GetEditor(selection)
|
||||
selection = notebook:GetSelection()
|
||||
end
|
||||
local editor
|
||||
if (selection >= 0) and (selection < notebook:GetPageCount()) and (notebook:GetPage(selection):GetClassInfo():GetClassName()=="wxStyledTextCtrl") then
|
||||
if (selection >= 0) and (selection < notebook:GetPageCount())
|
||||
and (notebook:GetPage(selection):GetClassInfo():GetClassName()=="wxStyledTextCtrl") then
|
||||
editor = notebook:GetPage(selection):DynamicCast("wxStyledTextCtrl")
|
||||
end
|
||||
return editor
|
||||
@@ -146,7 +147,6 @@ function SetEditorSelection(selection)
|
||||
|
||||
editor:SetFocus()
|
||||
editor:SetSTCFocus(true)
|
||||
isFileAlteredOnDisk(editor)
|
||||
local id = editor:GetId()
|
||||
if openDocuments[id] and openDocuments[id].filePath then
|
||||
FileTreeMarkSelected(openDocuments[id].filePath)
|
||||
@@ -206,8 +206,8 @@ function CreateEditor(name)
|
||||
editor:SetBufferedDraw(true)
|
||||
editor:StyleClearAll()
|
||||
|
||||
editor:SetFont(ide.font)
|
||||
editor:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.font)
|
||||
editor:SetFont(ide.font.eNormal)
|
||||
editor:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.font.eNormal)
|
||||
|
||||
editor:SetTabWidth(ide.config.editor.tabwidth or 4)
|
||||
editor:SetIndent(ide.config.editor.tabwidth or 4)
|
||||
@@ -363,17 +363,17 @@ function CreateEditor(name)
|
||||
end)
|
||||
|
||||
editor:Connect(wxstc.wxEVT_STC_SAVEPOINTREACHED,
|
||||
function (event)
|
||||
function ()
|
||||
SetDocumentModified(editor:GetId(), false)
|
||||
end)
|
||||
|
||||
editor:Connect(wxstc.wxEVT_STC_SAVEPOINTLEFT,
|
||||
function (event)
|
||||
function ()
|
||||
SetDocumentModified(editor:GetId(), true)
|
||||
end)
|
||||
|
||||
editor:Connect(wxstc.wxEVT_STC_UPDATEUI,
|
||||
function (event)
|
||||
function ()
|
||||
updateStatusText(editor)
|
||||
updateBraceMatch(editor)
|
||||
for _,iv in ipairs(editor.ev) do
|
||||
@@ -396,13 +396,14 @@ function CreateEditor(name)
|
||||
event:Skip()
|
||||
end)
|
||||
|
||||
local inhandler = false
|
||||
editor:Connect(wx.wxEVT_SET_FOCUS,
|
||||
function (event)
|
||||
event:Skip()
|
||||
if ide.in_evt_focus or ide.exitingProgram then return end
|
||||
ide.in_evt_focus = true -- true when in editor focus event to avoid recursion
|
||||
if inhandler or ide.exitingProgram then return end
|
||||
inhandler = true
|
||||
isFileAlteredOnDisk(editor)
|
||||
ide.in_evt_focus = false
|
||||
inhandler = false
|
||||
end)
|
||||
|
||||
editor:Connect(wx.wxEVT_KEY_DOWN,
|
||||
@@ -447,10 +448,10 @@ function GetSpec(ext,forcespec)
|
||||
-- search proper spec
|
||||
-- allow forcespec for "override"
|
||||
if ext and not spec then
|
||||
for i,curspec in pairs(ide.specs) do
|
||||
for _,curspec in pairs(ide.specs) do
|
||||
local exts = curspec.exts
|
||||
if (exts) then
|
||||
for n,curext in ipairs(exts) do
|
||||
for _,curext in ipairs(exts) do
|
||||
if (curext == ext) then
|
||||
spec = curspec
|
||||
break
|
||||
@@ -474,16 +475,11 @@ function IndicateFunctions(editor, lines, linee)
|
||||
|
||||
if (lines < 0) then return end
|
||||
|
||||
local isfunc = editor.spec.isfncall
|
||||
local iscomment = editor.spec.iscomment
|
||||
local iskeyword0 = editor.spec.iskeyword0
|
||||
local isfncall = editor.spec.isfncall
|
||||
local isinvalid = {}
|
||||
for i,v in pairs(iscomment) do
|
||||
isinvalid[i] = v
|
||||
end
|
||||
for i,v in pairs(iskeyword0) do
|
||||
isinvalid[i] = v
|
||||
end
|
||||
for i,v in pairs(editor.spec.iscomment) do isinvalid[i] = v end
|
||||
for i,v in pairs(editor.spec.iskeyword0) do isinvalid[i] = v end
|
||||
for i,v in pairs(editor.spec.isstring) do isinvalid[i] = v end
|
||||
|
||||
local INDICS_MASK = wxstc.wxSTC_INDICS_MASK
|
||||
local INDIC0_MASK = wxstc.wxSTC_INDIC0_MASK
|
||||
@@ -500,13 +496,13 @@ function IndicateFunctions(editor, lines, linee)
|
||||
while from do
|
||||
tx = from==1 and tx or string.sub(tx,from)
|
||||
|
||||
local f,t,w = isfunc(tx)
|
||||
local f,t,w = isfncall(tx)
|
||||
|
||||
if (f) then
|
||||
local p = ls+f+off
|
||||
local s = bit.band(editor:GetStyleAt(p),31)
|
||||
editor:StartStyling(p,INDICS_MASK)
|
||||
editor:SetStyling(t-f,isinvalid[s] and 0 or (INDIC0_MASK + 1))
|
||||
editor:SetStyling(#w,isinvalid[s] and 0 or (INDIC0_MASK + 1))
|
||||
off = off + t
|
||||
end
|
||||
from = t and (t+1)
|
||||
@@ -533,11 +529,11 @@ function SetupKeywords(editor, ext, forcespec, styles, font, fontitalic)
|
||||
-- Get the items in the global "wx" table for autocompletion
|
||||
if not wxkeywords then
|
||||
local keyword_table = {}
|
||||
for index, value in pairs(wx) do
|
||||
for index in pairs(wx) do
|
||||
table.insert(keyword_table, "wx."..index.." ")
|
||||
end
|
||||
|
||||
for index, value in pairs(wxstc) do
|
||||
for index in pairs(wxstc) do
|
||||
table.insert(keyword_table, "wxstc."..index.." ")
|
||||
end
|
||||
|
||||
@@ -559,7 +555,7 @@ function SetupKeywords(editor, ext, forcespec, styles, font, fontitalic)
|
||||
end
|
||||
|
||||
StylesApplyToEditor(styles or ide.config.styles, editor,
|
||||
font or ide.font,fontitalic or ide.fontItalic,lexerstyleconvert)
|
||||
font or ide.font.eNormal,fontitalic or ide.font.eItalic,lexerstyleconvert)
|
||||
end
|
||||
|
||||
----------------------------------------------------
|
||||
@@ -587,9 +583,13 @@ funclist:Connect(wx.wxEVT_SET_FOCUS,
|
||||
local linee = editor:GetLineCount()-1
|
||||
for line=lines,linee do
|
||||
local tx = editor:GetLine(line)
|
||||
local s,e,cap,l = editor.spec.isfndef(tx)
|
||||
local s,_,cap,l = editor.spec.isfndef(tx)
|
||||
if (s) then
|
||||
funclist:Append((l and " " or "")..cap,line)
|
||||
local ls = editor:PositionFromLine(line)
|
||||
local style = bit.band(editor:GetStyleAt(ls+s),31)
|
||||
if not (editor.spec.iscomment[style] or editor.spec.isstring[style]) then
|
||||
funclist:Append((l and " " or "")..cap,line)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -30,14 +30,15 @@ local filetree = ide.filetree
|
||||
-- ------------
|
||||
|
||||
do
|
||||
local getBitmap = (ide.app.createbitmap or wx.wxArtProvider.GetBitmap)
|
||||
local size = wx.wxSize(16, 16)
|
||||
filetree.imglist = wx.wxImageList(16,16)
|
||||
-- 0 = directory
|
||||
filetree.imglist:Add(wx.wxArtProvider.GetBitmap(wx.wxART_FOLDER, wx.wxART_OTHER, size))
|
||||
filetree.imglist:Add(getBitmap(wx.wxART_FOLDER, wx.wxART_OTHER, size))
|
||||
-- 1 = file known spec
|
||||
filetree.imglist:Add(wx.wxArtProvider.GetBitmap(wx.wxART_HELP_PAGE, wx.wxART_OTHER, size))
|
||||
filetree.imglist:Add(getBitmap(wx.wxART_HELP_PAGE, wx.wxART_OTHER, size))
|
||||
-- 2 = file rest
|
||||
filetree.imglist:Add(wx.wxArtProvider.GetBitmap(wx.wxART_NORMAL_FILE, wx.wxART_OTHER, size))
|
||||
filetree.imglist:Add(getBitmap(wx.wxART_NORMAL_FILE, wx.wxART_OTHER, size))
|
||||
end
|
||||
|
||||
local function treeAddDir(tree,parent_id,rootdir)
|
||||
@@ -50,11 +51,10 @@ local function treeAddDir(tree,parent_id,rootdir)
|
||||
end
|
||||
|
||||
local curr
|
||||
local search = rootdir..string_Pathsep.."*.*"
|
||||
local dirs = FileSysGet(search,wx.wxDIR)
|
||||
local search = rootdir..string_Pathsep.."*"
|
||||
|
||||
-- append directories
|
||||
for _,dir in ipairs(dirs) do
|
||||
for _,dir in ipairs(FileSysGet(search,wx.wxDIR)) do
|
||||
local name = dir:match("%"..string_Pathsep.."("..stringset_File.."+)$")
|
||||
local icon = 0
|
||||
local item = items[name .. icon]
|
||||
@@ -78,8 +78,7 @@ local function treeAddDir(tree,parent_id,rootdir)
|
||||
end
|
||||
|
||||
-- then append files
|
||||
local files = FileSysGet(search,wx.wxFILE)
|
||||
for _,file in ipairs(files) do
|
||||
for _,file in ipairs(FileSysGet(search,wx.wxFILE)) do
|
||||
local name = file:match("%"..string_Pathsep.."("..stringset_File.."+)$")
|
||||
local known = GetSpec(GetFileExt(name))
|
||||
local icon = known and 1 or 2
|
||||
@@ -127,10 +126,8 @@ end
|
||||
|
||||
local function treeSetRoot(tree,treedata,rootdir)
|
||||
tree:DeleteAllItems()
|
||||
|
||||
if (not wx.wxDirExists(rootdir)) then
|
||||
treedata.root_id = nil
|
||||
tree:AddRoot("Invalid")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -199,7 +196,8 @@ local projtree = wx.wxTreeCtrl(projpanel, ID "filetree.projtree",
|
||||
or (wx.wxTR_HAS_BUTTONS + wx.wxTR_SINGLE + wx.wxTR_HIDE_ROOT))
|
||||
|
||||
-- use the same font in the combobox as is used in the filetree
|
||||
projcombobox:SetFont(projtree:GetFont())
|
||||
projtree:SetFont(ide.font.fNormal)
|
||||
projcombobox:SetFont(ide.font.fNormal)
|
||||
|
||||
local projTopSizer = wx.wxBoxSizer( wx.wxHORIZONTAL );
|
||||
projTopSizer:Add(projcombobox, 1, wx.wxALL + wx.wxALIGN_LEFT + wx.wxGROW, 0)
|
||||
@@ -308,7 +306,7 @@ end
|
||||
|
||||
local curr_id
|
||||
function FileTreeMarkSelected(file)
|
||||
if not file then return end
|
||||
if not file or not filetree.projdirText or #filetree.projdirText == 0 then return end
|
||||
local item_id = findItem(projtree, file)
|
||||
if curr_id ~= item_id then
|
||||
if curr_id and projtree:IsBold(curr_id) then
|
||||
@@ -321,3 +319,7 @@ function FileTreeMarkSelected(file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function FileTreeRefresh()
|
||||
treeSetRoot(projtree,filetree.projdata,filetree.projdirText)
|
||||
end
|
||||
|
||||
@@ -219,9 +219,8 @@ end
|
||||
|
||||
local function ProcInFiles(startdir,mask,subdirs,replace)
|
||||
if (subdirs) then
|
||||
local dirs = FileSysGet(startdir..string_Pathsep.."*.*",wx.wxDIR)
|
||||
local dirs = FileSysGet(startdir..string_Pathsep.."*",wx.wxDIR)
|
||||
for i,dir in ipairs(dirs) do
|
||||
--DisplayOutput(dir.."\n")
|
||||
ProcInFiles(dir,mask,true,replace)
|
||||
end
|
||||
end
|
||||
@@ -230,8 +229,6 @@ local function ProcInFiles(startdir,mask,subdirs,replace)
|
||||
for i,file in ipairs(files) do
|
||||
findReplace.curfilename = file
|
||||
|
||||
--DisplayOutput(file.."\n")
|
||||
|
||||
-- load file
|
||||
local handle = io.open(file, "rb")
|
||||
if handle then
|
||||
|
||||
@@ -10,33 +10,23 @@ BREAKPOINT_MARKER_VALUE = 2 -- = 2^BREAKPOINT_MARKER
|
||||
CURRENT_LINE_MARKER = 2
|
||||
CURRENT_LINE_MARKER_VALUE = 4 -- = 2^CURRENT_LINE_MARKER
|
||||
|
||||
-- Globals
|
||||
local font = nil -- fonts to use for the editor
|
||||
local fontItalic = nil
|
||||
local ofont = nil -- fonts to use for the outputshell
|
||||
local ofontItalic = nil
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Pick some reasonable fixed width fonts to use for the editor
|
||||
if wx.__WXMSW__ then
|
||||
font = wx.wxFont(ide.config.editor.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_NORMAL, wx.wxFONTWEIGHT_NORMAL, false, ide.config.editor.fontname or "Courier New")
|
||||
fontItalic = wx.wxFont(ide.config.editor.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_ITALIC, wx.wxFONTWEIGHT_NORMAL, false, ide.config.editor.fontname or "Courier New")
|
||||
else
|
||||
font = wx.wxFont(ide.config.editor.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_NORMAL, wx.wxFONTWEIGHT_NORMAL, false, ide.config.editor.fontname or "")
|
||||
fontItalic = wx.wxFont(ide.config.editor.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_ITALIC, wx.wxFONTWEIGHT_NORMAL, false, ide.config.editor.fontname or "")
|
||||
local function setFont(style, config, name, size)
|
||||
return wx.wxFont(config.fontsize or size or 10, wx.wxFONTFAMILY_MODERN, style,
|
||||
wx.wxFONTWEIGHT_NORMAL, false, config.fontname or name,
|
||||
config.fontencoding or wx.wxFONTENCODING_DEFAULT)
|
||||
end
|
||||
ide.font.eNormal = setFont(wx.wxFONTSTYLE_NORMAL, ide.config.editor, wx.__WXMSW__ and "Courier New" or "")
|
||||
ide.font.eItalic = setFont(wx.wxFONTSTYLE_ITALIC, ide.config.editor, wx.__WXMSW__ and "Courier New" or "")
|
||||
|
||||
if wx.__WXMSW__ then
|
||||
ofont = wx.wxFont(ide.config.outputshell.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_NORMAL, wx.wxFONTWEIGHT_NORMAL, false, ide.config.outputshell.fontname or "Courier New")
|
||||
ofontItalic = wx.wxFont(ide.config.outputshell.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_ITALIC, wx.wxFONTWEIGHT_NORMAL, false, ide.config.outputshell.fontname or "Courier New")
|
||||
else
|
||||
ofont = wx.wxFont(ide.config.outputshell.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_NORMAL, wx.wxFONTWEIGHT_NORMAL, false, ide.config.outputshell.fontname or "")
|
||||
ofontItalic = wx.wxFont(ide.config.outputshell.fontsize or 10, wx.wxFONTFAMILY_MODERN, wx.wxFONTSTYLE_ITALIC, wx.wxFONTWEIGHT_NORMAL, false, ide.config.outputshell.fontname or "")
|
||||
end
|
||||
ide.font.oNormal = setFont(wx.wxFONTSTYLE_NORMAL, ide.config.outputshell, wx.__WXMSW__ and "Courier New" or "")
|
||||
ide.font.oItalic = setFont(wx.wxFONTSTYLE_ITALIC, ide.config.outputshell, wx.__WXMSW__ and "Courier New" or "")
|
||||
|
||||
ide.font = font
|
||||
ide.fontItalic = fontItalic
|
||||
ide.ofont = ofont
|
||||
ide.ofontItalic = ofontItalic
|
||||
-- treeCtrl font requires slightly different handling
|
||||
local gui, config = wx.wxTreeCtrl():GetFont(), ide.config.filetree
|
||||
if config.fontsize then gui:SetPointSize(config.fontsize) end
|
||||
if config.fontname then gui:SetFaceName(config.fontname) end
|
||||
ide.font.fNormal = gui
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Create the wxFrame
|
||||
@@ -57,7 +47,6 @@ local function createFrame()
|
||||
end)
|
||||
|
||||
local menuBar = wx.wxMenuBar()
|
||||
frame:SetMenuBar(menuBar)
|
||||
frame.menuBar = menuBar
|
||||
|
||||
local statusBar = frame:CreateStatusBar( 5 )
|
||||
@@ -79,23 +68,24 @@ local function createToolBar(frame)
|
||||
local funclist = wx.wxChoice.new(toolBar,ID "toolBar.funclist",wx.wxDefaultPosition, wx.wxSize.new(240,16))
|
||||
|
||||
-- note: Ususally the bmp size isn't necessary, but the HELP icon is not the right size in MSW
|
||||
local getBitmap = (ide.app.createbitmap or wx.wxArtProvider.GetBitmap)
|
||||
local toolBmpSize = toolBar:GetToolBitmapSize()
|
||||
toolBar:AddTool(ID_NEW, "New", wx.wxArtProvider.GetBitmap(wx.wxART_NORMAL_FILE, wx.wxART_TOOLBAR, toolBmpSize), "Create an empty document")
|
||||
toolBar:AddTool(ID_OPEN, "Open", wx.wxArtProvider.GetBitmap(wx.wxART_FILE_OPEN, wx.wxART_TOOLBAR, toolBmpSize), "Open an existing document")
|
||||
toolBar:AddTool(ID_SAVE, "Save", wx.wxArtProvider.GetBitmap(wx.wxART_FILE_SAVE, wx.wxART_TOOLBAR, toolBmpSize), "Save the current document")
|
||||
toolBar:AddTool(ID_SAVEALL, "Save All", wx.wxArtProvider.GetBitmap(wx.wxART_NEW_DIR, wx.wxART_TOOLBAR, toolBmpSize), "Save all documents")
|
||||
toolBar:AddTool(ID_NEW, "New", getBitmap(wx.wxART_NORMAL_FILE, wx.wxART_TOOLBAR, toolBmpSize), "Create an empty document")
|
||||
toolBar:AddTool(ID_OPEN, "Open", getBitmap(wx.wxART_FILE_OPEN, wx.wxART_TOOLBAR, toolBmpSize), "Open an existing document")
|
||||
toolBar:AddTool(ID_SAVE, "Save", getBitmap(wx.wxART_FILE_SAVE, wx.wxART_TOOLBAR, toolBmpSize), "Save the current document")
|
||||
toolBar:AddTool(ID_SAVEALL, "Save All", getBitmap(wx.wxART_NEW_DIR, wx.wxART_TOOLBAR, toolBmpSize), "Save all documents")
|
||||
toolBar:AddSeparator()
|
||||
toolBar:AddTool(ID_CUT, "Cut", wx.wxArtProvider.GetBitmap(wx.wxART_CUT, wx.wxART_TOOLBAR, toolBmpSize), "Cut the selection")
|
||||
toolBar:AddTool(ID_COPY, "Copy", wx.wxArtProvider.GetBitmap(wx.wxART_COPY, wx.wxART_TOOLBAR, toolBmpSize), "Copy the selection")
|
||||
toolBar:AddTool(ID_PASTE, "Paste", wx.wxArtProvider.GetBitmap(wx.wxART_PASTE, wx.wxART_TOOLBAR, toolBmpSize), "Paste text from the clipboard")
|
||||
toolBar:AddTool(ID_CUT, "Cut", getBitmap(wx.wxART_CUT, wx.wxART_TOOLBAR, toolBmpSize), "Cut the selection")
|
||||
toolBar:AddTool(ID_COPY, "Copy", getBitmap(wx.wxART_COPY, wx.wxART_TOOLBAR, toolBmpSize), "Copy the selection")
|
||||
toolBar:AddTool(ID_PASTE, "Paste", getBitmap(wx.wxART_PASTE, wx.wxART_TOOLBAR, toolBmpSize), "Paste text from the clipboard")
|
||||
toolBar:AddSeparator()
|
||||
toolBar:AddTool(ID_UNDO, "Undo", wx.wxArtProvider.GetBitmap(wx.wxART_UNDO, wx.wxART_TOOLBAR, toolBmpSize), "Undo last edit")
|
||||
toolBar:AddTool(ID_REDO, "Redo", wx.wxArtProvider.GetBitmap(wx.wxART_REDO, wx.wxART_TOOLBAR, toolBmpSize), "Redo last undo")
|
||||
toolBar:AddTool(ID_UNDO, "Undo", getBitmap(wx.wxART_UNDO, wx.wxART_TOOLBAR, toolBmpSize), "Undo last edit")
|
||||
toolBar:AddTool(ID_REDO, "Redo", getBitmap(wx.wxART_REDO, wx.wxART_TOOLBAR, toolBmpSize), "Redo last undo")
|
||||
toolBar:AddSeparator()
|
||||
toolBar:AddTool(ID_FIND, "Find", wx.wxArtProvider.GetBitmap(wx.wxART_FIND, wx.wxART_TOOLBAR, toolBmpSize), "Find text")
|
||||
toolBar:AddTool(ID_REPLACE, "Replace", wx.wxArtProvider.GetBitmap(wx.wxART_FIND_AND_REPLACE, wx.wxART_TOOLBAR, toolBmpSize), "Find and replace text")
|
||||
toolBar:AddTool(ID_FIND, "Find", getBitmap(wx.wxART_FIND, wx.wxART_TOOLBAR, toolBmpSize), "Find text")
|
||||
toolBar:AddTool(ID_REPLACE, "Replace", getBitmap(wx.wxART_FIND_AND_REPLACE, wx.wxART_TOOLBAR, toolBmpSize), "Find and replace text")
|
||||
toolBar:AddSeparator()
|
||||
toolBar:AddTool(ID "debug.projectdir.fromfile", "Update", wx.wxArtProvider.GetBitmap(wx.wxART_GO_DIR_UP , wx.wxART_TOOLBAR, toolBmpSize), "Sets projectdir from file")
|
||||
toolBar:AddTool(ID "debug.projectdir.fromfile", "Update", getBitmap(wx.wxART_GO_DIR_UP , wx.wxART_TOOLBAR, toolBmpSize), "Sets projectdir from file")
|
||||
toolBar:AddSeparator()
|
||||
toolBar:AddControl(funclist)
|
||||
toolBar:Realize()
|
||||
|
||||
@@ -37,15 +37,13 @@ ID_FIND_IN_FILES = NewID()
|
||||
ID_REPLACE_IN_FILES = NewID()
|
||||
ID_GOTOLINE = NewID()
|
||||
ID_SORT = NewID()
|
||||
-- Debug menu
|
||||
-- Project/Debug menu
|
||||
ID_TOGGLEBREAKPOINT = NewID()
|
||||
ID_COMPILE = NewID()
|
||||
ID_RUN = NewID()
|
||||
ID_RUNNOW = NewID()
|
||||
ID_ATTACH_DEBUG = NewID()
|
||||
ID_START_DEBUG = NewID()
|
||||
--ID_USECONSOLE = NewID()
|
||||
|
||||
ID_STOP_DEBUG = NewID()
|
||||
ID_STEP = NewID()
|
||||
ID_STEP_OVER = NewID()
|
||||
@@ -53,10 +51,12 @@ ID_STEP_OUT = NewID()
|
||||
ID_CONTINUE = NewID()
|
||||
ID_BREAK = NewID()
|
||||
ID_TRACE = NewID()
|
||||
--ID_VIEWCALLSTACK = NewID()
|
||||
--ID_VIEWWATCHWINDOW = NewID()
|
||||
ID_VIEWCALLSTACK = NewID()
|
||||
ID_VIEWWATCHWINDOW = NewID()
|
||||
ID_FULLSCREEN = NewID()
|
||||
ID_CLEAROUTPUT = NewID()
|
||||
ID_DEBUGGER_PORT = NewID()
|
||||
ID_PROJECTDIR = NewID()
|
||||
ID_INTERPRETER = NewID()
|
||||
-- Help menu
|
||||
ID_ABOUT = wx.wxID_ABOUT
|
||||
-- Watch window menu items
|
||||
@@ -66,8 +66,6 @@ ID_EDITWATCH = NewID()
|
||||
ID_REMOVEWATCH = NewID()
|
||||
ID_EVALUATEWATCH = NewID()
|
||||
|
||||
ID_WORKDIR_CHOSE = NewID()
|
||||
|
||||
local ids = {}
|
||||
function ID (name)
|
||||
ids[name] = ids[name] or NewID()
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
-- Integration with LuaInspect
|
||||
-- (C) 2012 Paul Kulchenko
|
||||
|
||||
require "metalua"
|
||||
|
||||
local M = {}
|
||||
|
||||
local LA = require "luainspect.ast"
|
||||
local LI = require "luainspect.init"
|
||||
local T = require "luainspect.types"
|
||||
|
||||
local M, LA, LI, T = {}
|
||||
local FAST = true
|
||||
if FAST then
|
||||
LI.eval_comments = function () end
|
||||
LI.infer_values = function () end
|
||||
|
||||
local function init()
|
||||
if LA then return end
|
||||
|
||||
require "metalua"
|
||||
LA = require "luainspect.ast"
|
||||
LI = require "luainspect.init"
|
||||
T = require "luainspect.types"
|
||||
|
||||
if FAST then
|
||||
LI.eval_comments = function () end
|
||||
LI.infer_values = function () end
|
||||
end
|
||||
end
|
||||
|
||||
function M.warnings_from_string(src, file)
|
||||
init()
|
||||
|
||||
local ast, err, linenum, colnum = LA.ast_from_string(src, file)
|
||||
if err then return nil, err, linenum, colnum end
|
||||
|
||||
@@ -41,11 +46,15 @@ function M.show_warnings(top_ast)
|
||||
LA.walk(top_ast, function(ast)
|
||||
local line = ast.lineinfo and ast.lineinfo.first[1] or 0
|
||||
local path = ast.lineinfo and ast.lineinfo.first[4] or '?'
|
||||
local name = ast[1]
|
||||
-- check if we're masking a variable in the same scope
|
||||
if ast.localmasking and
|
||||
if ast.localmasking and name ~= '_' and
|
||||
ast.level == ast.localmasking.level then
|
||||
local linenum = ast.localmasking.lineinfo.first[1]
|
||||
warn("local variable '" .. ast[1] .. "' masks earlier declaration " ..
|
||||
local parent = ast.parent and ast.parent.parent
|
||||
local func = parent and parent.tag == 'Localrec'
|
||||
warn("local " .. (func and 'function' or 'variable') .. " '" ..
|
||||
name .. "' masks earlier declaration " ..
|
||||
(linenum and "on line " .. linenum or "in the same scope"),
|
||||
line, path)
|
||||
end
|
||||
@@ -54,40 +63,60 @@ function M.show_warnings(top_ast)
|
||||
local parent = ast.parent and ast.parent.parent
|
||||
local isparam = parent and parent.tag == 'Function'
|
||||
if isparam then
|
||||
if ast[1] ~= 'self' then
|
||||
if name ~= 'self' then
|
||||
local func = parent.parent and parent.parent.parent
|
||||
local name = type(func[1][1][1]) == 'string' and func[1][1][1]
|
||||
local assignment = not func.tag or func.tag == 'Set' or func.tag == 'Localrec'
|
||||
local fname = assignment and type(func[1][1][1]) == 'string' and func[1][1][1]
|
||||
-- "function foo(bar)" => func.tag == 'Set'
|
||||
-- "local function foo(bar)" => func.tag == 'Localrec'
|
||||
-- "local _, foo = 1, function(bar)" => func.tag == 'Local'
|
||||
-- "print(function(bar) end)" => func.tag == nil
|
||||
warn("unused parameter '" .. ast[1] .. "'" ..
|
||||
(func and (not func.tag or func.tag == 'Set' or func.tag == 'Localrec')
|
||||
and (name and func.tag
|
||||
and (" in function '" .. name .. "'")
|
||||
warn("unused parameter '" .. name .. "'" ..
|
||||
(func and assignment
|
||||
and (fname and func.tag
|
||||
and (" in function '" .. fname .. "'")
|
||||
or " in anonymous function")
|
||||
or ""),
|
||||
line, path)
|
||||
end
|
||||
else
|
||||
warn("unused local variable '" .. ast[1] ..
|
||||
"'; consider removing or replacing with '_'",
|
||||
line, path)
|
||||
if parent.tag == 'Localrec' then -- local function foo...
|
||||
warn("unused local function '" .. name .. "'", line, path)
|
||||
else
|
||||
warn("unused local variable '" .. name .. "'; "..
|
||||
"consider removing or replacing with '_'", line, path)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- added check for FAST as ast.seevalue relies on value evaluation,
|
||||
-- which is very slow even on simple and short scripts
|
||||
if not FAST and ast.isfield and not(known(ast.seevalue.value) and ast.seevalue.value ~= nil) then
|
||||
warn("unknown field " .. ast[1], ast.lineinfo.first[1], path)
|
||||
warn("unknown field " .. name, ast.lineinfo.first[1], path)
|
||||
elseif ast.tag == 'Id' and not ast.localdefinition and not ast.definedglobal then
|
||||
warn("unknown global variable '" .. ast[1] .. "'", line, path)
|
||||
if not globseen[name] then
|
||||
globseen[name] = true
|
||||
local parent = ast.parent
|
||||
-- if being called and not one of the parameters
|
||||
if parent and parent.tag == 'Call' and parent[1] == ast then
|
||||
warn("first use of unknown global function '" .. name .. "'", line, path)
|
||||
else
|
||||
warn("first use of unknown global variable '" .. name .. "'", line, path)
|
||||
end
|
||||
end
|
||||
elseif ast.tag == 'Id' and not ast.localdefinition and ast.definedglobal then
|
||||
local parent = ast.parent and ast.parent.parent
|
||||
if parent and parent.tag == 'Set' and not globseen[ast[1]] -- report assignments to global
|
||||
if parent and parent.tag == 'Set' and not globseen[name] -- report assignments to global
|
||||
-- only report if it is on the left side of the assignment
|
||||
-- this is a bit tricky as it can be assigned as part of a, b = c, d
|
||||
-- `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2...
|
||||
and parent[1] == ast.parent
|
||||
and parent[2][1].tag ~= "Function" then -- but ignore global functions
|
||||
warn("first assignment to global variable '" .. ast[1] .. "'", line, path)
|
||||
globseen[ast[1]] = true
|
||||
warn("first assignment to global variable '" .. name .. "'", line, path)
|
||||
globseen[name] = true
|
||||
end
|
||||
elseif (ast.tag == 'Set' or ast.tag == 'Local') and #(ast[2]) > #(ast[1]) then
|
||||
warn(("value discarded in multiple assignment: %d values assigned to %d variable%s")
|
||||
:format(#(ast[2]), #(ast[1]), #(ast[1]) > 1 and 's' or ''), line, path)
|
||||
end
|
||||
local vast = ast.seevalue or ast
|
||||
local note = vast.parent
|
||||
@@ -95,7 +124,7 @@ function M.show_warnings(top_ast)
|
||||
and vast.parent.note
|
||||
if note and not isseen[vast.parent] then
|
||||
isseen[vast.parent] = true
|
||||
warn("function '" .. ast[1] .. "': " .. note, line, path)
|
||||
warn("function '" .. name .. "': " .. note, line, path)
|
||||
end
|
||||
end)
|
||||
return warnings
|
||||
@@ -131,7 +160,8 @@ local function analyzeProgram(editor)
|
||||
return false
|
||||
end
|
||||
|
||||
DisplayOutput(": " .. (warn and #warn > 0 and (#warn .. " warnings") or "no warnings.") .. "\n")
|
||||
DisplayOutput((": %s warning%s.\n")
|
||||
:format(#warn > 0 and #warn or 'no', #warn == 1 and '' or 's'))
|
||||
DisplayOutputNoMarker(table.concat(warn, "\n") .. "\n")
|
||||
|
||||
return true -- analyzed ok
|
||||
|
||||
@@ -39,7 +39,7 @@ ide.iofilters["GermanUtf8Ascii"] = {
|
||||
}
|
||||
local lst = "["
|
||||
for k in pairs(charconv) do lst = lst .. k end
|
||||
lst = "]"
|
||||
lst = lst.."]"
|
||||
|
||||
return content:gsub(lst,charconv)
|
||||
end,
|
||||
|
||||
@@ -5,12 +5,13 @@ local styles = ide.config.styles
|
||||
local comment = styles.comment
|
||||
local MD_MARK_ITAL = '_' -- italic
|
||||
local MD_MARK_BOLD = '**' -- bold
|
||||
local MD_MARK_LINK = '[' -- link
|
||||
local MD_MARK_LINT = ')' -- link terminator
|
||||
local MD_MARK_LINK = '[' -- link description start
|
||||
local MD_MARK_LINZ = ']' -- link description end
|
||||
local MD_MARK_LINA = '(' -- link URL start
|
||||
local MD_MARK_LINT = ')' -- link URL end
|
||||
local MD_MARK_HEAD = '#' -- header
|
||||
local MD_MARK_CODE = '`' -- code
|
||||
local MD_MARK_BOXD = '|' -- highlight
|
||||
local MD_MARK_LSEP = '](' -- link separator (between text and link)
|
||||
local MD_MARK_MARK = ' ' -- separator
|
||||
local MD_LINK_NEWWINDOW = '+' -- indicator to open a new window for links
|
||||
local markup = {
|
||||
@@ -45,13 +46,13 @@ function MarkupHotspotClick(pos, editor)
|
||||
pos = pos + #MD_MARK_LINK - editor:PositionFromLine(line) -- turn into relative position
|
||||
|
||||
-- extract the URL/command on the right side of the separator
|
||||
local _,_,text = string.find(tx, q(MD_MARK_LSEP).."([^%s]+)"..q(MD_MARK_LINT), pos)
|
||||
local _,_,text = string.find(tx, q(MD_MARK_LINZ).."(%b"..MD_MARK_LINA..MD_MARK_LINT..")", pos)
|
||||
if text then
|
||||
text = text:gsub("^"..q(MD_MARK_LINA), ""):gsub(q(MD_MARK_LINT).."$", "")
|
||||
local filepath = ide.openDocuments[editor:GetId()].filePath
|
||||
local _,_,shell = string.find(text, [[^macro:shell%((.*%S)%)$]])
|
||||
local _,_,http = string.find(text, [[^(http:%S+)$]])
|
||||
local _,_,http = string.find(text, [[^(https?:%S+)$]])
|
||||
local _,_,command = string.find(text, [[^macro:(%w+)$]])
|
||||
local bottomnotebook = ide.frame.bottomnotebook
|
||||
if shell then
|
||||
ShellExecuteCode(shell)
|
||||
elseif command == 'run' then -- run the current file
|
||||
@@ -60,7 +61,7 @@ function MarkupHotspotClick(pos, editor)
|
||||
ProjectDebug()
|
||||
elseif http then -- open the URL in a new browser window
|
||||
wx.wxLaunchDefaultBrowser(http, 0)
|
||||
else
|
||||
elseif filepath then -- only check for saved files
|
||||
-- check if requested to open in a new window
|
||||
local newwindow = string.find(text, MD_LINK_NEWWINDOW, 1, true) -- plain search
|
||||
if newwindow then text = string.gsub(text, "^%" .. MD_LINK_NEWWINDOW, "") end
|
||||
@@ -70,7 +71,8 @@ function MarkupHotspotClick(pos, editor)
|
||||
local filename = wx.wxFileName(name)
|
||||
filename:Normalize() -- remove .., ., and other similar elements
|
||||
if filename:FileExists() and
|
||||
(newindow or SaveModifiedDialog(editor, true) ~= wx.wxID_CANCEL) then
|
||||
(newwindow or SaveModifiedDialog(editor, true) ~= wx.wxID_CANCEL) then
|
||||
if not newwindow and ide.osname == 'Macintosh' then editor:GotoPos(0) end
|
||||
LoadFile(filename,not newwindow and editor or nil,true)
|
||||
end
|
||||
end
|
||||
@@ -97,10 +99,10 @@ local function ismarkup (tx)
|
||||
-- [%w%p] set is needed to avoid continuing this markup to the next line
|
||||
s,e,cap = string.find(tx,"^("..q(MD_MARK_HEAD)..".+[%w%p])")
|
||||
elseif sep == MD_MARK_LINK then
|
||||
-- allow everything except spaces in the second part
|
||||
s,e,cap = string.find(tx,"^("..q(MD_MARK_LINK)..nonspace..".-"..nonspace
|
||||
..q(MD_MARK_LSEP).."[^%s]+"
|
||||
..q(MD_MARK_LINT)..")", st)
|
||||
-- allow everything based on balanced link separators
|
||||
s,e,cap = string.find(tx,
|
||||
"^(%b"..MD_MARK_LINK..MD_MARK_LINZ
|
||||
.."%b"..MD_MARK_LINA..MD_MARK_LINT..")", st)
|
||||
elseif markup[sep] then
|
||||
-- try 2+ characters between separators first
|
||||
-- if not found, try a single character
|
||||
@@ -145,7 +147,9 @@ function MarkupStyle(editor, lines, linee)
|
||||
if (f) then
|
||||
local p = ls+f+off
|
||||
local s = bit.band(editor:GetStyleAt(p), 31)
|
||||
if iscomment[s] then
|
||||
-- only style comments and only those that are not at the beginning
|
||||
-- of the file to avoid styling shebang (#!) lines
|
||||
if iscomment[s] and p > 0 then
|
||||
local smark = #mark
|
||||
local emark = #mark -- assumes end mark is the same length as start mark
|
||||
if mark == MD_MARK_HEAD then
|
||||
@@ -153,7 +157,7 @@ function MarkupStyle(editor, lines, linee)
|
||||
local _,_,full = string.find(w,"^("..q(MD_MARK_HEAD).."+)")
|
||||
smark,emark = #full,0
|
||||
elseif mark == MD_MARK_LINK then
|
||||
local lsep = w:find(q(MD_MARK_LSEP))
|
||||
local lsep = w:find(q(MD_MARK_LINZ)..q(MD_MARK_LINA))
|
||||
if lsep then emark = #w-lsep+#MD_MARK_LINT end
|
||||
end
|
||||
editor:StartStyling(p, 31)
|
||||
|
||||
@@ -8,5 +8,3 @@ dofile "src/editor/menu_search.lua"
|
||||
dofile "src/editor/menu_view.lua"
|
||||
dofile "src/editor/menu_project.lua"
|
||||
dofile "src/editor/menu_tools.lua"
|
||||
|
||||
ide.frame:SetMenuBar(ide.frame.menuBar )
|
||||
|
||||
@@ -17,11 +17,11 @@ local editMenu = wx.wxMenu{
|
||||
{ ID_UNDO, "&Undo\tCtrl-Z", "Undo the last action" },
|
||||
{ ID_REDO, "&Redo\tCtrl-Y", "Redo the last action undone" },
|
||||
{ },
|
||||
{ ID "edit.showtooltip", "Show &Tooltip\tCtrl+T", "Show tooltip for current position. Place cursor after opening bracket of function."},
|
||||
{ ID_AUTOCOMPLETE, "Complete &Identifier\tCtrl+K", "Complete the current identifier" },
|
||||
{ ID "edit.showtooltip", "Show &Tooltip\tCtrl-T", "Show tooltip for current position. Place cursor after opening bracket of function."},
|
||||
{ ID_AUTOCOMPLETE, "Complete &Identifier\tCtrl-K", "Complete the current identifier" },
|
||||
{ ID_AUTOCOMPLETE_ENABLE, "Auto complete Identifiers", "Auto complete while typing", wx.wxITEM_CHECK },
|
||||
{ },
|
||||
{ ID_COMMENT, "C&omment/Uncomment\tCtrl-Q", "Comment or uncomment current or selected lines"},
|
||||
{ ID_COMMENT, "C&omment/Uncomment\tCtrl-U", "Comment or uncomment current or selected lines"},
|
||||
{ },
|
||||
{ ID_FOLD, "&Fold/Unfold all\tF12", "Fold or unfold all code folds"},
|
||||
{ ID "edit.cleardynamics", "Clear &Dynamic Words", "Resets the dynamic word list for autcompletion."},
|
||||
@@ -35,13 +35,13 @@ function OnUpdateUIEditMenu(event) -- enable if there is a valid focused editor
|
||||
event:Enable(editor ~= nil)
|
||||
end
|
||||
|
||||
local shellboxeditor = ide.frame.bottomnotebook.shellbox
|
||||
local othereditors = { frame.bottomnotebook.shellbox, frame.bottomnotebook.errorlog }
|
||||
|
||||
function OnEditMenu(event)
|
||||
local menu_id = event:GetId()
|
||||
local editor = GetEditor()
|
||||
if shellboxeditor:FindFocus():GetId() == shellboxeditor:GetId() then
|
||||
editor = shellboxeditor
|
||||
for _,e in pairs(othereditors) do
|
||||
if e:FindFocus():GetId() == e:GetId() then editor = e end
|
||||
end
|
||||
if editor == nil then return end
|
||||
|
||||
@@ -93,22 +93,38 @@ frame:Connect(ID "edit.cleardynamics", wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
frame:Connect(ID "edit.showtooltip", wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
function (event)
|
||||
local editor = GetEditor()
|
||||
|
||||
if (editor:CallTipActive()) then
|
||||
editor:CallTipCancel()
|
||||
return
|
||||
end
|
||||
|
||||
local pos = editor:GetCurrentPos()
|
||||
local line = editor:GetCurrentLine()
|
||||
local linetx = editor:GetLine(line)
|
||||
local linestart = editor:PositionFromLine(line)
|
||||
local localpos = pos-linestart
|
||||
|
||||
local ident = "([a-zA-Z_0-9][a-zA-Z_0-9%.%:]*)"
|
||||
local linetxtopos = linetx:sub(1,localpos)
|
||||
linetxtopos = linetxtopos..")"
|
||||
linetxtopos = linetxtopos:match("([a-zA-Z_0-9%.%:]+)%b()$")
|
||||
linetxtopos = linetxtopos:match(ident .. "%b()$")
|
||||
|
||||
local tip = linetxtopos and GetTipInfo(editor,linetxtopos.."(",false)
|
||||
if tip then
|
||||
if(editor:CallTipActive()) then
|
||||
editor:CallTipCancel()
|
||||
editor:CallTipShow(pos, tip)
|
||||
else
|
||||
-- check if we have a selected text or an identifier
|
||||
-- for an identifier, check fragments on the left and on the right.
|
||||
-- this is to match 'io' in 'i^o.print' and 'io.print' in 'io.pr^int'
|
||||
local left = linetx:sub(1,localpos):match(ident.."$")
|
||||
local right = linetx:sub(localpos+1,#linetx):match("^[a-zA-Z_0-9]*")
|
||||
local var = editor:GetSelectionStart() ~= editor:GetSelectionEnd()
|
||||
and editor:GetSelectedText()
|
||||
or left and left..right or nil
|
||||
if var and ide.debugger then
|
||||
ide.debugger.quickeval(var, function(val) editor:CallTipShow(pos, val) end)
|
||||
end
|
||||
editor:CallTipShow(pos,tip)
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
@@ -7,20 +7,19 @@ local ide = ide
|
||||
local frame = ide.frame
|
||||
local menuBar = frame.menuBar
|
||||
local openDocuments = ide.openDocuments
|
||||
local debugger = ide.debugger
|
||||
|
||||
local fileMenu = wx.wxMenu({
|
||||
{ ID_NEW, "&New\tCtrl-N", "Create an empty document" },
|
||||
{ ID_OPEN, "&Open...\tCtrl-O", "Open an existing document" },
|
||||
{ ID_CLOSE, "&Close page\tCtrl+W", "Close the current editor window" },
|
||||
{ ID_CLOSE, "&Close page\tCtrl-W", "Close the current editor window" },
|
||||
{ },
|
||||
{ ID_SAVE, "&Save\tCtrl-S", "Save the current document" },
|
||||
{ ID_SAVEAS, "Save &As...\tAlt-Shift-S", "Save the current document to a file with a new name" },
|
||||
{ ID_SAVEALL, "Save A&ll...\tCtrl-Shift-S", "Save all open documents" },
|
||||
{ ID_SAVEALL, "Save A&ll...", "Save all open documents" },
|
||||
{ },
|
||||
--{ ID "file.recentfiles", "Recent files",},
|
||||
{ },
|
||||
{ ID_EXIT, "E&xit\tAlt-X", "Exit Program" }})
|
||||
{ ID_EXIT, "E&xit", "Exit Program" }})
|
||||
menuBar:Append(fileMenu, "&File")
|
||||
|
||||
local filehistorymenu = wx.wxMenu({})
|
||||
|
||||
@@ -46,17 +46,16 @@ local debugTab = {
|
||||
{ ID_RUNNOW, "Run as Scratchpad\tCtrl-F6", "Execute the current project/file and keep updating the code to see immediate results", wx.wxITEM_CHECK },
|
||||
{ ID_COMPILE, "&Compile\tF7", "Test compile the Lua file" },
|
||||
{ ID_START_DEBUG, "Start &Debugging\tF5", "Start a debugging session" },
|
||||
{ ID_ATTACH_DEBUG, "&Start Debugger Server\tShift-F6", "Allow a client to start a debugging session" },
|
||||
{ ID_ATTACH_DEBUG, "&Start Debugger Server", "Allow a client to start a debugging session" },
|
||||
{ },
|
||||
{ ID_STOP_DEBUG, "S&top Debugging\tShift-F12", "Stop the currently running process" },
|
||||
{ ID_STEP, "St&ep\tF11", "Step into the next line" },
|
||||
{ ID_STEP_OVER, "Step &Over\tF10", "Step over the next line" },
|
||||
{ ID_STEP_OUT, "Step O&ut\tShift-F10", "Step out of the current function" },
|
||||
{ ID_TRACE, "Tr&ace", "Trace execution showing each executed line" },
|
||||
{ ID_BREAK, "&Break", "Stop execution of the program at the next executed line of code" },
|
||||
{ ID_BREAK, "&Break\tShift-F9", "Stop execution of the program at the next executed line of code" },
|
||||
{ },
|
||||
{ ID_TOGGLEBREAKPOINT, "Toggle &Breakpoint\tF9", "Toggle Breakpoint" },
|
||||
--{ ID "view.debug.callstack", "V&iew Call Stack", "View the call stack" },
|
||||
{ ID_TOGGLEBREAKPOINT, "Toggle Break&point\tF9", "Toggle Breakpoint" },
|
||||
{ },
|
||||
{ ID_CLEAROUTPUT, "C&lear Output Window", "Clear the output window before compiling or debugging", wx.wxITEM_CHECK },
|
||||
}
|
||||
@@ -72,8 +71,8 @@ local targetDirMenu = wx.wxMenu{
|
||||
{ID "debug.projectdir.currentdir",""}
|
||||
}
|
||||
|
||||
debugMenu:Append(0,"Lua &interpreter",targetMenu,"Set the interpreter to be used")
|
||||
debugMenu:Append(0,"Project directory",targetDirMenu,"Set the project directory to be used")
|
||||
debugMenu:Append(ID_INTERPRETER,"Lua &Interpreter",targetMenu,"Set the interpreter to be used")
|
||||
debugMenu:Append(ID_PROJECTDIR,"Project Directory",targetDirMenu,"Set the project directory to be used")
|
||||
menuBar:Append(debugMenu, "&Project")
|
||||
|
||||
-----------------------------
|
||||
@@ -221,7 +220,7 @@ function ProjectDebug(skipcheck, debtype)
|
||||
end
|
||||
else
|
||||
local debcall = (debuggers[debtype or "debug"]):
|
||||
format(wx.wxGetHostName(), ide.debugger.portnumber)
|
||||
format(ide.debugger.hostname, ide.debugger.portnumber)
|
||||
local fname = getNameToRun(skipcheck)
|
||||
if not fname then return end
|
||||
runInterpreter(fname, debcall)
|
||||
@@ -274,9 +273,10 @@ frame:Connect(ID_RUNNOW, wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
frame:Connect(ID_RUNNOW, wx.wxEVT_UPDATE_UI,
|
||||
function (event)
|
||||
local editor = GetEditor()
|
||||
-- allow scratchpad if there is no server or there is a server, but it is
|
||||
-- allowed to turn it into a scratchpad and we are not debugging anything
|
||||
event:Enable((editor ~= nil) and ((debugger.server == nil or debugger.scratchable)
|
||||
-- allow scratchpad if there is no server or (there is a server and it is
|
||||
-- allowed to turn it into a scratchpad) and we are not debugging anything
|
||||
event:Enable((ide.interpreter) and (ide.interpreter.hasdebugger) and
|
||||
(editor ~= nil) and ((debugger.server == nil or debugger.scratchable)
|
||||
and debugger.pid == nil or debugger.scratchpad ~= nil))
|
||||
end)
|
||||
|
||||
@@ -311,8 +311,7 @@ frame:Connect(ID_START_DEBUG, wx.wxEVT_UPDATE_UI,
|
||||
frame:Connect(ID_STOP_DEBUG, wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
function (event)
|
||||
ClearAllCurrentLineMarkers()
|
||||
if debugger.server then debugger.terminate() end
|
||||
if debugger.pid then DebuggerKillClient() end
|
||||
DebuggerShutdown()
|
||||
end)
|
||||
frame:Connect(ID_STOP_DEBUG, wx.wxEVT_UPDATE_UI,
|
||||
function (event)
|
||||
@@ -386,19 +385,6 @@ frame:Connect(ID_BREAK, wx.wxEVT_UPDATE_UI,
|
||||
and (editor ~= nil) and (not debugger.scratchpad))
|
||||
end)
|
||||
|
||||
--[[
|
||||
frame:Connect(ID "view.debug.callstack", wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
function (event)
|
||||
if debugger.server then
|
||||
DebuggerCreateStackWindow()
|
||||
end
|
||||
end)
|
||||
frame:Connect(ID "view.debug.callstack", wx.wxEVT_UPDATE_UI,
|
||||
function (event)
|
||||
event:Enable((debugger.server ~= nil) and (not debugger.running))
|
||||
end)
|
||||
]]
|
||||
|
||||
frame:Connect(wx.wxEVT_IDLE,
|
||||
function(event)
|
||||
if (debugger.update) then debugger.update() end
|
||||
|
||||
@@ -13,10 +13,10 @@ local findMenu = wx.wxMenu{
|
||||
{ ID_FIND, "&Find\tCtrl-F", "Find the specified text" },
|
||||
{ ID_FINDNEXT, "Find &Next\tF3", "Find the next occurrence of the specified text" },
|
||||
{ ID_FINDPREV, "Find &Previous\tShift-F3", "Repeat the search backwards in the file" },
|
||||
{ ID_REPLACE, "&Replace\tCtrl-H", "Replaces the specified text with different text" },
|
||||
{ ID_REPLACE, "&Replace\tCtrl-R", "Replaces the specified text with different text" },
|
||||
{ },
|
||||
{ ID_FIND_IN_FILES, "Find &In Files\tCtrl-Shift-F", " Find specified text in files"},
|
||||
{ ID_REPLACE_IN_FILES, "Re&place In Files\tCtrl-Shift-H", " Replace specified text in files"},
|
||||
{ ID_REPLACE_IN_FILES, "Re&place In Files\tCtrl-Shift-R", " Replace specified text in files"},
|
||||
{ },
|
||||
{ ID_GOTOLINE, "&Goto line\tCtrl-G", "Go to a selected line" },
|
||||
{ },
|
||||
|
||||
@@ -6,17 +6,16 @@ local frame = ide.frame
|
||||
local menuBar = frame.menuBar
|
||||
local uimgr = frame.uimgr
|
||||
|
||||
local debugger = ide.debugger
|
||||
|
||||
local viewMenu = wx.wxMenu{
|
||||
local viewMenu = wx.wxMenu {
|
||||
-- NYI { ID "view.preferences", "&Preferences...", "Brings up dialog for settings (TODO)" },
|
||||
-- NYI { },
|
||||
{ ID "view.filetree.show", "Project/&FileTree Window\tCtrl-Alt-P", "View the project/filetree window" },
|
||||
{ ID "view.output.show", "&Output/Shell Window\tCtrl-Alt-O", "View the output/shell window" },
|
||||
{ ID "view.debug.watches", "&Watch Window", "View the Watch window" },
|
||||
{ ID "view.filetree.show", "Project/&FileTree Window\tCtrl-Shift-P", "View the project/filetree window" },
|
||||
{ ID "view.output.show", "&Output/Console Window\tCtrl-Shift-O", "View the output/console window" },
|
||||
{ ID_VIEWWATCHWINDOW, "&Watch Window\tCtrl-Shift-W", "View the Watch window" },
|
||||
{ ID_VIEWCALLSTACK, "&Stack Window\tCtrl-Shift-S", "View the Stack window" },
|
||||
{ },
|
||||
{ ID "view.defaultlayout", "&Default Layout", "Reset to default layout"},
|
||||
{ ID "view.fullscreen", "Full &Screen\tCtrl-Alt-F", "Switch to or from full screen mode"},
|
||||
{ ID_FULLSCREEN, "Full &Screen\tCtrl-Shift-A", "Switch to or from full screen mode"},
|
||||
{ ID "view.style.loadconfig", "&Load Config Style...", "Load and apply style from config file (must contain .styles)"},
|
||||
}
|
||||
menuBar:Append(viewMenu, "&View")
|
||||
@@ -46,12 +45,12 @@ frame:Connect(ID "view.filetree.show", wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
uimgr:Update()
|
||||
end)
|
||||
|
||||
frame:Connect(ID "view.fullscreen", wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
function (event) ShowFullScreen(not frame:IsFullScreen()) end)
|
||||
|
||||
frame:Connect(ID "view.debug.watches", wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
function (event)
|
||||
if not debugger.watchWindow then
|
||||
DebuggerCreateWatchWindow()
|
||||
end
|
||||
frame:Connect(ID_FULLSCREEN, wx.wxEVT_COMMAND_MENU_SELECTED, function ()
|
||||
pcall(function() ShowFullScreen(not frame:IsFullScreen()) end)
|
||||
end)
|
||||
|
||||
frame:Connect(ID_VIEWWATCHWINDOW, wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
function (event) DebuggerCreateWatchWindow() end)
|
||||
|
||||
frame:Connect(ID_VIEWCALLSTACK, wx.wxEVT_COMMAND_MENU_SELECTED,
|
||||
function (event) DebuggerCreateStackWindow() end)
|
||||
|
||||
@@ -9,15 +9,20 @@ local errorlog = bottomnotebook.errorlog
|
||||
|
||||
-------
|
||||
-- setup errorlog
|
||||
local INPUT_MARKER = 3
|
||||
local INPUT_MARKER_VALUE = 2^INPUT_MARKER
|
||||
|
||||
errorlog:Show(true)
|
||||
errorlog:SetFont(ide.ofont)
|
||||
errorlog:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.ofont)
|
||||
errorlog:SetFont(ide.font.oNormal)
|
||||
errorlog:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.font.oNormal)
|
||||
errorlog:StyleClearAll()
|
||||
errorlog:SetMarginWidth(1, 16) -- marker margin
|
||||
errorlog:SetMarginType(1, wxstc.wxSTC_MARGIN_SYMBOL);
|
||||
errorlog:MarkerDefine(CURRENT_LINE_MARKER, wxstc.wxSTC_MARK_ARROWS, wx.wxBLACK, wx.wxWHITE)
|
||||
errorlog:MarkerDefine(INPUT_MARKER, wxstc.wxSTC_MARK_CHARACTER+string.byte('>'),
|
||||
wx.wxColour(127, 127, 127), wx.wxColour(240, 240, 240))
|
||||
errorlog:SetReadOnly(true)
|
||||
StylesApplyToEditor(ide.config.stylesoutshell,errorlog,ide.ofont,ide.ofontItalic)
|
||||
StylesApplyToEditor(ide.config.stylesoutshell,errorlog,ide.font.oNormal,ide.font.oItalic)
|
||||
|
||||
function ClearOutput()
|
||||
errorlog:SetReadOnly(false)
|
||||
@@ -33,9 +38,11 @@ function DisplayOutputNoMarker(...)
|
||||
message = message..tostring(v)..(i<cnt and "\t" or "")
|
||||
end
|
||||
|
||||
local current = errorlog:GetReadOnly()
|
||||
errorlog:SetReadOnly(false)
|
||||
errorlog:AppendText(message)
|
||||
errorlog:SetReadOnly(true)
|
||||
errorlog:EmptyUndoBuffer()
|
||||
errorlog:SetReadOnly(current)
|
||||
errorlog:GotoPos(errorlog:GetLength())
|
||||
end
|
||||
function DisplayOutput(...)
|
||||
@@ -45,7 +52,9 @@ end
|
||||
|
||||
local streamins = {}
|
||||
local streamerrs = {}
|
||||
local streamouts = {}
|
||||
local customprocs = {}
|
||||
local textout = '' -- this is a buffer for any text sent to external scripts
|
||||
|
||||
function CommandLineRunning(uid)
|
||||
for pid,custom in pairs(customprocs) do
|
||||
@@ -89,32 +98,33 @@ local function unHideWxWindow(pidAssign)
|
||||
end
|
||||
end
|
||||
|
||||
local function nameTab(tab, name)
|
||||
local index = bottomnotebook:GetPageIndex(tab)
|
||||
if index then bottomnotebook:SetPageText(index, name) end
|
||||
end
|
||||
|
||||
function CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback)
|
||||
if (not cmd) then return end
|
||||
|
||||
-- try to extract the name of the executable from the command
|
||||
-- the executable may not have the extension and may be in quotes
|
||||
local exename = string.gsub(cmd, "\\", "/")
|
||||
exename = string.match(exename,'%/*([^%/]+%.%w+)') or exename
|
||||
exename = string.match(exename,'%/*([^%/]+%.%w+)[%s%"]') or exename
|
||||
local _,_,fullname = string.find(exename,'^[\'"]([^\'"]+)[\'"]')
|
||||
exename = fullname and string.match(fullname,'/?([^/]+)$')
|
||||
or string.match(exename,'/?([^/]-)%s') or exename
|
||||
|
||||
uid = uid or exename
|
||||
|
||||
if (CommandLineRunning(uid)) then
|
||||
DisplayOutput("Conflicting Process still running: "..cmd.."\n")
|
||||
DisplayOutput(("Program can't start because conflicting process is running as '%s'.\n")
|
||||
:format(cmd))
|
||||
return
|
||||
end
|
||||
|
||||
DisplayOutput("Running program: "..cmd.."\n")
|
||||
DisplayOutput(("Program starting as '%s'.\n"):format(cmd))
|
||||
|
||||
local pid = -1
|
||||
local proc = nil
|
||||
local customproc
|
||||
|
||||
if (tooutput) then
|
||||
customproc = wx.wxProcess(errorlog)
|
||||
customproc:Redirect()
|
||||
|
||||
proc = customproc
|
||||
end
|
||||
local proc = wx.wxProcess(errorlog)
|
||||
if (tooutput) then proc:Redirect() end -- redirect the output if requested
|
||||
|
||||
-- manipulate working directory
|
||||
local oldcwd
|
||||
@@ -124,42 +134,64 @@ function CommandLineRun(cmd,wdir,tooutput,nohide,stringcallback,uid,endcallback)
|
||||
end
|
||||
|
||||
-- launch process
|
||||
local pid = (proc and wx.wxExecute(cmd, wx.wxEXEC_ASYNC + (nohide and wx.wxEXEC_NOHIDE or 0),proc) or
|
||||
wx.wxExecute(cmd, wx.wxEXEC_ASYNC + (nohide and wx.wxEXEC_NOHIDE or 0)))
|
||||
local params = wx.wxEXEC_ASYNC + wx.wxEXEC_MAKE_GROUP_LEADER + (nohide and wx.wxEXEC_NOHIDE or 0)
|
||||
local pid = wx.wxExecute(cmd, params, proc)
|
||||
|
||||
if (oldcwd) then
|
||||
wx.wxFileName.SetCwd(oldcwd)
|
||||
end
|
||||
|
||||
-- check process
|
||||
if not pid or pid == -1 then
|
||||
DisplayOutputNoMarker("Unknown ERROR Running program!\n")
|
||||
customproc = nil
|
||||
-- For asynchronous execution, the return value is the process id and
|
||||
-- zero value indicates that the command could not be executed.
|
||||
-- The return value of -1 in this case indicates that we didn't launch
|
||||
-- a new process, but connected to the running one (e.g. DDE under Windows).
|
||||
if not pid or pid == -1 or pid == 0 then
|
||||
DisplayOutput(("Program unable to run as '%s'\n"):format(cmd))
|
||||
return
|
||||
else
|
||||
DisplayOutput(
|
||||
"Process: "..uid..", pid:"..tostring(pid)..
|
||||
", started in '"..(wdir and wdir or wx.wxFileName.GetCwd()).."'\n")
|
||||
customprocs[pid] = {proc=customproc, uid=uid, endcallback=endcallback}
|
||||
end
|
||||
|
||||
DisplayOutput(("Program '%s' started in '%s' (pid: %d).\n")
|
||||
:format(uid, (wdir and wdir or wx.wxFileName.GetCwd()), pid))
|
||||
customprocs[pid] = {proc=proc, uid=uid, endcallback=endcallback, started = os.clock()}
|
||||
|
||||
local streamin = proc and proc:GetInputStream()
|
||||
local streamerr = proc and proc:GetErrorStream()
|
||||
local streamout = proc and proc:GetOutputStream()
|
||||
if (streamin) then
|
||||
streamins[pid] = {stream=streamin, callback=stringcallback}
|
||||
end
|
||||
if (streamerr) then
|
||||
streamerrs[pid] = {stream=streamerr, callback=stringcallback}
|
||||
end
|
||||
if (streamout) then
|
||||
streamouts[pid] = {stream=streamout, callback=stringcallback, out=true}
|
||||
end
|
||||
|
||||
unHideWxWindow(pid)
|
||||
nameTab(errorlog, "Output (running)")
|
||||
|
||||
return pid
|
||||
end
|
||||
|
||||
local inputBound -- to track where partial output ends for input editing purposes
|
||||
local function getInputLine()
|
||||
local totalLines = errorlog:GetLineCount()
|
||||
return errorlog:MarkerPrevious(totalLines+1, INPUT_MARKER_VALUE)
|
||||
end
|
||||
local function getInputText(bound)
|
||||
return errorlog:GetTextRange(
|
||||
errorlog:PositionFromLine(getInputLine())+(bound or 0), errorlog:GetLength())
|
||||
end
|
||||
local function updateInputMarker()
|
||||
local lastline = errorlog:GetLineCount()-1
|
||||
errorlog:MarkerDeleteAll(INPUT_MARKER)
|
||||
errorlog:MarkerAdd(lastline, INPUT_MARKER)
|
||||
inputBound = #getInputText()
|
||||
end
|
||||
|
||||
local function getStreams()
|
||||
local function displayStream(tab)
|
||||
for i,v in pairs(tab) do
|
||||
local function readStream(tab)
|
||||
for _,v in pairs(tab) do
|
||||
while(v.stream:CanRead()) do
|
||||
local str = v.stream:Read(4096)
|
||||
local pfn
|
||||
@@ -171,35 +203,66 @@ local function getStreams()
|
||||
else
|
||||
DisplayOutputNoMarker(str)
|
||||
end
|
||||
if str and ide.config.allowinteractivescript and
|
||||
(getInputLine() > -1 or errorlog:GetReadOnly()) then
|
||||
ActivateOutput()
|
||||
updateInputMarker()
|
||||
end
|
||||
pfn = pfn and pfn()
|
||||
end
|
||||
end
|
||||
end
|
||||
local function sendStream(tab)
|
||||
local str = textout
|
||||
if not str then return end
|
||||
textout = nil
|
||||
str = str .. "\n"
|
||||
for _,v in pairs(tab) do
|
||||
local pfn
|
||||
if (v.callback) then
|
||||
str,pfn = v.callback(str)
|
||||
end
|
||||
v.stream:Write(str, #str)
|
||||
updateInputMarker()
|
||||
pfn = pfn and pfn()
|
||||
end
|
||||
end
|
||||
|
||||
displayStream(streamins)
|
||||
displayStream(streamerrs)
|
||||
readStream(streamins)
|
||||
readStream(streamerrs)
|
||||
sendStream(streamouts)
|
||||
end
|
||||
|
||||
errorlog:Connect(wx.wxEVT_END_PROCESS, function(event)
|
||||
local pid = event:GetPid()
|
||||
if (pid ~= -1) then
|
||||
getStreams()
|
||||
-- delete markers and set focus to the editor if there is an input marker
|
||||
if errorlog:MarkerPrevious(errorlog:GetLineCount(), INPUT_MARKER_VALUE) > -1 then
|
||||
errorlog:MarkerDeleteAll(INPUT_MARKER)
|
||||
local editor = GetEditor()
|
||||
-- check if editor still exists; it may not if the window is closed
|
||||
if editor then editor:SetFocus() end
|
||||
end
|
||||
nameTab(errorlog, "Output")
|
||||
local runtime = os.clock() - customprocs[pid].started
|
||||
|
||||
streamins[pid] = nil
|
||||
streamerrs[pid] = nil
|
||||
streamouts[pid] = nil
|
||||
if (customprocs[pid].endcallback) then
|
||||
customprocs[pid].endcallback()
|
||||
end
|
||||
customprocs[pid] = nil
|
||||
unHideWxWindow(0)
|
||||
DebuggerStop()
|
||||
DisplayOutput("Program finished (pid: "..pid..").\n")
|
||||
DisplayOutput(("Program completed in %.2f seconds (pid: %d).\n")
|
||||
:format(runtime, pid))
|
||||
end
|
||||
end)
|
||||
|
||||
errorlog:Connect(wx.wxEVT_IDLE, function(event)
|
||||
if (#streamins or #streamerrs) then
|
||||
getStreams()
|
||||
end
|
||||
errorlog:Connect(wx.wxEVT_IDLE, function()
|
||||
if (#streamins or #streamerrs) then getStreams() end
|
||||
unHideWxWindow()
|
||||
end)
|
||||
|
||||
@@ -215,7 +278,7 @@ local jumptopatterns = {
|
||||
}
|
||||
|
||||
errorlog:Connect(wxstc.wxEVT_STC_DOUBLECLICK,
|
||||
function(event)
|
||||
function()
|
||||
local line = errorlog:GetCurrentLine()
|
||||
local linetx = errorlog:GetLine(line)
|
||||
-- try to detect a filename + line
|
||||
@@ -225,7 +288,7 @@ errorlog:Connect(wxstc.wxEVT_STC_DOUBLECLICK,
|
||||
local jumpline
|
||||
local jumplinepos
|
||||
|
||||
for i,pattern in ipairs(jumptopatterns) do
|
||||
for _,pattern in ipairs(jumptopatterns) do
|
||||
fname,jumpline,jumplinepos = linetx:match(pattern)
|
||||
if (fname and jumpline) then
|
||||
break
|
||||
@@ -244,5 +307,92 @@ errorlog:Connect(wxstc.wxEVT_STC_DOUBLECLICK,
|
||||
editor:SetFocus()
|
||||
end
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
local function positionInLine(line)
|
||||
return errorlog:GetCurrentPos() - errorlog:PositionFromLine(line)
|
||||
end
|
||||
local function caretOnInputLine(disallowLeftmost)
|
||||
local inputLine = getInputLine()
|
||||
local boundary = inputBound + (disallowLeftmost and 0 or -1)
|
||||
return (errorlog:GetCurrentLine() > inputLine
|
||||
or errorlog:GetCurrentLine() == inputLine
|
||||
and positionInLine(inputLine) > boundary)
|
||||
end
|
||||
|
||||
errorlog:Connect(wx.wxEVT_KEY_DOWN,
|
||||
function (event)
|
||||
-- this loop is only needed to allow to get to the end of function easily
|
||||
-- "return" aborts the processing and ignores the key
|
||||
-- "break" aborts the processing and processes the key normally
|
||||
while true do
|
||||
-- no special processing if it's readonly
|
||||
if errorlog:GetReadOnly() then break end
|
||||
|
||||
local key = event:GetKeyCode()
|
||||
if key == wx.WXK_UP or key == wx.WXK_NUMPAD_UP then
|
||||
if errorlog:GetCurrentLine() > getInputLine() then break
|
||||
else return end
|
||||
elseif key == wx.WXK_DOWN or key == wx.WXK_NUMPAD_DOWN then
|
||||
break -- can go down
|
||||
elseif key == wx.WXK_LEFT or key == wx.WXK_NUMPAD_LEFT then
|
||||
if not caretOnInputLine(true) then return end
|
||||
elseif key == wx.WXK_BACK then
|
||||
if not caretOnInputLine(true) then return end
|
||||
elseif key == wx.WXK_DELETE or key == wx.WXK_NUMPAD_DELETE then
|
||||
if not caretOnInputLine()
|
||||
or errorlog:LineFromPosition(errorlog:GetSelectionStart()) < getInputLine() then
|
||||
return
|
||||
end
|
||||
elseif key == wx.WXK_PAGEUP or key == wx.WXK_NUMPAD_PAGEUP
|
||||
or key == wx.WXK_PAGEDOWN or key == wx.WXK_NUMPAD_PAGEDOWN
|
||||
or key == wx.WXK_END or key == wx.WXK_NUMPAD_END
|
||||
or key == wx.WXK_HOME or key == wx.WXK_NUMPAD_HOME
|
||||
or key == wx.WXK_RIGHT or key == wx.WXK_NUMPAD_RIGHT
|
||||
or key == wx.WXK_SHIFT or key == wx.WXK_CONTROL
|
||||
or key == wx.WXK_ALT then
|
||||
break
|
||||
elseif key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER then
|
||||
if not caretOnInputLine()
|
||||
or errorlog:LineFromPosition(errorlog:GetSelectionStart()) < getInputLine() then
|
||||
return
|
||||
end
|
||||
errorlog:GotoPos(errorlog:GetLength()) -- move to the end
|
||||
textout = (textout or '') .. getInputText(inputBound)
|
||||
-- remove selection if any, otherwise the text gets replaced
|
||||
errorlog:SetSelection(errorlog:GetSelectionEnd()+1,errorlog:GetSelectionEnd())
|
||||
break -- don't need to do anything else with return
|
||||
else
|
||||
-- move cursor to end if not already there
|
||||
if not caretOnInputLine() then
|
||||
errorlog:GotoPos(errorlog:GetLength())
|
||||
-- check if the selection starts before the input line and reset it
|
||||
elseif errorlog:LineFromPosition(errorlog:GetSelectionStart()) < getInputLine(-1) then
|
||||
errorlog:GotoPos(errorlog:GetLength())
|
||||
errorlog:SetSelection(errorlog:GetSelectionEnd()+1,errorlog:GetSelectionEnd())
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
event:Skip()
|
||||
end)
|
||||
|
||||
local function inputEditable(line)
|
||||
local inputLine = getInputLine()
|
||||
local currentLine = line or errorlog:GetCurrentLine()
|
||||
return inputLine > -1 and
|
||||
(currentLine > inputLine or
|
||||
currentLine == inputLine and positionInLine(inputLine) >= inputBound) and
|
||||
not (errorlog:LineFromPosition(errorlog:GetSelectionStart()) < getInputLine())
|
||||
end
|
||||
|
||||
errorlog:Connect(wxstc.wxEVT_STC_UPDATEUI,
|
||||
function () errorlog:SetReadOnly(not inputEditable()) end)
|
||||
|
||||
-- only allow copy/move text by dropping to the input line
|
||||
errorlog:Connect(wxstc.wxEVT_STC_DO_DROP,
|
||||
function (event)
|
||||
if not inputEditable(errorlog:LineFromPosition(event:GetPosition())) then
|
||||
event:SetDragResult(wx.wxDragNone)
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -233,7 +233,6 @@ end
|
||||
|
||||
-----------------------------------
|
||||
|
||||
|
||||
local function saveNotebook(nb)
|
||||
local cnt = nb:GetPageCount()
|
||||
|
||||
@@ -250,7 +249,6 @@ local function saveNotebook(nb)
|
||||
|
||||
for i=1,cnt do
|
||||
local id = nb:GetPageText(i-1)
|
||||
|
||||
local pg = nb:GetPage(i-1)
|
||||
local x,y = pg:GetPosition():GetXY()
|
||||
addTo(pagesX,x,id)
|
||||
@@ -303,14 +301,14 @@ local function loadNotebook(nb,str,fnIdConvert)
|
||||
if (not str) then return end
|
||||
local cnt = nb:GetPageCount()
|
||||
local sel = nb:GetSelection()
|
||||
|
||||
|
||||
-- store old pages
|
||||
local currentpages = {}
|
||||
for i=1,cnt do
|
||||
local id = nb:GetPageText(i-1)
|
||||
local newid = fnIdConvert and fnIdConvert(id) or id
|
||||
currentpages[newid] = {page = nb:GetPage(i-1), text = id, index = i-1}
|
||||
currentpages[newid] = currentpages[newid] or {}
|
||||
table.insert(currentpages[newid], {page = nb:GetPage(i-1), text = id, index = i-1})
|
||||
end
|
||||
|
||||
-- remove them
|
||||
@@ -318,7 +316,7 @@ local function loadNotebook(nb,str,fnIdConvert)
|
||||
nb:RemovePage(i-1)
|
||||
end
|
||||
|
||||
-- readd them and perform splits
|
||||
-- read them and perform splits
|
||||
local direction
|
||||
local splits = {
|
||||
X = wx.wxRIGHT,
|
||||
@@ -337,13 +335,13 @@ local function loadNotebook(nb,str,fnIdConvert)
|
||||
local instr = cmd:match("<(%w)>")
|
||||
if (not instr) then
|
||||
local id = fnIdConvert and fnIdConvert(cmd) or cmd
|
||||
local page = currentpages[id]
|
||||
if (page) then
|
||||
local pageind = next(currentpages[id] or {})
|
||||
if (pageind) then
|
||||
local page = currentpages[id][pageind]
|
||||
currentpages[id][pageind] = nil
|
||||
|
||||
nb:AddPage(page.page, page.text)
|
||||
currentpages[id] = nil
|
||||
if (direction) then
|
||||
nb:Split(t, direction)
|
||||
end
|
||||
if (direction) then nb:Split(t, direction) end
|
||||
finishPage(page)
|
||||
end
|
||||
end
|
||||
@@ -351,18 +349,18 @@ local function loadNotebook(nb,str,fnIdConvert)
|
||||
end
|
||||
|
||||
-- add anything we forgot
|
||||
for i,page in pairs(currentpages) do
|
||||
nb:AddPage(page.page, page.text)
|
||||
finishPage(page)
|
||||
for _,pagelist in pairs(currentpages) do
|
||||
for _,page in pairs(pagelist) do
|
||||
nb:AddPage(page.page, page.text)
|
||||
finishPage(page)
|
||||
end
|
||||
end
|
||||
|
||||
if (newsel) then
|
||||
nb:SetSelection(newsel)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
function SettingsRestoreView()
|
||||
local listname = "/view"
|
||||
local path = settings:GetPath()
|
||||
@@ -394,7 +392,7 @@ function SettingsRestoreView()
|
||||
local layout = settingsReadSafe(settings,"nbbtmlayout",layoutcur)
|
||||
if (layout ~= layoutcur) then
|
||||
loadNotebook(ide.frame.bottomnotebook,layout,
|
||||
function(name) return name:match("console") or name end)
|
||||
function(name) return name:match("Output") or name end)
|
||||
end
|
||||
|
||||
settings:SetPath(path)
|
||||
@@ -424,6 +422,7 @@ function SettingsRestoreEditorSettings()
|
||||
ide.config.interpreter = settingsReadSafe(settings,"interpreter",ide.config.interpreter)
|
||||
ProjectSetInterpreter(ide.config.interpreter)
|
||||
end
|
||||
|
||||
function SettingsSaveEditorSettings()
|
||||
local listname = "/editor"
|
||||
local path = settings:GetPath()
|
||||
|
||||
@@ -8,14 +8,11 @@ local ide = ide
|
||||
local bottomnotebook = ide.frame.bottomnotebook
|
||||
local out = bottomnotebook.shellbox
|
||||
|
||||
local OUTPUT_MARKER = 3
|
||||
local remotesend
|
||||
|
||||
local OUTPUT_MARKER = 3
|
||||
local OUTPUT_MARKER_VALUE = 8 -- = 2^OUTPUT_MARKER
|
||||
|
||||
local frame = ide.frame
|
||||
out:SetFont(ide.ofont)
|
||||
out:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.ofont)
|
||||
out:SetFont(ide.font.oNormal)
|
||||
out:StyleSetFont(wxstc.wxSTC_STYLE_DEFAULT, ide.font.oNormal)
|
||||
out:StyleClearAll()
|
||||
out:SetBufferedDraw(true)
|
||||
|
||||
@@ -35,7 +32,7 @@ out:MarkerDefine(BREAKPOINT_MARKER, wxstc.wxSTC_MARK_BACKGROUND, wx.wxBLACK, wx.
|
||||
out:MarkerDefine(OUTPUT_MARKER, wxstc.wxSTC_MARK_BACKGROUND, wx.wxBLACK, wx.wxColour(240, 240, 240))
|
||||
out:SetReadOnly(false)
|
||||
|
||||
SetupKeywords(out,"lua",nil,ide.config.stylesoutshell,ide.ofont,ide.ofontItalic)
|
||||
SetupKeywords(out,"lua",nil,ide.config.stylesoutshell,ide.font.oNormal,ide.font.oItalic)
|
||||
|
||||
local function getPromptLine()
|
||||
local totalLines = out:GetLineCount()
|
||||
@@ -59,11 +56,12 @@ local function positionInLine(line)
|
||||
return out:GetCurrentPos() - out:PositionFromLine(line)
|
||||
end
|
||||
|
||||
local function caretOnPromptLine(disallowLeftmost)
|
||||
local function caretOnPromptLine(disallowLeftmost, line)
|
||||
local promptLine = getPromptLine()
|
||||
local currentLine = line or out:GetCurrentLine()
|
||||
local boundary = disallowLeftmost and 0 or -1
|
||||
return (out:GetCurrentLine() > promptLine
|
||||
or out:GetCurrentLine() == promptLine and positionInLine(promptLine) > boundary)
|
||||
return (currentLine > promptLine
|
||||
or currentLine == promptLine and positionInLine(promptLine) > boundary)
|
||||
end
|
||||
|
||||
local function chomp(line)
|
||||
@@ -109,6 +107,8 @@ end
|
||||
|
||||
local function shellPrint(marker, ...)
|
||||
local cnt = select('#',...)
|
||||
if cnt == 0 then return end -- return if nothing to print
|
||||
|
||||
local isPrompt = marker and (getPromptLine() > -1)
|
||||
|
||||
local text = ''
|
||||
@@ -155,6 +155,7 @@ end
|
||||
|
||||
local function filterTraceError(err, addedret)
|
||||
local err = err:match("(.-:%d+:.-)\n[^\n]*\n[^\n]*\n[^\n]*src/editor/shellbox.lua:.*in function 'executeShellCode'")
|
||||
or err
|
||||
err = err:gsub("stack traceback:.-\n[^\n]+\n?","")
|
||||
if addedret then err = err:gsub('^%[string "return ', '[string "') end
|
||||
err = err:match("(.*)\n[^\n]*%(tail call%): %?$") or err
|
||||
@@ -200,7 +201,7 @@ local function createenv ()
|
||||
end
|
||||
|
||||
local function relativeFilepath(file)
|
||||
local name,level = luafilepath(3)
|
||||
local name = luafilepath(3)
|
||||
return (file and name) and name.."/"..file or file or name
|
||||
end
|
||||
|
||||
@@ -235,29 +236,32 @@ end
|
||||
|
||||
local env = createenv()
|
||||
|
||||
local function packResults(status, ...) return status, {...} end
|
||||
|
||||
local function executeShellCode(tx)
|
||||
if tx == nil or tx == '' then return end
|
||||
|
||||
DisplayShellPrompt('')
|
||||
|
||||
local addedret = false
|
||||
local fn,err
|
||||
if remotesend then
|
||||
remotesend(tx)
|
||||
else
|
||||
fn,err = loadstring(tx)
|
||||
-- for statement queries create the return
|
||||
if err and (err:find("'=' expected near '<eof>'") or
|
||||
err:find("syntax error near '") or
|
||||
err:find("unexpected symbol near '")) then
|
||||
local errmore
|
||||
fn,errmore = loadstring("return "..tx:gsub("^%s*=%s*",""))
|
||||
addedret = not errmore
|
||||
end
|
||||
-- try to compile as statement
|
||||
local _, err = loadstring(tx)
|
||||
local isstatement = not err
|
||||
|
||||
if remotesend then remotesend(tx, isstatement); return end
|
||||
|
||||
local addedret, forceexpression = true, tx:match("^%s*=%s*")
|
||||
tx = tx:gsub("^%s*=%s*","")
|
||||
fn, err = loadstring("return "..tx)
|
||||
if not forceexpression and err and
|
||||
(err:find("'<eof>' expected near '") or
|
||||
err:find("'%(' expected near") or
|
||||
err:find("unexpected symbol near '")) then
|
||||
fn, err = loadstring(tx)
|
||||
addedret = false
|
||||
end
|
||||
|
||||
if fn == nil and err then
|
||||
DisplayShellErr(err)
|
||||
DisplayShellErr(filterTraceError(err, addedret))
|
||||
elseif fn then
|
||||
setfenv(fn,env)
|
||||
|
||||
@@ -269,19 +273,33 @@ local function executeShellCode(tx)
|
||||
wx.wxFileName.SetCwd(projectDir)
|
||||
end
|
||||
|
||||
local ok, res = xpcall(fn,
|
||||
local ok, res = packResults(xpcall(fn,
|
||||
function(err)
|
||||
DisplayShellErr(filterTraceError(debug.traceback(err), addedret))
|
||||
end)
|
||||
end))
|
||||
|
||||
-- restore the current dir
|
||||
if projectDir then wx.wxFileName.SetCwd(cwd) end
|
||||
|
||||
if ok and (addedret or res ~= nil) then DisplayShell(res) end
|
||||
if ok and (addedret or #res > 0) then
|
||||
if addedret then
|
||||
local mobdebug = require "mobdebug"
|
||||
for i,v in pairs(res) do -- stringify each of the returned values
|
||||
res[i] = mobdebug.line(v, {nocode = true, comment = 1})
|
||||
end
|
||||
-- add nil only if we are forced (using =) or if this is not a statement
|
||||
-- this is needed to print 'nil' when asked for 'foo',
|
||||
-- and don't print it when asked for 'print(1)'
|
||||
if #res == 0 and (forceexpression or not isstatement) then
|
||||
res = {'nil'}
|
||||
end
|
||||
end
|
||||
DisplayShell((table.unpack or unpack)(res))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ShellSupportRemote(client,uid)
|
||||
function ShellSupportRemote(client)
|
||||
remotesend = client
|
||||
|
||||
local index = bottomnotebook:GetPageIndex(out)
|
||||
@@ -359,7 +377,7 @@ out:Connect(wx.wxEVT_KEY_DOWN,
|
||||
or key == wx.WXK_SHIFT or key == wx.WXK_CONTROL
|
||||
or key == wx.WXK_ALT then
|
||||
break
|
||||
elseif key == wx.WXK_RETURN or key == WXK_NUMPAD_ENTER then
|
||||
elseif key == wx.WXK_RETURN or key == wx.WXK_NUMPAD_ENTER then
|
||||
if not caretOnPromptLine()
|
||||
or out:LineFromPosition(out:GetSelectionStart()) < getPromptLine() then
|
||||
return
|
||||
@@ -369,6 +387,7 @@ out:Connect(wx.wxEVT_KEY_DOWN,
|
||||
if caretOnPromptLine(true) and event:ShiftDown() then break end
|
||||
|
||||
local promptText = getPromptText()
|
||||
if #promptText == 0 then return end -- nothing to execute, exit
|
||||
if promptText == 'clear' then
|
||||
out:ClearAll()
|
||||
displayShellIntro()
|
||||
@@ -393,4 +412,20 @@ out:Connect(wx.wxEVT_KEY_DOWN,
|
||||
event:Skip()
|
||||
end)
|
||||
|
||||
local function inputEditable(line)
|
||||
return caretOnPromptLine(fale, line) and
|
||||
not (out:LineFromPosition(out:GetSelectionStart()) < getPromptLine())
|
||||
end
|
||||
|
||||
out:Connect(wxstc.wxEVT_STC_UPDATEUI,
|
||||
function (event) out:SetReadOnly(not inputEditable()) end)
|
||||
|
||||
-- only allow copy/move text by dropping to the input line
|
||||
out:Connect(wxstc.wxEVT_STC_DO_DROP,
|
||||
function (event)
|
||||
if not inputEditable(out:LineFromPosition(event:GetPosition())) then
|
||||
event:SetDragResult(wx.wxDragNone)
|
||||
end
|
||||
end)
|
||||
|
||||
displayShellIntro()
|
||||
|
||||
@@ -224,9 +224,9 @@ function ReApplySpecAndStyles()
|
||||
local errorlog = ide.frame.bottomnotebook.errorlog
|
||||
local shellbox = ide.frame.bottomnotebook.shellbox
|
||||
|
||||
SetupKeywords(shellbox,"lua",nil,ide.config.stylesoutshell,ide.ofont,ide.ofontItalic)
|
||||
SetupKeywords(shellbox,"lua",nil,ide.config.stylesoutshell,ide.font.oNormal,ide.font.oItalic)
|
||||
|
||||
StylesApplyToEditor(ide.config.stylesoutshell,errorlog,ide.ofont,ide.ofontItalic)
|
||||
StylesApplyToEditor(ide.config.stylesoutshell,errorlog,ide.font.oNormal,ide.font.oItalic)
|
||||
end
|
||||
|
||||
function LoadConfigStyle()
|
||||
@@ -234,7 +234,7 @@ function LoadConfigStyle()
|
||||
"/cfg",
|
||||
"",
|
||||
"Lua file (*.lua)|*.lua|All files (*)|*",
|
||||
wx.wxOPEN + wx.wxFILE_MUST_EXIST)
|
||||
wx.wxFD_OPEN + wx.wxFD_FILE_MUST_EXIST)
|
||||
if fileDialog:ShowModal() == wx.wxID_OK then
|
||||
local cfg = {wxstc = wxstc, path = {}, editor = {}, view ={}, acandtip = {}, outputshell = {}, debugger={},}
|
||||
local cfgfn,err = loadfile(fileDialog:GetPath())
|
||||
|
||||
86
src/main.lua
86
src/main.lua
@@ -1,9 +1,15 @@
|
||||
-- authors: Luxinia Dev (Eike Decker & Christoph Kubisch)
|
||||
---------------------------------------------------------
|
||||
|
||||
package.cpath = package.cpath..';bin/?.dll;bin/clibs/?.dll;bin/clibs/?/?.dll;bin/clibs/?/?/?.dll'
|
||||
package.cpath = package.cpath..';bin/?.so;bin/clibs/?.so;bin/clibs/?/?.so;bin/clibs/?/?/?.so'
|
||||
package.path = package.path..';lualibs/?.lua;lualibs/?/?.lua;lualibs/?/init.lua;lualibs/?/?/?.lua;lualibs/?/?/init.lua'
|
||||
-- put bin/ and lualibs/ first to avoid conflicts with included modules
|
||||
-- that may have other versions present somewhere else in path/cpath
|
||||
local iswindows = os.getenv('WINDIR') or (os.getenv('OS') or ''):match('[Ww]indows')
|
||||
package.cpath = (iswindows
|
||||
and 'bin/?.dll;bin/clibs/?.dll;'
|
||||
or 'bin/clibs/?.dylib;bin/lib?.dylib;bin/?.so;bin/clibs/?.so;')
|
||||
.. package.cpath
|
||||
package.path = 'lualibs/?.lua;lualibs/?/?.lua;lualibs/?/init.lua;lualibs/?/?/?.lua;lualibs/?/?/init.lua;'
|
||||
.. package.path
|
||||
|
||||
require("wx")
|
||||
require("bit")
|
||||
@@ -31,6 +37,7 @@ ide = {
|
||||
verbose = false,
|
||||
},
|
||||
outputshell = {},
|
||||
filetree = {},
|
||||
|
||||
styles = StylesGetDefault(),
|
||||
stylesoutshell = StylesGetDefault(),
|
||||
@@ -45,6 +52,7 @@ ide = {
|
||||
|
||||
activateoutput = false, -- activate output/console on Run/Debug/Compile
|
||||
unhidewxwindow = false, -- try to unhide a wx window
|
||||
allowinteractivescript = false, -- allow interaction in the output window
|
||||
filehistorylength = 20,
|
||||
projecthistorylength = 15,
|
||||
savebak = false,
|
||||
@@ -84,10 +92,13 @@ ide = {
|
||||
-- modTime = wxDateTime of disk file or nil,
|
||||
-- isModified = bool is the document modified? }
|
||||
ignoredFilesList = {},
|
||||
font = nil,
|
||||
fontItalic = nil,
|
||||
ofont = nil,
|
||||
ofontItalic = nil,
|
||||
font = {
|
||||
eNormal = nil,
|
||||
eItalic = nil,
|
||||
oNormal = nil,
|
||||
oItalic = nil,
|
||||
fNormal = nil,
|
||||
}
|
||||
}
|
||||
|
||||
---------------
|
||||
@@ -96,11 +107,18 @@ local filenames = {}
|
||||
local configs = {}
|
||||
do
|
||||
local arg = {...}
|
||||
local fullPath = arg[1] -- first argument must be the application name
|
||||
assert(type(fullPath) == "string", "first argument must be application name")
|
||||
|
||||
if not wx.wxIsAbsolutePath(fullPath) then
|
||||
fullPath = wx.wxGetCwd().."/"..fullPath
|
||||
if wx.__WXMSW__ then fullPath = wx.wxUnix2DosFilename(fullPath) end
|
||||
end
|
||||
|
||||
ide.arg = arg
|
||||
-- first argument must be the application name
|
||||
assert(type(arg[1]) == "string","first argument must be application name")
|
||||
ide.editorFilename = arg[1]
|
||||
ide.config.path.app = arg[1]:match("([%w_-]+)%.?[^%.]*$")
|
||||
ide.editorFilename = fullPath
|
||||
ide.osname = wx.wxPlatformInfo.Get():GetOperatingSystemFamilyName()
|
||||
ide.config.path.app = fullPath:match("([%w_-%.]+)$"):gsub("%.[^%.]*$","")
|
||||
assert(ide.config.path.app, "no application path defined")
|
||||
for index = 2, #arg do
|
||||
if (arg[index] == "-cfg" and index+1 <= #arg) then
|
||||
@@ -129,7 +147,7 @@ local function addConfig(filename,showerror,isstring)
|
||||
ide.config.os = os
|
||||
ide.config.wxstc = wxstc
|
||||
setfenv(cfgfn,ide.config)
|
||||
xpcall(function()cfgfn(assert(_G))end,
|
||||
xpcall(function()cfgfn(assert(_G or _ENV))end,
|
||||
function(err)
|
||||
print("Error while executing configuration file: \n",
|
||||
debug.traceback(err))
|
||||
@@ -139,11 +157,6 @@ end
|
||||
|
||||
do
|
||||
addConfig(ide.config.path.app.."/config.lua",true)
|
||||
addConfig("cfg/user.lua",false)
|
||||
for i,v in ipairs(configs) do
|
||||
addConfig(v,true,true)
|
||||
end
|
||||
configs = nil
|
||||
end
|
||||
|
||||
----------------------
|
||||
@@ -172,7 +185,7 @@ local function addToTab(tab,file)
|
||||
|
||||
local success,result
|
||||
success, result = xpcall(
|
||||
function()return cfgfn(_G)end,
|
||||
function()return cfgfn(_G or _ENV)end,
|
||||
function(err)
|
||||
print(("Error while executing configuration file (%s): \n%s"):
|
||||
format(file,debug.traceback(err)))
|
||||
@@ -192,8 +205,7 @@ end
|
||||
|
||||
-- load interpreters
|
||||
local function loadInterpreters()
|
||||
|
||||
local files = FileSysGet(".\\interpreters\\*.*",wx.wxFILE)
|
||||
local files = FileSysGet("./interpreters/*.*",wx.wxFILE)
|
||||
for i,file in ipairs(files) do
|
||||
if file:match "%.lua$" and app.loadfilters.interpreters(file) then
|
||||
addToTab(ide.interpreters,file)
|
||||
@@ -204,8 +216,7 @@ loadInterpreters()
|
||||
|
||||
-- load specs
|
||||
local function loadSpecs()
|
||||
|
||||
local files = FileSysGet(".\\spec\\*.*",wx.wxFILE)
|
||||
local files = FileSysGet("./spec/*.*",wx.wxFILE)
|
||||
for i,file in ipairs(files) do
|
||||
if file:match "%.lua$" and app.loadfilters.specs(file) then
|
||||
addToTab(ide.specs,file)
|
||||
@@ -216,6 +227,7 @@ local function loadSpecs()
|
||||
spec.sep = spec.sep or ""
|
||||
spec.iscomment = {}
|
||||
spec.iskeyword0 = {}
|
||||
spec.isstring = {}
|
||||
if (spec.lexerstyleconvert) then
|
||||
if (spec.lexerstyleconvert.comment) then
|
||||
for i,s in pairs(spec.lexerstyleconvert.comment) do
|
||||
@@ -227,6 +239,11 @@ local function loadSpecs()
|
||||
spec.iskeyword0[s] = true
|
||||
end
|
||||
end
|
||||
if (spec.lexerstyleconvert.stringtxt) then
|
||||
for i,s in pairs(spec.lexerstyleconvert.stringtxt) do
|
||||
spec.isstring[s] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -234,8 +251,7 @@ loadSpecs()
|
||||
|
||||
-- load tools
|
||||
local function loadTools()
|
||||
|
||||
local files = FileSysGet(".\\tools\\*.*",wx.wxFILE)
|
||||
local files = FileSysGet("./tools/*.*",wx.wxFILE)
|
||||
for i,file in ipairs(files) do
|
||||
if file:match "%.lua$" and app.loadfilters.tools(file) then
|
||||
addToTab(ide.tools,file)
|
||||
@@ -246,6 +262,14 @@ loadTools()
|
||||
|
||||
if app.preinit then app.preinit() end
|
||||
|
||||
do
|
||||
addConfig("cfg/user.lua",false)
|
||||
for i,v in ipairs(configs) do
|
||||
addConfig(v,true,true)
|
||||
end
|
||||
configs = nil
|
||||
end
|
||||
|
||||
---------------
|
||||
-- Load App
|
||||
|
||||
@@ -303,10 +327,16 @@ end
|
||||
|
||||
if app.postinit then app.postinit() end
|
||||
|
||||
-- only set menu bar *after* postinit handler as it may include adding
|
||||
-- app-specific menus (Help/About), which are not recognized by MacOS
|
||||
-- as special items unless SetMenuBar is done after menus are populated.
|
||||
ide.frame:SetMenuBar(ide.frame.menuBar)
|
||||
if ide.osname == 'Macintosh' then -- force refresh to fix the filetree
|
||||
pcall(function() ide.frame:ShowFullScreen(true) ide.frame:ShowFullScreen(false) end)
|
||||
end
|
||||
ide.frame:Show(true)
|
||||
|
||||
-- Call wx.wxGetApp():MainLoop() last to start the wxWidgets event loop,
|
||||
-- otherwise the wxLua program will exit immediately.
|
||||
-- Does nothing if running from wxLua, wxLuaFreeze, or wxLuaEdit since the
|
||||
-- MainLoop is already running or will be started by the C++ program.
|
||||
-- call wx.wxGetApp():MainLoop() last to start the wxWidgets event loop,
|
||||
-- otherwise the program will exit immediately.
|
||||
-- Does nothing if the MainLoop is already running.
|
||||
wx.wxGetApp():MainLoop()
|
||||
|
||||
@@ -168,7 +168,7 @@ function FileSysGet(dir,spec)
|
||||
end
|
||||
local f = browse:FindFirst(dir,spec)
|
||||
while #f>0 do
|
||||
table.insert(content,f)
|
||||
table.insert(content,(f:gsub("^file:",""))) -- remove file: protocol (wx2.9+)
|
||||
f = browse:FindNext()
|
||||
end
|
||||
return content
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
LICENSE
|
||||
README.md
|
||||
api/lua/baselib.lua
|
||||
api/lua/love2d.lua
|
||||
api/readme.txt
|
||||
bin/clibs/lfs.dll
|
||||
bin/clibs/mojoshader.dll
|
||||
@@ -40,7 +41,8 @@ bin/wxmsw28_qa_vc_custom.dll
|
||||
bin/wxmsw28_richtext_vc_custom.dll
|
||||
bin/wxmsw28_stc_vc_custom.dll
|
||||
bin/wxmsw28_xrc_vc_custom.dll
|
||||
cfg/user.lua_for_custom_settings.txt
|
||||
cfg/user-sample.lua
|
||||
interpreters/love2d.lua
|
||||
interpreters/luadeb.lua
|
||||
lualibs/copas/copas.lua
|
||||
lualibs/coxpcall/coxpcall.lua
|
||||
@@ -126,12 +128,15 @@ zbstudio/res/16/wxART_FIND.png
|
||||
zbstudio/res/16/wxART_FIND_AND_REPLACE.png
|
||||
zbstudio/res/16/wxART_FOLDER.png
|
||||
zbstudio/res/16/wxART_GO_DIR_UP.png
|
||||
zbstudio/res/16/wxART_GO_FORWARD-wxART_OTHER_C.png
|
||||
zbstudio/res/16/wxART_HELP_PAGE.png
|
||||
zbstudio/res/16/wxART_LIST_VIEW-wxART_OTHER_C.png
|
||||
zbstudio/res/16/wxART_NEW_DIR.png
|
||||
zbstudio/res/16/wxART_NORMAL_FILE-wxART_OTHER_C.png
|
||||
zbstudio/res/16/wxART_NORMAL_FILE.png
|
||||
zbstudio/res/16/wxART_PASTE.png
|
||||
zbstudio/res/16/wxART_REDO.png
|
||||
zbstudio/res/16/wxART_REPORT_VIEW-wxART_OTHER_C.png
|
||||
zbstudio/res/16/wxART_UNDO.png
|
||||
zbstudio/res/32.ico
|
||||
zbstudio/res/zerobrane.png
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
local icons = {}
|
||||
local CreateBitmap = function(id, client, size)
|
||||
local width = size:GetWidth()
|
||||
local key = width .. "/" .. id
|
||||
local fileClient = "zbstudio/res/" .. key .. "-" .. client .. ".png"
|
||||
local fileKey = "zbstudio/res/" .. key .. ".png"
|
||||
local file
|
||||
if wx.wxFileName(fileClient):FileExists() then file = fileClient
|
||||
elseif wx.wxFileName(fileKey):FileExists() then file = fileKey
|
||||
else return wx.wxNullBitmap end
|
||||
local icon = icons[file] or wx.wxBitmap(file)
|
||||
icons[file] = icon
|
||||
return icon
|
||||
end
|
||||
local ide = ide
|
||||
local app = {
|
||||
|
||||
createbitmap = CreateBitmap,
|
||||
loadfilters = {
|
||||
tools = function(file) return false end,
|
||||
specs = function(file) return true end,
|
||||
@@ -9,24 +23,12 @@ local app = {
|
||||
|
||||
preinit = function ()
|
||||
local artProvider = wx.wxLuaArtProvider()
|
||||
local icons = {}
|
||||
artProvider.CreateBitmap = function(self, id, client, size)
|
||||
local width = size:GetWidth()
|
||||
local key = width .. "/" .. id
|
||||
local fileClient = "zbstudio/res/" .. key .. "-" .. client .. ".png"
|
||||
local fileKey = "zbstudio/res/" .. key .. ".png"
|
||||
local file
|
||||
if wx.wxFileName(fileClient):FileExists() then file = fileClient
|
||||
elseif wx.wxFileName(fileKey):FileExists() then file = fileKey
|
||||
else return wx.wxNullBitmap end
|
||||
local icon = icons[file] or wx.wxBitmap(file)
|
||||
icons[file] = icon
|
||||
return icon
|
||||
end
|
||||
artProvider.CreateBitmap = function(self, ...) return CreateBitmap(...) end
|
||||
wx.wxArtProvider.Push(artProvider)
|
||||
|
||||
ide.config.interpreter = "luadeb"
|
||||
ide.config.unhidewxwindow = true
|
||||
ide.config.unhidewxwindow = true -- allow unhiding of wx windows
|
||||
ide.config.allowinteractivescript = true -- allow interaction in the output window
|
||||
|
||||
-- this needs to be in pre-init to load the styles
|
||||
dofile("src/editor/markup.lua")
|
||||
@@ -49,9 +51,7 @@ local app = {
|
||||
|
||||
local menuBar = ide.frame.menuBar
|
||||
local menu = menuBar:GetMenu(menuBar:FindMenu("&Project"))
|
||||
local itemid = menu:FindItem("Lua &interpreter")
|
||||
if itemid ~= wx.wxNOT_FOUND then menu:Destroy(itemid) end
|
||||
itemid = menu:FindItem("Project directory")
|
||||
local itemid = menu:FindItem("Project Directory")
|
||||
if itemid ~= wx.wxNOT_FOUND then menu:Destroy(itemid) end
|
||||
|
||||
menu = menuBar:GetMenu(menuBar:FindMenu("&View"))
|
||||
@@ -60,14 +60,20 @@ local app = {
|
||||
|
||||
menuBar:Check(ID_CLEAROUTPUT, true)
|
||||
|
||||
-- load welcome.lua from myprograms/ if exists
|
||||
local fn = wx.wxFileName("myprograms/welcome.lua")
|
||||
if fn:FileExists() and
|
||||
(not ide.config.path.projectdir
|
||||
or string.len(ide.config.path.projectdir) == 0) then
|
||||
fn:Normalize() -- make absolute path
|
||||
LoadFile(fn:GetFullPath(),nil,true)
|
||||
ProjectUpdateProjectDir(fn:GetPath(wx.wxPATH_GET_VOLUME))
|
||||
-- load myprograms/welcome.lua if exists and no projectdir
|
||||
local projectdir = ide.config.path.projectdir
|
||||
if (not projectdir or string.len(projectdir) == 0
|
||||
or not wx.wxFileName(projectdir):DirExists()) then
|
||||
local home = wx.wxGetHomeDir():gsub("[\\/]$","")
|
||||
for _,dir in pairs({home, home.."/Desktop", ""}) do
|
||||
local fn = wx.wxFileName("myprograms/welcome.lua")
|
||||
-- normalize to absolute path
|
||||
if fn:Normalize(wx.wxPATH_NORM_ALL, dir) and fn:FileExists() then
|
||||
LoadFile(fn:GetFullPath(),nil,true)
|
||||
ProjectUpdateProjectDir(fn:GetPath(wx.wxPATH_GET_VOLUME))
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
@@ -79,7 +85,6 @@ local app = {
|
||||
settingsapp = "ZeroBraneStudio",
|
||||
settingsvendor = "ZeroBraneLLC",
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
return app
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
editor.fontname = "Courier New"
|
||||
editor.caretline = true
|
||||
editor.showfncall = true
|
||||
editor.autotabs = false
|
||||
@@ -6,12 +5,15 @@ editor.usetabs = false
|
||||
editor.tabwidth = 2
|
||||
editor.usewrap = true
|
||||
|
||||
local G = ... -- this now points to the global environment
|
||||
if G.ide.osname == 'Macintosh' then filetree.fontsize = 11 end
|
||||
|
||||
filehistorylength = 20
|
||||
|
||||
singleinstance = true
|
||||
singleinstanceport = 0xe493
|
||||
|
||||
acandtip.shorttip = true
|
||||
acandtip.shorttip = false
|
||||
acandtip.nodynwords = true
|
||||
|
||||
activateoutput = true
|
||||
|
||||
@@ -55,7 +55,7 @@ local function DisplayAbout(event)
|
||||
</body>
|
||||
</html>]]
|
||||
|
||||
local dlg = wx.wxDialog(frame, wx.wxID_ANY, "About ZeroBane Studio")
|
||||
local dlg = wx.wxDialog(frame, wx.wxID_ANY, "About ZeroBrane Studio")
|
||||
local html = wx.wxLuaHtmlWindow(dlg, wx.wxID_ANY,
|
||||
wx.wxDefaultPosition, wx.wxSize(440, 270),
|
||||
wx.wxHW_SCROLLBAR_NEVER)
|
||||
@@ -73,11 +73,10 @@ local function DisplayAbout(event)
|
||||
topsizer:Add(html, 1, wx.wxALL, 10)
|
||||
topsizer:Add(line, 0, wx.wxEXPAND + wx.wxLEFT + wx.wxRIGHT, 10)
|
||||
topsizer:Add(button, 0, wx.wxALL + wx.wxALIGN_RIGHT, 10)
|
||||
topsizer:Fit(dlg)
|
||||
|
||||
dlg:SetAutoLayout(true)
|
||||
dlg:SetSizer(topsizer)
|
||||
topsizer:Fit(dlg)
|
||||
|
||||
dlg:ShowModal()
|
||||
dlg:Destroy()
|
||||
end
|
||||
|
||||
BIN
zbstudio/res/16/wxART_GO_FORWARD-wxART_OTHER_C.png
Normal file
BIN
zbstudio/res/16/wxART_GO_FORWARD-wxART_OTHER_C.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 758 B |
BIN
zbstudio/res/16/wxART_LIST_VIEW-wxART_OTHER_C.png
Normal file
BIN
zbstudio/res/16/wxART_LIST_VIEW-wxART_OTHER_C.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 783 B |
BIN
zbstudio/res/16/wxART_REPORT_VIEW-wxART_OTHER_C.png
Normal file
BIN
zbstudio/res/16/wxART_REPORT_VIEW-wxART_OTHER_C.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 792 B |
Reference in New Issue
Block a user