diff --git a/README.md b/README.md index af0472b..492db13 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +### NOTE +This is a modified fork of the [original GRLD project](https://github.com/neuoy/GRLD/master/) for personal use and should be considered **unstable**. Anyone looking to use GRLD should check out the [official repo](https://github.com/neuoy/GRLD/master/) unless they are specifically looking for the modifications provided in this version. The original readme contents remain below. + + # GRLD Graphical Remote Lua Debugger, a debugger for the lua programming language diff --git a/server/wxLdb/ui/mainWindow.lua b/server/wxLdb/ui/mainWindow.lua index 98f9e50..5d9d176 100644 --- a/server/wxLdb/ui/mainWindow.lua +++ b/server/wxLdb/ui/mainWindow.lua @@ -57,16 +57,22 @@ end function meta.__index:init() self.frame = wx.wxFrame( wx.NULL, wx.wxID_ANY, "GRLD server", wx.wxDefaultPosition, wx.wxDefaultSize, wx.wxDEFAULT_FRAME_STYLE + wx.wxMAXIMIZE) mainthread.init( self.frame ) - + self:initLayout_() - + self.mountPathPopup = ui.promptMountPath.new() self.notificationPopup = ui.notification.new() - + self.idleUpdates = {} self.frame:Connect( wx.wxEVT_IDLE, function( event ) self:onIdleUpdate_( event ) end ) - - self.events = { onBreakPointChanged = {}, onFileOpen = {}, onFileClosed = {}, onApplicationExiting = {} } + + self.events = { + onBreakPointChanged = {}, + onFileOpen = {}, + onFileClosed = {}, + onApplicationExiting = {}, + onScrollChanged = {} + } end function meta.__index:show( show ) @@ -81,37 +87,37 @@ function meta.__index:initLayout_() self.root = wx.wxSplitterWindow( self.frame, wx.wxID_ANY, wx.wxDefaultPosition, wx.wxDefaultSize, 0 ) -- root widget self.sourceBook = wx.wxNotebook( self.root, wx.wxID_ANY ) -- book of source code pages self.debugRoot = wx.wxSplitterWindow( self.root, wx.wxID_ANY, wx.wxDefaultPosition, wx.wxDefaultSize, 0 ) - + -- threads window self.threads = ui.threads.new( self.debugRoot, self.frame ) - + self.debugBooks = wx.wxSplitterWindow( self.debugRoot, wx.wxID_ANY, wx.wxDefaultPosition, wx.wxDefaultSize, 0 ) self.debugBookL = wx.wxNotebook( self.debugBooks, wx.wxID_ANY, wx.wxDefaultPosition, wx.wxDefaultSize, wx.wxNB_BOTTOM ) self.debugBookR = wx.wxNotebook( self.debugBooks, wx.wxID_ANY, wx.wxDefaultPosition, wx.wxDefaultSize, wx.wxNB_BOTTOM ) - + -- callstack window self.callstack = ui.callstack.new( self.debugBookL ) self.debugBookL:AddPage( self.callstack:getRoot(), "Call stack" ) - + -- automatic variables window self.auto = ui.luaExplorer.new( self.debugBookR, false ) self.debugBookR:AddPage( self.auto:getRoot(), "Automatic variables" ) - + -- watch window self.watch = ui.luaExplorer.new( self.debugBookR, true ) self.debugBookR:AddPage( self.watch:getRoot(), "Watch" ) - + self.root:SplitHorizontally( self.sourceBook, self.debugRoot, -200 ) self.debugRoot:SplitVertically( self.threads:getRoot(), self.debugBooks, 250 ) self.debugBooks:SplitVertically( self.debugBookL, self.debugBookR ) - + self.sourcePages = {} - + local fileMenu = wx.wxMenu() fileMenu:Append( ID_FILE_OPEN, "&Open\tCtrl-O", "Open a source file" ) fileMenu:Append( ID_FILE_CLOSE, "&Close\tCtrl-F4", "Close the current source file" ) fileMenu:Append( ID_EXIT, "E&xit\tAlt-F4", "Exit the GRLD server" ) - + local debugMenu = wx.wxMenu() debugMenu:Append( ID_BREAK, "&Break\tF12", "Stop execution of the program at the next executed line of code" ) debugMenu:Append( ID_CONTINUE, "&Continue\tF5", "Run the program at full speed" ) @@ -119,17 +125,17 @@ function meta.__index:initLayout_() debugMenu:Append( ID_STEP_INTO, "Step &into\tF11", "Step into a line" ) debugMenu:Append( ID_STEP_OUT, "Step &out\tShift-F11", "Step out of the current function" ) debugMenu:Append( ID_TOGGLE_BREAKPOINT, "&Toggle breakpoint\tF9", "Toggle the breakpoint on the current line" ) - + local helpMenu = wx.wxMenu() helpMenu:Append( ID_HELP_MANUAL, "&Manual", "Send the GRLD manual to your web browser" ) helpMenu:Append( ID_HELP_ABOUT, "&About", "Open a window with various information about GRLD" ) - + local menuBar = wx.wxMenuBar() menuBar:Append( fileMenu, "&File" ) menuBar:Append( debugMenu, "&Debug" ) menuBar:Append( helpMenu, "&Help" ) self.frame:SetMenuBar( menuBar ) - + self.frame:Connect( ID_FILE_OPEN, wx.wxEVT_COMMAND_MENU_SELECTED, function( ... ) self:onFileOpen_( ... ) end ) self.frame:Connect( ID_FILE_CLOSE, wx.wxEVT_COMMAND_MENU_SELECTED, function( ... ) self:onFileClose_( ... ) end ) self.frame:Connect( ID_HELP_MANUAL, wx.wxEVT_COMMAND_MENU_SELECTED, function( ... ) self:onHelpManual_( ... ) end ) @@ -170,7 +176,7 @@ function meta.__index:onFileOpen_( event ) fullPath = fileDialog:GetPath() end fileDialog:Destroy() - + if fullPath ~= nil then self:runEvents_( "onFileOpen", fullPath ) end @@ -199,22 +205,22 @@ end function meta.__index:onHelpManual_() local docPath = "../doc/index.html" - + -- get the command to start the default web browser from the registry (MS Windows only) local pipe = io.popen( "reg query HKEY_CLASSES_ROOT\\HTTP\\shell\\open\\command" ) local data = pipe:read( "*a" ) pipe:close() - + -- example of returned data (Windows XP): --! REG.EXE VERSION 3.0 -- --HKEY_CLASSES_ROOT\HTTP\shell\open\command -- REG_SZ "E:\Outils\Firefox\firefox.exe" -requestPending -osint -url "%1" - + -- other example (on Vista) --HKEY_CLASSES_ROOT\HTTP\shell\open\command -- (par défaut) REG_SZ "C:\Program Files\Mozilla Firefox\firefox.exe" -requestPending -osint -url "%1" - + -- parse returned data local _, _, cmd = string.find( data, "REG_SZ%s+(.+)" ) local result = -1 @@ -228,7 +234,7 @@ function meta.__index:onHelpManual_() cmd = cmd.."\""..docPath.."\"" end print( cmd ) - + -- start the default browser with the GRLD documentation as parameter result = os.execute( "start \"GRLD documentation\" "..cmd ) end @@ -272,6 +278,7 @@ function meta.__index:getSourcePage( source ) page.pageIdx = self.sourceBook:GetPageCount() self.sourceBook:AddPage( page:getRoot(), name ) page:registerEvent( "onBreakPointChanged", function( ... ) self:runEvents_( "onBreakPointChanged", source, ... ) end ) + page:registerEvent( "onScrollChanged", function( ... ) self:runEvents_( "onScrollChanged", source, ... ) end ) end return page end @@ -341,6 +348,10 @@ function meta.__index:setActive() self.active = true end +function meta.__index:raise() + self.frame:Raise() +end + function meta.__index:onIdleUpdate_( event ) local currentPageIdx = self.sourceBook:GetSelection() for _, page in pairs( self.sourcePages ) do @@ -351,7 +362,7 @@ function meta.__index:onIdleUpdate_( event ) end end end - + self.active = false for _, func in pairs( self.idleUpdates ) do local ok, msg = xpcall( func, debug.traceback ) diff --git a/server/wxLdb/ui/sourcePage.lua b/server/wxLdb/ui/sourcePage.lua index 42a9bfe..d426676 100644 --- a/server/wxLdb/ui/sourcePage.lua +++ b/server/wxLdb/ui/sourcePage.lua @@ -5,6 +5,8 @@ local ui = editor = require( "ui.editor" ), } +local wx = require( "wx" ) + local lfs = require( "lfs" ) local assert = assert @@ -23,7 +25,10 @@ function new( parent, source ) local page = {} setmetatable( page, meta ) page.editor = ui.editor.new( parent ) - page.events = { onBreakPointChanged = {} } + page.events = { + onBreakPointChanged = {}, + onScrollChanged = {}, + } page:setSource_( source ) page.editor.breakpointCallback = function( line ) page:runEvents_( "onBreakPointChanged", line ) @@ -53,6 +58,9 @@ function meta.__index:update() assert( string.sub( self.source, 1, 1 ) == "@" ) local fileName = string.sub( self.source, 2 ) + local scrollPosition = self:GetScrollPos() + self:runEvents_( "onScrollChanged", scrollPosition ) + local newDate = lfs.attributes( fileName, "modification" ) or 0 if newDate > self.sourceDate then print( "reloading source file "..fileName ) @@ -78,6 +86,14 @@ function meta.__index:getFocus() return ed:GetCurrentLine() + 1 end +function meta.__index:SetScrollPos( pos ) + self.editor.editor:LineScroll(0, pos) +end + +function meta.__index:GetScrollPos() + return self.editor.editor:GetScrollPos( wx.wxVERTICAL ) +end + function meta.__index:setCurrentLine( line ) local editor = self.editor.editor if self.currentLine == line then return end diff --git a/server/wxLdb/wxLdbController.lua b/server/wxLdb/wxLdbController.lua index afd5cec..514d68a 100644 --- a/server/wxLdb/wxLdbController.lua +++ b/server/wxLdb/wxLdbController.lua @@ -11,6 +11,8 @@ local ui = } local lfs = require( "lfs" ) +local wx = require( "wx" ) + require( "coxpcall" ) local coxpcall = coxpcall local copcall = copcall @@ -37,6 +39,15 @@ local meta = { __index = {} } local complexValueManagerMeta = { __index = {} } +local function createClientConfig(name) + return { + name = name, + mappings = {}, + breakpoints = {}, + scrollPositions = {}, + } +end + function new( engine, window ) local res = { @@ -101,9 +112,9 @@ 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 ) @@ -114,7 +125,7 @@ function meta.__index:run_() 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 @@ -122,30 +133,31 @@ function meta.__index:run_() 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( "onScrollChanged", wrapCb(function( ... ) self:onScrollChanged_( ...) 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.configs.global = createClientConfig('global') self:loadConfig_( "global" ) - + self:sleep_() while not self.exiting do for clientId, clientData in pairs( self.clients ) do @@ -188,18 +200,34 @@ function meta.__index:run_() 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:getActiveClientConfig_() + local activeClientId = self.activeClient + + if activeClientId == nil then + return self.configs.global + end + + local clientData = self.clients[activeClientId] + + if clientData == nil then + return self.configs.global + end + + return clientData.config +end + function meta.__index:evaluateExpression_( expr ) local clientId = self.activeClient local client = self.engine.getClient( clientId ) @@ -224,6 +252,7 @@ function meta.__index:refreshSourcePageFocus_( remoteSource, line ) local clientData = assert( self.clients[clientId] ) local sourceType = string.sub( remoteSource, 1, 1 ) if sourceType == "@" then + self.window:raise() print( "Setting focus to "..remoteSource.."("..line..")" ) remoteSource = grldc.utilities.normalizePath( string.sub( remoteSource, 2 ) ) local source, remotePath, remoteFile = self:getLocalSource_( clientId, remoteSource ) @@ -243,16 +272,16 @@ function meta.__index:refreshSourcePageFocus_( remoteSource, line ) clientData.config.mappings[mount] = path clientData.config.dirty = true source = self:getLocalSource_( clientId, remoteSource ) - assert( source ~= nil ) + 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 @@ -264,12 +293,12 @@ function meta.__index:refreshSourceFocus_( callstack, level ) 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 @@ -333,10 +362,10 @@ function meta.__index:onBreakPointChanged_( source, line ) 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 @@ -345,9 +374,9 @@ function meta.__index:onBreakPointChanged_( 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 @@ -375,7 +404,7 @@ function meta.__index:onBreakPointChanged_( source, line ) assert( remoteSource ~= nil ) end end - + if remoteSource ~= nil then client:setbreakpoint( "@"..remoteSource, line, newValue ) clientData.config.dirty = true @@ -396,6 +425,17 @@ function meta.__index:onApplicationExiting_() self.window.threads:setData( nil ) end +function meta.__index:refreshScrollPosition_() + local config = self:getActiveClientConfig_() + + for source, sp in pairs( config.scrollPositions ) do + local page = self.window:getSourcePage( source ) + if page ~= nil then + page:SetScrollPos(sp) + end + end +end + function meta.__index:refreshBreakPoints_() local clientId = self.activeClient local config = nil @@ -406,14 +446,14 @@ function meta.__index:refreshBreakPoints_() 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 ) ) @@ -437,7 +477,7 @@ function meta.__index:refreshBreakPoints_() end end end - + for source, lines in pairs( config.breakpoints ) do local page = self.window:findSourcePage( source ) if page ~= nil then @@ -459,6 +499,7 @@ function meta.__index:onFileOpen_( path ) self:setSourceFocus_( source, 1 ) self:refreshPointers_() self:refreshBreakPoints_() + self:refreshScrollPosition_() end function meta.__index:onFileClosed_( source ) @@ -467,6 +508,12 @@ function meta.__index:onFileClosed_( source ) end end +function meta.__index:onScrollChanged_( source, position ) + local clientConfig = self:getActiveClientConfig_() + clientConfig.scrollPositions[source] = position + clientConfig.dirty = true +end + function meta.__index:onThreadClicked_( clientId, threadId ) print( "Thread clicked: client="..clientId..", thread="..threadId ) local clientData = self.clients[clientId] @@ -492,9 +539,9 @@ function meta.__index:onDebugCommand_( command, neededState, 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 @@ -514,7 +561,7 @@ function meta.__index:invalidateState_( immediate ) 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() @@ -636,7 +683,7 @@ function meta.__index:refreshThreads_() 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() @@ -644,7 +691,7 @@ function meta.__index:refreshThreads_() 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 @@ -655,7 +702,7 @@ function meta.__index:refreshThreads_() table.insert( cdata.coroutines, codata ) end end - + table.insert( data, cdata ) end end @@ -683,15 +730,15 @@ 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.configs[name] = createClientConfig(name) 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 ) + local remoteSource, dir = self:getRemoteSource_( clientId, source ) if remoteSource ~= nil then for line, value in pairs( lines ) do if value then @@ -700,12 +747,12 @@ function meta.__index:onNewClient_( clientId ) 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 @@ -717,7 +764,7 @@ function meta.__index:onClientBreak_( clientId ) clientData.dirty = true clientData.activeLevel = 1 self.threadsDirty = true - + if clientData.ignoreNextBreak then clientData.ignoreNextBreak = false self:onDebugCommand_( "run", "break", clientId ) @@ -741,12 +788,19 @@ function meta.__index:saveConfig_( name ) openFiles[page.pageIdx+1] = source end local breakpoints = clientConfig.breakpoints - + local scrollPositions = clientConfig.scrollPositions + 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:write( grldc.net.serialize( { + mappings = clientConfig.mappings, + openFiles = openFiles, + breakpoints = breakpoints, + scrollPositions = scrollPositions, + breakOnConnection = clientConfig.breakOnConnection + } ) ) file:close() print( "Saved config \""..name.."\"" ) clientConfig.lastConfigSave = os.time() @@ -774,6 +828,12 @@ function meta.__index:loadConfig_( name ) end end end + + if config.scrollPositions ~= nil then + for source, sp in pairs( config.scrollPositions ) do + clientConfig.scrollPositions[source] = sp + end + end end if clientConfig.breakOnConnection == nil then clientConfig.breakOnConnection = true