You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
810 lines
26 KiB
810 lines
26 KiB
-- see copyright notice in wxLdb.lua |
|
|
|
local grldc = |
|
{ |
|
net = require( "grldc.net" ), |
|
utilities = require( "grldc.utilities" ) |
|
} |
|
local ui = |
|
{ |
|
mainWindow = require( "ui.mainWindow" ) |
|
} |
|
local lfs = require( "lfs" ) |
|
|
|
require( "coxpcall" ) |
|
local coxpcall = coxpcall |
|
local copcall = copcall |
|
|
|
local setmetatable = setmetatable |
|
local ipairs = ipairs |
|
local type = type |
|
local assert = assert |
|
local pairs = pairs |
|
local next = next |
|
local print = print |
|
local tostring = tostring |
|
local string = string |
|
local coroutine = coroutine |
|
local io = io |
|
local debug = debug |
|
local xpcall = xpcall |
|
local table = table |
|
local os = os |
|
|
|
module( "wxLdbController" ) |
|
|
|
local meta = { __index = {} } |
|
|
|
local complexValueManagerMeta = { __index = {} } |
|
|
|
function new( engine, window ) |
|
local res = |
|
{ |
|
engine = engine, |
|
window = window, |
|
clients = {}, |
|
configs = {}, |
|
activeClient = nil, |
|
threadsDirty = true, |
|
exiting = false, |
|
requests_ = {}, |
|
toListen = {}, |
|
} |
|
setmetatable( res, meta ) |
|
res.runCoroutine = coroutine.create( function() res:run_() end ) |
|
local ok, msg = coroutine.resume( res.runCoroutine ) |
|
assert( ok, debug.traceback( res.runCoroutine, msg ) ) |
|
return res |
|
end |
|
|
|
function meta.__index:ready_() |
|
return self.sleeping and #self.requests_ == 0 |
|
end |
|
|
|
function meta.__index:addListener( ip, port ) |
|
if self.engine.initialized() then |
|
self.engine.listen( ip, port ) |
|
else |
|
table.insert( self.toListen, { ip = ip, port = port } ) |
|
end |
|
end |
|
|
|
function meta.__index:update() |
|
if self:ready_() then |
|
self.sleeping = false |
|
local ok, msg = coroutine.resume( self.runCoroutine ) |
|
assert( ok, debug.traceback( self.runCoroutine, msg ) ) |
|
elseif self.currentRequest_ ~= nil then |
|
if coroutine.status( self.currentRequest_ ) ~= "suspended" then |
|
print( "Asynchronous request has terminated" ) |
|
table.remove( self.requests_, 1 ) |
|
self.currentRequest_ = nil |
|
end |
|
elseif self.requests_[1] ~= nil then |
|
print( "Starting asynchronous request..." ) |
|
local req = self.requests_[1] |
|
local co = coroutine.create( req ) |
|
self.currentRequest_ = co |
|
local ok, msg = coroutine.resume( co ) |
|
assert( ok, debug.traceback( co, msg ) ) |
|
end |
|
end |
|
|
|
function meta.__index:sleep_() |
|
self.sleeping = true |
|
coroutine.yield() |
|
end |
|
|
|
function meta.__index:queueRequest_( req ) |
|
table.insert( self.requests_, req ) |
|
end |
|
|
|
function meta.__index:run_() |
|
self:sleep_() |
|
|
|
--@GRLD_PROTECTION@ |
|
|
|
self.engine.init() |
|
for _, listener in ipairs( self.toListen ) do |
|
self.engine.listen( listener.ip, listener.port ) |
|
end |
|
self.toListen = {} |
|
self.window:show( true ) |
|
|
|
for _, cbName in ipairs( { "onNewClient", "onClientBreak", "onClientLost" } ) do |
|
self.engine.registerEvent( cbName, function( ... ) return self[cbName.."_"]( self, ... ) end ) |
|
end |
|
|
|
local wrapCb = function( cb ) |
|
return function( ... ) |
|
if self:ready_() then |
|
return cb( ... ) |
|
end |
|
end |
|
end |
|
|
|
self.window:registerEvent( ui.mainWindow.ID_BREAK, wrapCb( function() self:onDebugCommand_( "breaknow", "running" ) end ) ) |
|
self.window:registerEvent( ui.mainWindow.ID_CONTINUE, wrapCb( function() self:onDebugCommand_( "run", "break" ) end ) ) |
|
self.window:registerEvent( ui.mainWindow.ID_STEP_OVER, wrapCb( function() self:onDebugCommand_( "stepover", "break" ) end ) ) |
|
self.window:registerEvent( ui.mainWindow.ID_STEP_INTO, wrapCb( function() self:onDebugCommand_( "stepin", "break" ) end ) ) |
|
self.window:registerEvent( ui.mainWindow.ID_STEP_OUT, wrapCb( function() self:onDebugCommand_( "stepout", "break" ) end ) ) |
|
self.window:registerEvent( ui.mainWindow.ID_TOGGLE_BREAKPOINT, wrapCb( function() self:onToggleBreakpoint_() end ) ) |
|
|
|
self.window:registerEvent( "onBreakPointChanged", wrapCb( function( ... ) self:onBreakPointChanged_( ... ) end ) ) |
|
self.window:registerEvent( "onFileOpen", wrapCb( function( ... ) self:onFileOpen_( ... ) end ) ) |
|
self.window:registerEvent( "onFileClosed", wrapCb( function( ... ) self:onFileClosed_( ... ) end ) ) |
|
|
|
self.window:registerEvent( "onApplicationExiting", wrapCb( function( ... ) self:onApplicationExiting_( ... ) end ) ) |
|
|
|
self.window.threads:registerEvent( "onThreadClicked", wrapCb( function( ... ) self:onThreadClicked_( ... ) end ) ) |
|
self.window.threads:registerEvent( "onBreakOnConnectionChanged", wrapCb( function( ... ) self:onBreakOnConnectionChanged_( ... ) end ) ) |
|
|
|
self.window.callstack:registerEvent( "onCallstackClicked", wrapCb( function( ... ) self:onCallstackClicked_( ... ) end ) ) |
|
|
|
self.window.watch.evaluateCallback = wrapCb( function( expr ) return self:evaluateExpression_( expr ) end ) |
|
|
|
self.configs.global = { name = "global", breakpoints = {} } |
|
self:loadConfig_( "global" ) |
|
|
|
self:sleep_() |
|
while not self.exiting do |
|
for clientId, clientData in pairs( self.clients ) do |
|
coxpcall( function() |
|
if clientData.invalidateTimer ~= nil then |
|
if os.time() >= clientData.invalidateTimer then |
|
clientData.invalidateTimer = nil |
|
assert( clientId == self.activeClient ) |
|
self:invalidateState_( true ) |
|
end |
|
end |
|
if clientData.config.dirty and os.time() >= (clientData.config.lastConfigSave or 0) + 10 then |
|
self:saveConfig_( clientData.config.name ) |
|
end |
|
if clientData.dirty then |
|
self.window:setActive() |
|
clientData.dirty = false |
|
local client = self.engine.getClient( clientId ) |
|
if client == nil then |
|
self.threadsDirty = true |
|
elseif clientId == self.activeClient then |
|
if client:status() == "break" then |
|
client:setactivethread( clientData.activeThread ) |
|
self.threadsDirty = true |
|
local callstack = client:callstack() |
|
if type( callstack ) ~= "table" then |
|
-- if we can not get the callstack, we try to switch to the current thread |
|
clientData.activeThread = "current" |
|
client:setactivethread( clientData.activeThread ) |
|
callstack = client:callstack() |
|
end |
|
self:refreshCallstack_( callstack ) |
|
self:refreshSourceFocus_( callstack, clientData.activeLevel ) |
|
end |
|
self:refreshBreakPoints_() |
|
end |
|
elseif clientData.lastUpdate == nil or os.time() > clientData.lastUpdate + 2 then |
|
clientData.lastUpdate = os.time() |
|
self.engine.getClient( clientId ):checkConnection() |
|
end |
|
end, function( msg ) print( "Error refreshing client "..clientId ) print( msg ) print( debug.traceback() ) end ) |
|
end |
|
|
|
if self.threadsDirty then |
|
coxpcall( function() |
|
self.threadsDirty = false |
|
self:refreshThreads_() |
|
end, function( msg ) print( "Error refreshing threads" ) print( msg ) print( debug.traceback() ) end ) |
|
end |
|
|
|
self:sleep_() |
|
end |
|
end |
|
|
|
function meta.__index:evaluateExpression_( expr ) |
|
local clientId = self.activeClient |
|
local client = self.engine.getClient( clientId ) |
|
if client == nil then return { { name = "<no such client>" } } end |
|
if client:status() == "running" then return { { name = "<can't evaluate expression while client is running>" } } end |
|
local clientData = assert( self.clients[clientId] ) |
|
local results = client:evaluate( expr, clientData.activeLevel ) |
|
complexValueManagerMeta.init( results, self.engine, clientId ) |
|
return results |
|
end |
|
|
|
function meta.__index:onCallstackClicked_( level ) |
|
local clientId = self.activeClient |
|
local clientData = self.clients[clientId] |
|
if clientData == nil then return end |
|
clientData.activeLevel = level |
|
clientData.dirty = true |
|
end |
|
|
|
function meta.__index:refreshSourcePageFocus_( remoteSource, line ) |
|
local clientId = self.activeClient |
|
local clientData = assert( self.clients[clientId] ) |
|
local sourceType = string.sub( remoteSource, 1, 1 ) |
|
if sourceType == "@" then |
|
print( "Setting focus to "..remoteSource.."("..line..")" ) |
|
remoteSource = grldc.utilities.normalizePath( string.sub( remoteSource, 2 ) ) |
|
local source, remotePath, remoteFile = self:getLocalSource_( clientId, remoteSource ) |
|
if source == nil then |
|
print( "Prompting mount path..." ) |
|
local mount, path = self.window:promptMountPath( remotePath, nil, remoteFile ) |
|
print( mount, path ) |
|
if mount ~= nil then |
|
mount = grldc.utilities.normalizePath( mount ) |
|
path = grldc.utilities.normalizePath( path, lfs.currentdir() ) |
|
print( mount, path ) |
|
local mountEnd = string.sub( mount, -1 ) |
|
if mountEnd ~= "/" and mountEnd ~= "\\" then |
|
mount = mount.."/" |
|
end |
|
print( mount, path ) |
|
clientData.config.mappings[mount] = path |
|
clientData.config.dirty = true |
|
source = self:getLocalSource_( clientId, remoteSource ) |
|
assert( source ~= nil ) |
|
end |
|
end |
|
|
|
if source ~= nil then |
|
print( source ) |
|
source = grldc.utilities.normalizePath( source ) |
|
self:setSourceFocus_( "@"..source, line ) |
|
end |
|
|
|
--print( source ) |
|
return source |
|
end |
|
end |
|
|
|
function meta.__index:refreshSourceFocus_( callstack, level ) |
|
local clientId = self.activeClient |
|
local clientData = assert( self.clients[clientId] ) |
|
if type( callstack ) == "table" and callstack[level] ~= nil then |
|
local remoteSource = callstack[level].source |
|
local line = callstack[level].line |
|
|
|
local source = self:refreshSourcePageFocus_( remoteSource, line ) |
|
|
|
self:setPointers_( level, source, line ) |
|
self:refreshPointers_() |
|
|
|
self:refreshWatches_( level ) |
|
end |
|
end |
|
|
|
function meta.__index:setPointers_( level, source, line ) |
|
self.pointer = { level = level, source = source, line = line } |
|
end |
|
|
|
function meta.__index:refreshPointers_() |
|
self.window:clearMarkers() |
|
if self.pointer ~= nil then |
|
local source = self.pointer.source |
|
local level = self.pointer.level |
|
local line = self.pointer.line |
|
if source ~= nil then |
|
if level == 1 then |
|
self.window:setCurrentLine( "@"..source, line ) |
|
else |
|
self.window:getSourcePage( "@"..source ):addOtherLine( line ) |
|
end |
|
end |
|
end |
|
end |
|
|
|
function meta.__index:refreshWatches_( level ) |
|
local clientId = self.activeClient |
|
local client = self.engine.getClient( clientId ) |
|
if client == nil then return end |
|
local autoVariables = {} |
|
local locals = client:locals( level ) |
|
if type( locals ) == "string" then |
|
self.window.auto:setData( { { name = "<error evaluating local variables>", value = locals } } ) |
|
else |
|
for _, entry in ipairs( locals ) do |
|
table.insert( autoVariables, { name = "[local] "..entry.name, value = entry.value } ) |
|
end |
|
end |
|
local upvalues = client:upvalues( level ) |
|
if type( upvalues ) == "string" then |
|
self.window.auto:setData( { { name = "<error evaluating upvalues>", value = locals } } ) |
|
else |
|
for _, entry in ipairs( upvalues ) do |
|
table.insert( autoVariables, { name = "[upvalue] "..entry.name, value = entry.value } ) |
|
end |
|
end |
|
complexValueManagerMeta.init( autoVariables, self.engine, clientId ) |
|
self.window.auto:setData( autoVariables ) |
|
self.window.watch:refresh() |
|
end |
|
|
|
function meta.__index:onToggleBreakpoint_() |
|
local source, line = self.window:findSourcePageFocus() |
|
self:onBreakPointChanged_( source, line ) |
|
end |
|
|
|
function meta.__index:onBreakPointChanged_( source, line ) |
|
local clientId = self.activeClient |
|
local config = nil |
|
if clientId == nil or self.clients[clientId] == nil then |
|
config = self.configs.global |
|
else |
|
local clientData = self.clients[clientId] |
|
if clientData == nil then return end |
|
|
|
config = clientData.config |
|
end |
|
|
|
if config.breakpoints[source] == nil then |
|
config.breakpoints[source] = {} |
|
end |
|
config.breakpoints[source][line] = not config.breakpoints[source][line] |
|
newValue = config.breakpoints[source][line] |
|
if not newValue then |
|
config.breakpoints[source][line] = nil |
|
end |
|
|
|
print( "Setting breakpoint at "..source.."("..line..") to "..tostring(newValue) ) |
|
|
|
assert( string.sub( source, 1, 1 ) == "@" ) |
|
source = string.sub( source, 2 ) |
|
for clientId, clientData in pairs( self.clients ) do |
|
if clientData.config == config then |
|
local client = self.engine.getClient( clientId ) |
|
if client == nil then return end |
|
local remoteSource, dir = self:getRemoteSource_( clientId, source ) |
|
self:queueRequest_( function() |
|
if remoteSource == nil then |
|
print( "Can't find remote source corresponding to "..source ) |
|
print( "Prompting mount path..." ) |
|
local mount, path = self.window:promptMountPath( nil, dir ) |
|
print( mount, path ) |
|
if mount ~= nil then |
|
mount = grldc.utilities.normalizePath( mount ) |
|
path = grldc.utilities.normalizePath( path, lfs.currentdir() ) |
|
print( mount, path ) |
|
local mountEnd = string.sub( mount, -1 ) |
|
if mountEnd ~= "/" and mountEnd ~= "\\" then |
|
mount = mount.."/" |
|
end |
|
print( mount, path ) |
|
clientData.config.mappings[mount] = path |
|
remoteSource, dir = self:getRemoteSource_( clientId, source ) |
|
assert( remoteSource ~= nil ) |
|
end |
|
end |
|
|
|
if remoteSource ~= nil then |
|
client:setbreakpoint( "@"..remoteSource, line, newValue ) |
|
clientData.config.dirty = true |
|
end |
|
self:refreshBreakPoints_() |
|
end ) |
|
end |
|
end |
|
self:refreshBreakPoints_() |
|
end |
|
|
|
function meta.__index:onApplicationExiting_() |
|
for id, clientData in pairs( self.clients ) do |
|
if clientData.config.dirty then |
|
self:saveConfig_( clientData.config.name ) |
|
end |
|
end |
|
self.window.threads:setData( nil ) |
|
end |
|
|
|
function meta.__index:refreshBreakPoints_() |
|
local clientId = self.activeClient |
|
local config = nil |
|
local client = nil |
|
if self.activeClient == nil or self.clients[clientId] == nil then |
|
config = self.configs.global |
|
else |
|
config = self.clients[clientId].config |
|
client = self.engine.getClient( clientId ) |
|
end |
|
|
|
local remoteBreakPoints = {} |
|
if client ~= nil then |
|
remoteBreakPoints = client:breakpoints() |
|
end |
|
self.window:clearBreakPoints() |
|
local goodBreakpoints = {} |
|
|
|
for remoteSource, lines in pairs( remoteBreakPoints ) do |
|
if next( lines ) ~= nil then |
|
local source = self:getLocalSource_( clientId, string.sub( remoteSource, 2 ) ) |
|
if source ~= nil then |
|
local page = self.window:findSourcePage( "@"..source ) |
|
if page ~= nil then |
|
if goodBreakpoints["@"..source] == nil then |
|
goodBreakpoints["@"..source] = {} |
|
end |
|
for line, value in pairs( lines ) do |
|
if value then |
|
page:addBreakPoint( line ) |
|
goodBreakpoints["@"..source][line] = true |
|
end |
|
end |
|
else |
|
print( "Can't find source page for breakpoint in file "..source ) |
|
end |
|
else |
|
print( "Can't find source corresponding to remote source "..remoteSource ) |
|
end |
|
end |
|
end |
|
|
|
for source, lines in pairs( config.breakpoints ) do |
|
local page = self.window:findSourcePage( source ) |
|
if page ~= nil then |
|
for line, value in pairs( lines ) do |
|
if value then |
|
if goodBreakpoints[source] == nil or not goodBreakpoints[source][line] then |
|
page:addBreakPoint( line, true ) |
|
end |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
function meta.__index:onFileOpen_( path ) |
|
print( "onFileOpen: "..path ) |
|
source = "@"..grldc.utilities.normalizePath( path ) |
|
print( "normalized path: "..source ) |
|
self:setSourceFocus_( source, 1 ) |
|
self:refreshPointers_() |
|
self:refreshBreakPoints_() |
|
end |
|
|
|
function meta.__index:onFileClosed_( source ) |
|
for id, clientData in pairs( self.clients ) do |
|
clientData.config.dirty = true |
|
end |
|
end |
|
|
|
function meta.__index:onThreadClicked_( clientId, threadId ) |
|
print( "Thread clicked: client="..clientId..", thread="..threadId ) |
|
local clientData = self.clients[clientId] |
|
if clientData == nil then return end |
|
clientData.activeThread = threadId |
|
clientData.activeLevel = 1 |
|
self:setActiveClient_( clientId ) |
|
end |
|
|
|
function meta.__index:onBreakOnConnectionChanged_( clientId, newValue ) |
|
local clientData = self.clients[clientId] |
|
if clientData == nil then return end |
|
clientData.config.breakOnConnection = newValue |
|
clientData.config.dirty = true |
|
end |
|
|
|
function meta.__index:onDebugCommand_( command, neededState, targetClientId ) |
|
if targetClientId == nil then targetClientId = self.activeClient end |
|
if targetClientId == nil then return end |
|
local client = self.engine.getClient( targetClientId ) |
|
if client == nil then |
|
print( "No client "..targetClientId ) |
|
return |
|
end |
|
if neededState ~= nil and client:status() ~= neededState then return end |
|
|
|
self:invalidateState_( false ) |
|
|
|
local clientData = assert( self.clients[targetClientId] ) |
|
local ok, msg = xpcall( function() client[command]( client ) end, debug.traceback ) |
|
if not ok then |
|
print( msg ) |
|
end |
|
clientData.dirty = true |
|
end |
|
|
|
function meta.__index:invalidateState_( immediate ) |
|
local clientData = self.clients[self.activeClient] |
|
if not clientData or immediate then |
|
if clientData ~= nil then |
|
clientData.invalidateTimer = nil |
|
end |
|
self.threadsDirty = true |
|
self.pointer = nil |
|
self.window:clearMarkers() |
|
self.window.callstack:setData( nil ) |
|
self:refreshBreakPoints_() |
|
|
|
local client = self.engine.getClient( self.activeClient ) |
|
if client == nil then |
|
self.window.auto:clear() |
|
self.window.watch:refresh() |
|
else |
|
self.window.auto:setData( nil ) |
|
self.window.watch:refresh() |
|
end |
|
else |
|
clientData.invalidateTimer = os.time() + 1 |
|
end |
|
end |
|
|
|
function meta.__index:getLocalSource_( clientId, source ) |
|
--print( source ) |
|
local _, _, dir, file = string.find( source, "(.*[/\\])(.*)" ) |
|
assert( dir ~= nil and file ~= nil ) |
|
local clientData = assert( self.clients[clientId] ) |
|
for mount, path in pairs( clientData.config.mappings ) do |
|
if string.sub( dir, 1, #mount ) == mount then |
|
local s = string.sub( dir, #mount, #mount ) |
|
assert( s == "/" or s == "\\" ) |
|
local r = string.sub( dir, #mount + 1 ) |
|
if r ~= "" then r = r.."/" end |
|
local localPath = grldc.utilities.normalizePath( path.."/"..r..file, lfs.currentdir() ) |
|
--print( localPath ) |
|
if lfs.attributes( localPath, "mode" ) == "file" then |
|
return localPath, dir, file |
|
end |
|
end |
|
end |
|
return nil, dir, file |
|
end |
|
|
|
function meta.__index:getRemoteSource_( clientId, localSource ) |
|
print( "Searching remote source corresponding to local source "..localSource ) |
|
local _, _, dir, file = string.find( localSource, "(.*[/\\])(.*)" ) |
|
assert( dir ~= nil and file ~= nil ) |
|
local clientData = assert( self.clients[clientId] ) |
|
local bestScore = -1 |
|
local bestPath = nil |
|
for mount, path in pairs( clientData.config.mappings ) do |
|
if string.sub( dir, 1, #path ) == path then |
|
local s = string.sub( dir, #path+1, #path+1 ) |
|
if s == "/" or s == "\\" then |
|
local r = string.sub( dir, #path + 2 ) |
|
if r ~= "" then r = r.."/" end |
|
local remotePath = grldc.utilities.normalizePath( mount..r..file ) |
|
local score = 0 |
|
string.gsub( mount, "[/\\]", function() score = score + 1 end ) |
|
print( "Candidate (score="..score..") : "..remotePath ) |
|
if score > bestScore then |
|
bestScore = score |
|
bestPath = remotePath |
|
end |
|
end |
|
end |
|
end |
|
return bestPath, dir |
|
end |
|
|
|
function meta.__index:setSourceFocus_( source, line ) |
|
local exist = (self.window:findSourcePage( source ) ~= nil) |
|
local page = self.window:getSourcePage( source ) |
|
page:setFocus( line ) |
|
self.window:setSourcePageFocus( source ) |
|
if not exist then |
|
for id, clientData in pairs( self.clients ) do |
|
clientData.config.dirty = true |
|
end |
|
end |
|
end |
|
|
|
function meta.__index:refreshCallstack_( callstack ) |
|
if type( callstack ) == "string" then |
|
self.window.callstack:setData( { { name = callstack, type = "", source = "", line = "" } } ) |
|
elseif callstack[1] == nil then |
|
self.window.callstack:setData( { { name = "empty callstack", type = "", source = "", line = "" } } ) |
|
else |
|
local callstackData = {} |
|
for level, data in ipairs( callstack ) do |
|
local entry = {} |
|
if data.namewhat ~= "" then |
|
assert( data.name ~= nil ) |
|
entry.name = "["..data.namewhat.."] "..data.name |
|
else |
|
entry.name = "???" |
|
end |
|
entry.type = data.what |
|
entry.source = data.source |
|
if data.line < 0 then |
|
entry.line = "" |
|
else |
|
entry.line = tostring( data.line ) |
|
end |
|
callstackData[level] = entry |
|
end |
|
self.window.callstack:setData( callstackData ) |
|
end |
|
end |
|
|
|
function meta.__index:refreshThreads_() |
|
local data = {} |
|
for clientId, clientData in pairs( self.clients ) do |
|
local client = self.engine.getClient( clientId ) |
|
if client == nil then |
|
print( "Client does not exist anymore: "..clientId ) |
|
if self.activeClient == clientId then |
|
self.activeClient = nil |
|
self:invalidateState_( true ) |
|
end |
|
self.clients[clientId] = nil |
|
else |
|
local cdata = {} |
|
cdata.name = client:name() |
|
cdata.ip = client:ip() |
|
cdata.clientId = client.clientId |
|
cdata.coroutines = {} |
|
cdata.status = client:status() |
|
cdata.active = (clientId == self.activeClient) |
|
cdata.breakOnConnection = clientData.config.breakOnConnection |
|
|
|
if cdata.status == "break" then |
|
local current = client:getcurrentthread() |
|
local active = client:getactivethread() |
|
if active == "current" then |
|
active = current |
|
end |
|
table.insert( cdata.coroutines, { id = "main", current = (current == "main"), active = (active=="main" and cdata.clientId == self.activeClient and client:getactivethread() ~= "current") } ) |
|
|
|
local coroutines = client:coroutines() |
|
--print( coroutines ) |
|
for _, data in ipairs( coroutines ) do |
|
local codata = {} |
|
codata.id = data.id |
|
codata.current = (current == codata.id) |
|
codata.active = (active == codata.id and cdata.clientId == self.activeClient and client:getactivethread() ~= "current") |
|
table.insert( cdata.coroutines, codata ) |
|
end |
|
end |
|
|
|
table.insert( data, cdata ) |
|
end |
|
end |
|
self.window.threads:setData( data ) |
|
end |
|
|
|
function meta.__index:setActiveClient_( clientId ) |
|
self:invalidateState_( true ) |
|
self.activeClient = clientId |
|
self.clients[clientId].dirty = true |
|
self.threadsDirty = true |
|
local clientConfig = self.clients[clientId].config |
|
for source, lines in pairs( self.configs.global.breakpoints ) do |
|
for line, _ in pairs( lines ) do |
|
if clientConfig.breakpoints[source] == nil or not clientConfig.breakpoints[source][line] then |
|
self:onBreakPointChanged_( source, line ) |
|
end |
|
lines[line] = nil |
|
end |
|
end |
|
self:refreshBreakPoints_() |
|
end |
|
|
|
function meta.__index:onNewClient_( clientId ) |
|
local client = self.engine.getClient( clientId ) |
|
local name = client:name() |
|
if self.configs[name] == nil then |
|
self.configs[name] = { name = name, mappings = {}, breakpoints = {} } |
|
self:loadConfig_( name ) |
|
end |
|
self.clients[clientId] = { dirty = true, activeThread = "current", activeLevel = 1, config = self.configs[name] } |
|
|
|
for source, lines in pairs( self.configs[name].breakpoints ) do |
|
assert( string.sub( source, 1, 1 ) == "@" ) |
|
source = string.sub( source, 2 ) |
|
local remoteSource, dir = self:getRemoteSource_( clientId, source ) |
|
if remoteSource ~= nil then |
|
for line, value in pairs( lines ) do |
|
if value then |
|
client:setbreakpoint( "@"..remoteSource, line, true ) |
|
end |
|
end |
|
end |
|
end |
|
|
|
self.threadsDirty = true |
|
if self.activeClient == nil then |
|
self:setActiveClient_( clientId ) |
|
end |
|
|
|
if not self.configs[name].breakOnConnection then |
|
self.clients[clientId].ignoreNextBreak = true |
|
end |
|
end |
|
|
|
function meta.__index:onClientBreak_( clientId ) |
|
local clientData = assert( self.clients[clientId] ) |
|
clientData.invalidateTimer = nil |
|
clientData.dirty = true |
|
clientData.activeLevel = 1 |
|
self.threadsDirty = true |
|
|
|
if clientData.ignoreNextBreak then |
|
clientData.ignoreNextBreak = false |
|
self:onDebugCommand_( "run", "break", clientId ) |
|
end |
|
end |
|
|
|
function meta.__index:onClientLost_( clientId ) |
|
local clientData = assert( self.clients[clientId] ) |
|
if clientData.config.dirty then |
|
self:saveConfig_( clientData.config.name ) |
|
end |
|
self.threadsDirty = true |
|
end |
|
|
|
function meta.__index:saveConfig_( name ) |
|
local clientConfig = assert( self.configs[name] ) |
|
clientConfig.dirty = false |
|
local name = clientConfig.name |
|
local openFiles = {} |
|
for source, page in pairs( self.window:getSourcePages() ) do |
|
openFiles[page.pageIdx+1] = source |
|
end |
|
local breakpoints = clientConfig.breakpoints |
|
|
|
local path = "clients/"..name.."/config.lua" |
|
lfs.mkdir( "clients" ) |
|
lfs.mkdir( "clients/"..name ) |
|
local file = assert( io.open( path, "w" ) ) |
|
file:write( grldc.net.serialize( { mappings = clientConfig.mappings, openFiles = openFiles, breakpoints = breakpoints, breakOnConnection = clientConfig.breakOnConnection } ) ) |
|
file:close() |
|
print( "Saved config \""..name.."\"" ) |
|
clientConfig.lastConfigSave = os.time() |
|
end |
|
|
|
function meta.__index:loadConfig_( name ) |
|
local clientConfig = self.configs[name] |
|
local path = "clients/"..name.."/config.lua" |
|
local file = io.open( path, "r" ) |
|
if file ~= nil then |
|
local config = grldc.net.deserialize( file:read( "*a" ) ) |
|
file:close() |
|
clientConfig.mappings = config.mappings |
|
clientConfig.breakOnConnection = config.breakOnConnection |
|
for _, file in ipairs( config.openFiles ) do |
|
self.window:getSourcePage( file ) |
|
end |
|
if config.breakpoints ~= nil then |
|
for source, bp in pairs( config.breakpoints ) do |
|
if clientConfig.breakpoints[source] == nil then |
|
clientConfig.breakpoints[source] = {} |
|
end |
|
for line, _ in pairs( bp ) do |
|
clientConfig.breakpoints[source][line] = true |
|
end |
|
end |
|
end |
|
end |
|
if clientConfig.breakOnConnection == nil then |
|
clientConfig.breakOnConnection = true |
|
end |
|
clientConfig.dirty = false |
|
end |
|
|
|
function complexValueManagerMeta.init( variables, engine, clientId ) |
|
local manager = nil |
|
for _, entry in pairs( variables ) do |
|
if type( entry.value ) == "table" and entry.value.id ~= nil then |
|
if manager == nil then |
|
manager = { engine = engine, clientId = clientId } |
|
end |
|
assert( entry.value.manager == nil ) |
|
entry.value.manager = manager |
|
setmetatable( entry.value, complexValueManagerMeta ) |
|
end |
|
end |
|
end |
|
|
|
function complexValueManagerMeta.__index:release() |
|
local client = self.manager.engine.getClient( self.manager.clientId ) |
|
if client == nil then return end |
|
client:releaseValue( self.id ) |
|
end |
|
|
|
function complexValueManagerMeta.__index:get() |
|
local client = self.manager.engine.getClient( self.manager.clientId ) |
|
if client == nil then return { ERROR = "connection with client lost" } end |
|
local value = client:getValue( self.id ) |
|
complexValueManagerMeta.init( value, self.manager.engine, self.manager.clientId ) |
|
return value |
|
end
|
|
|