From 882e68542e9dc6b37fefe01492ef14c6b7894b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 12 Jan 2025 20:29:44 -0300 Subject: [PATCH] Added expressions support in debugger. Persist debugger state. --- src/tools/ecode/appconfig.cpp | 44 +- src/tools/ecode/appconfig.hpp | 4 +- src/tools/ecode/ecode.cpp | 4 +- .../debugger/debuggerclientlistener.cpp | 134 +++--- .../debugger/debuggerclientlistener.hpp | 3 + .../ecode/plugins/debugger/debuggerplugin.cpp | 404 ++++++++++++++++-- .../ecode/plugins/debugger/debuggerplugin.hpp | 20 + .../debugger/models/variablesmodel.cpp | 26 +- .../debugger/models/variablesmodel.hpp | 5 +- .../debugger/statusdebuggercontroller.cpp | 14 +- .../debugger/statusdebuggercontroller.hpp | 4 + src/tools/ecode/plugins/plugin.hpp | 7 + src/tools/ecode/plugins/pluginmanager.cpp | 7 +- src/tools/ecode/plugins/pluginmanager.hpp | 2 + 14 files changed, 559 insertions(+), 119 deletions(-) diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index ef4131616..d47bc7ba8 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -1,5 +1,6 @@ #include "appconfig.hpp" #include "ecode.hpp" +#include "plugins/plugin.hpp" #include "plugins/pluginmanager.hpp" #include "version.hpp" #include @@ -443,7 +444,7 @@ json saveNode( Node* node ) { void AppConfig::saveProject( std::string projectFolder, UICodeEditorSplitter* editorSplitter, const std::string& configPath, const ProjectDocumentConfig& docConfig, const ProjectBuildConfiguration& buildConfig, bool onlyIfNeeded, - bool sessionSnapshot ) { + bool sessionSnapshot, PluginManager* pluginManager ) { FileSystem::dirAddSlashAtEnd( projectFolder ); std::string projectsPath( configPath + "projects" + FileSystem::getOSSlash() ); if ( !FileSystem::fileExists( projectsPath ) ) @@ -481,8 +482,27 @@ void AppConfig::saveProject( std::string projectFolder, UICodeEditorSplitter* ed } else { cfg.writeFile(); } + + if ( pluginManager ) { + std::string pluginsStatePath( projectsPath + "plugins_state" ); + if ( !FileSystem::fileExists( pluginsStatePath ) && + !FileSystem::makeDir( pluginsStatePath ) ) + return; + std::string projectPluginsStatePath( pluginsStatePath + FileSystem::getOSSlash() + + hash.toHexString() ); + if ( !FileSystem::fileExists( projectPluginsStatePath ) && + !FileSystem::makeDir( projectPluginsStatePath ) ) + return; + FileSystem::dirAddSlashAtEnd( projectPluginsStatePath ); + pluginManager->forEachPlugin( + [&projectFolder, &projectPluginsStatePath, onlyIfNeeded]( Plugin* plugin ) { + plugin->onSaveProject( projectFolder, projectPluginsStatePath, onlyIfNeeded ); + } ); + } + if ( !sessionSnapshot ) return; + std::string statePath( projectsPath + "state" ); if ( !FileSystem::fileExists( statePath ) && !FileSystem::makeDir( statePath ) ) return; @@ -490,6 +510,7 @@ void AppConfig::saveProject( std::string projectFolder, UICodeEditorSplitter* ed if ( !FileSystem::fileExists( projectStatePath ) && !FileSystem::makeDir( projectStatePath ) ) return; FileSystem::dirAddSlashAtEnd( projectStatePath ); + nlohmann::json j = nlohmann::json::array(); std::vector fileNames; editorSplitter->forEachDocSharedPtr( @@ -671,7 +692,7 @@ void AppConfig::loadDocuments( UICodeEditorSplitter* editorSplitter, json j, void AppConfig::loadProject( std::string projectFolder, UICodeEditorSplitter* editorSplitter, const std::string& configPath, ProjectDocumentConfig& docConfig, - ecode::App* app, bool sessionSnapshot ) { + ecode::App* app, bool sessionSnapshot, PluginManager* pluginManager ) { FileSystem::dirAddSlashAtEnd( projectFolder ); std::string projectsPath( configPath + "projects" + FileSystem::getOSSlash() ); MD5::Result hash = MD5::fromString( projectFolder ); @@ -709,11 +730,12 @@ void AppConfig::loadProject( std::string projectFolder, UICodeEditorSplitter* ed std::vector sessionSnapshotFiles; if ( sessionSnapshot ) { std::string projectStatePath( projectsPath + "state" + FileSystem::getOSSlash() + - hash.toHexString() + FileSystem::getOSSlash() + - "state.json" ); - if ( FileSystem::fileExists( projectStatePath ) ) { + hash.toHexString() + FileSystem::getOSSlash() ); + + std::string projectStateFilePath( projectStatePath + "state.json" ); + if ( FileSystem::fileExists( projectStateFilePath ) ) { std::string stateStr; - FileSystem::fileGet( projectStatePath, stateStr ); + FileSystem::fileGet( projectStateFilePath, stateStr ); json j; try { j = json::parse( stateStr ); @@ -776,6 +798,16 @@ void AppConfig::loadProject( std::string projectFolder, UICodeEditorSplitter* ed }, curTabWidget ); } + + if ( pluginManager ) { + std::string projectPluginsStatePath( projectsPath + "plugins_state" + + FileSystem::getOSSlash() + hash.toHexString() + + FileSystem::getOSSlash() ); + + pluginManager->forEachPlugin( [&projectFolder, &projectPluginsStatePath]( Plugin* plugin ) { + plugin->onLoadProject( projectFolder, projectPluginsStatePath ); + } ); + } } bool AppConfig::isNewVersion() const { diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index f0ed17dd6..8874badc5 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -220,11 +220,11 @@ class AppConfig { void saveProject( std::string projectFolder, UICodeEditorSplitter* editorSplitter, const std::string& configPath, const ProjectDocumentConfig& docConfig, const ProjectBuildConfiguration& buildConfig, bool onlyIfNeeded, - bool sessionSnapshot ); + bool sessionSnapshot, PluginManager* ); void loadProject( std::string projectFolder, UICodeEditorSplitter* editorSplitter, const std::string& configPath, ProjectDocumentConfig& docConfig, - ecode::App* app, bool sessionSnapshot ); + ecode::App* app, bool sessionSnapshot, PluginManager* pluginManager ); protected: Int64 editorsToLoad{ 0 }; diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index c2c39e765..864ea41a9 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -1908,7 +1908,7 @@ void App::saveProject( bool onlyIfNeeded, bool sessionSnapshotEnabled ) { mConfig.saveProject( mCurrentProject, mSplitter, mConfigPath, mProjectDocConfig, mProjectBuildManager ? mProjectBuildManager->getConfig() : ProjectBuildConfiguration(), - onlyIfNeeded, sessionSnapshotEnabled && mConfig.workspace.sessionSnapshot ); + onlyIfNeeded, sessionSnapshotEnabled && mConfig.workspace.sessionSnapshot, mPluginManager.get() ); } } @@ -3205,7 +3205,7 @@ void App::loadFolder( std::string path ) { mProjectBuildManager = std::make_unique( rpath, mThreadPool, mSidePanel, this ); mConfig.loadProject( rpath, mSplitter, mConfigPath, mProjectDocConfig, this, - mConfig.workspace.sessionSnapshot ); + mConfig.workspace.sessionSnapshot, mPluginManager.get() ); Log::info( "Load project took: %.2f ms", projClock.getElapsedTime().asMilliseconds() ); loadFileSystemMatcher( rpath ); diff --git a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp index 132a9b27f..2993b884a 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp @@ -5,6 +5,8 @@ #include "models/stackmodel.hpp" #include "models/threadsmodel.hpp" #include "models/variablesmodel.hpp" +#include +#include namespace ecode { @@ -37,63 +39,64 @@ DebuggerClientListener::~DebuggerClientListener() { } } +void DebuggerClientListener::initUI() { + auto sdc = getStatusDebuggerController(); + + sdc->createWidget(); + + mPlugin->getManager()->getPluginContext()->getStatusAppOutputController()->initNewOutput( + {}, false ); + + UISceneNode* sceneNode = mPlugin->getUISceneNode(); + + if ( !mThreadsModel ) { + mThreadsModel = std::make_shared( std::vector{}, sceneNode ); + mThreadsModel->setCurrentThreadId( mCurrentThreadId ); + } + + if ( !mStackModel ) { + mStackModel = std::make_shared( StackTraceInfo{}, sceneNode ); + } + + UITableView* uiThreads = sdc->getUIThreads(); + uiThreads->setModel( mThreadsModel ); + + uiThreads->removeEventsOfType( Event::OnModelEvent ); + uiThreads->onModelEvent( [this]( const ModelEvent* modelEvent ) { + if ( modelEvent->getModelEventType() == Abstract::ModelEventType::Open ) { + auto model = static_cast( modelEvent->getModel() ); + mClient->stackTrace( model->getThread( modelEvent->getModelIndex().row() ).id ); + } + } ); + + UITableView* uiStack = sdc->getUIStack(); + uiStack->setModel( mStackModel ); + uiStack->removeEventsOfType( Event::OnModelEvent ); + uiStack->onModelEvent( [this]( const ModelEvent* modelEvent ) { + if ( modelEvent->getModelEventType() == Abstract::ModelEventType::Open ) { + auto model = static_cast( modelEvent->getModel() ); + const auto& stack = model->getStack( modelEvent->getModelIndex().row() ); + changeScope( stack ); + } + } ); + + UITreeView* uiVariables = sdc->getUIVariables(); + uiVariables->setModel( mVariablesHolder->model ); + uiVariables->removeEventsOfType( Event::OnModelEvent ); + uiVariables->onModelEvent( [this]( const ModelEvent* modelEvent ) { + if ( modelEvent->getModelEventType() == Abstract::ModelEventType::OpenTree ) { + ModelVariableNode* node = + static_cast( modelEvent->getModelIndex().internalData() ); + mClient->variables( node->var.variablesReference ); + } + } ); + + mPlugin->setUIDebuggingState( StatusDebuggerController::State::Running ); +} + void DebuggerClientListener::stateChanged( DebuggerClient::State state ) { if ( state == DebuggerClient::State::Initializing ) { - mPlugin->getManager()->getUISceneNode()->runOnMainThread( [this] { - getStatusDebuggerController()->createWidget(); - - mPlugin->getManager() - ->getPluginContext() - ->getStatusAppOutputController() - ->initNewOutput( {}, false ); - - UISceneNode* sceneNode = mPlugin->getUISceneNode(); - - if ( !mThreadsModel ) { - mThreadsModel = - std::make_shared( std::vector{}, sceneNode ); - mThreadsModel->setCurrentThreadId( mCurrentThreadId ); - } - - if ( !mStackModel ) { - mStackModel = std::make_shared( StackTraceInfo{}, sceneNode ); - } - - UITableView* uiThreads = getStatusDebuggerController()->getUIThreads(); - uiThreads->setModel( mThreadsModel ); - - uiThreads->removeEventsOfType( Event::OnModelEvent ); - uiThreads->onModelEvent( [this]( const ModelEvent* modelEvent ) { - if ( modelEvent->getModelEventType() == Abstract::ModelEventType::Open ) { - auto model = static_cast( modelEvent->getModel() ); - mClient->stackTrace( model->getThread( modelEvent->getModelIndex().row() ).id ); - } - } ); - - UITableView* uiStack = getStatusDebuggerController()->getUIStack(); - uiStack->setModel( mStackModel ); - uiStack->removeEventsOfType( Event::OnModelEvent ); - uiStack->onModelEvent( [this]( const ModelEvent* modelEvent ) { - if ( modelEvent->getModelEventType() == Abstract::ModelEventType::Open ) { - auto model = static_cast( modelEvent->getModel() ); - const auto& stack = model->getStack( modelEvent->getModelIndex().row() ); - changeScope( stack ); - } - } ); - - UITreeView* uiVariables = getStatusDebuggerController()->getUIVariables(); - uiVariables->setModel( mVariablesHolder->model ); - uiVariables->removeEventsOfType( Event::OnModelEvent ); - uiVariables->onModelEvent( [this]( const ModelEvent* modelEvent ) { - if ( modelEvent->getModelEventType() == Abstract::ModelEventType::OpenTree ) { - ModelVariableNode* node = static_cast( - modelEvent->getModelIndex().internalData() ); - mClient->variables( node->var.variablesReference ); - } - } ); - - mPlugin->setUIDebuggingState( StatusDebuggerController::State::Running ); - } ); + mPlugin->getManager()->getUISceneNode()->runOnMainThread( [this] { initUI(); } ); } } @@ -162,7 +165,7 @@ void DebuggerClientListener::debuggeeContinued( const ContinuedEvent& ) { resetState(); UISceneNode* sceneNode = mPlugin->getUISceneNode(); - sceneNode->runOnMainThread( [sceneNode] { sceneNode->invalidateDraw(); } ); + sceneNode->runOnMainThread( [sceneNode] { sceneNode->getRoot()->invalidateDraw(); } ); mPlugin->setUIDebuggingState( StatusDebuggerController::State::Running ); } @@ -239,6 +242,25 @@ void DebuggerClientListener::stackTrace( const int threadId, StackTraceInfo&& st } mStackModel->setStack( std::move( stack ) ); + + for ( const auto& expression : mPlugin->mExpressions ) { + mClient->evaluate( + expression, "watch", getCurrentFrameId(), + [this, expression]( const std::string&, const std::optional& info ) { + Variable var; + var.evaluateName = expression; + var.name = std::move( expression ); + if ( info ) { + var.value = info->result; + var.type = info->type; + var.variablesReference = info->variablesReference; + var.indexedVariables = info->indexedVariables; + var.namedVariables = info->namedVariables; + var.memoryReference = info->memoryReference; + } + mPlugin->mExpressionsHolder->upsertRootChild( std::move( var ) ); + } ); + } } void DebuggerClientListener::scopes( const int /*frameId*/, std::vector&& scopes ) { diff --git a/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp b/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp index 061f4f573..a186ad3f2 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp @@ -84,6 +84,9 @@ class DebuggerClientListener : public DebuggerClient::Listener { void changeScope( const StackFrame& f ); void changeThread( int id ); + + void initUI(); + }; } // namespace ecode diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp index 53559c472..d38f666e2 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp @@ -9,6 +9,7 @@ #include "bussocketprocess.hpp" #include "dap/debuggerclientdap.hpp" #include "models/breakpointsmodel.hpp" +#include "models/variablesmodel.hpp" #include "statusdebuggercontroller.hpp" #include #include @@ -82,6 +83,160 @@ DebuggerPlugin::~DebuggerPlugin() { } } +void DebuggerPlugin::onSaveProject( const std::string& /*projectFolder*/, + const std::string& projectStatePath, + bool rewriteStateOnlyIfNeeded ) { + std::string debugger( mCurDebugger ); + std::string configuration( mCurConfiguration ); + auto expressions( mExpressions ); + UnorderedMap> breakpoints; + { + Lock l( mBreakpointsMutex ); + breakpoints = mBreakpoints; + } + mThreadPool->run( + [this, debugger = std::move( debugger ), configuration = std::move( configuration ), + expressions = std::move( expressions ), breakpoints = std::move( breakpoints ), + projectStatePath, rewriteStateOnlyIfNeeded] { + nlohmann::json j; + auto& config = j["config"]; + config["debugger"] = std::move( debugger ); + config["configuration"] = std::move( configuration ); + auto expArr = nlohmann::json::array(); + for ( auto& expression : expressions ) + expArr.push_back( expression ); + config["expressions"] = std::move( expArr ); + + nlohmann::json bpsArr; + for ( auto& bpSet : breakpoints ) { + auto bpArr = nlohmann::json::array(); + for ( auto& bp : bpSet.second ) { + nlohmann::json jbp; + jbp["line"] = bp.line; + jbp["enabled"] = bp.enabled; + bpArr.push_back( jbp ); + } + bpsArr[bpSet.first] = std::move( bpArr ); + } + config["breakpoints"] = std::move( bpsArr ); + + std::string stateString( j.dump() ); + if ( stateString == mLastStateJsonDump ) + return; + + std::string debuggerStatePath( projectStatePath + "debugger.json" ); + if ( rewriteStateOnlyIfNeeded && FileSystem::fileExists( debuggerStatePath ) && + MD5::fromFile( debuggerStatePath ) == MD5::fromString( stateString ) ) + return; + FileSystem::fileWrite( debuggerStatePath, stateString ); + mLastStateJsonDump = stateString; + } ); +} + +void DebuggerPlugin::onLoadProject( const std::string& projectFolder, + const std::string& projectStatePath ) { + mProjectPath = projectFolder; + + ScopedOp op( [this] { closeProject(); }, + [this] { + auto sdc = getStatusDebuggerController(); + if ( sdc && sdc->getUIBreakpoints() ) { + sdc->getUIBreakpoints()->runOnMainThread( [this, sdc] { + sdc->getUIBreakpoints()->setModel( mBreakpointsModel ); + } ); + } + } ); + + std::string debuggerStatePath( projectStatePath + "debugger.json" ); + std::string data; + if ( !FileSystem::fileGet( debuggerStatePath, data ) ) + return; + + json j; + try { + j = json::parse( data, nullptr, true, true ); + if ( j.contains( "config" ) && j["config"].is_object() ) { + auto& config = j["config"]; + mCurDebugger = config.value( "debugger", "" ); + mCurConfiguration = config.value( "configuration", "" ); + mExpressions.clear(); + if ( config.contains( "expressions" ) && config["expressions"].is_array() ) { + auto& exps = config["expressions"]; + if ( mExpressionsHolder ) + mExpressionsHolder->clear( true ); + for ( const auto& expression : exps ) { + if ( expression.is_string() ) { + mExpressions.push_back( expression.get() ); + if ( mExpressionsHolder ) { + mExpressionsHolder->addChild( + std::make_shared( expression, 0 ) ); + } + } + } + } + + if ( config.contains( "breakpoints" ) && config["breakpoints"].is_object() ) { + UnorderedMap> breakpoints; + for ( auto& [key, value] : config["breakpoints"].items() ) { + auto& bps = breakpoints[key]; + if ( value.is_array() ) { + for ( auto& jbp : value ) { + SourceBreakpointStateful bp; + bp.line = jbp.value( "line", 1 ); + bp.enabled = jbp.value( "enabled", true ); + bps.insert( std::move( bp ) ); + } + } + } + + { + Lock l( mBreakpointsMutex ); + mBreakpoints = std::move( breakpoints ); + mBreakpointsModel = + std::make_shared( mBreakpoints, getUISceneNode() ); + } + } + } + } catch ( const json::exception& e ) { + Log::error( + "DebuggerPlugin::onLoadProject - Error parsing config from path %s, error: %s, config " + "file content:\n%s", + debuggerStatePath.c_str(), e.what(), data.c_str() ); + } +} + +void DebuggerPlugin::resetExpressions() { + if ( !mExpressionsHolder ) + return; + mExpressionsHolder->clear( true ); + for ( const auto& expression : mExpressions ) + mExpressionsHolder->addChild( std::make_shared( expression, 0 ) ); +} + +void DebuggerPlugin::closeProject() { + exitDebugger(); + + mExpressions.clear(); + if ( mExpressionsHolder ) + mExpressionsHolder->clear( true ); + + { + Lock l( mBreakpointsMutex ); + mBreakpointsModel.reset(); + mPendingBreakpoints.clear(); + mBreakpoints.clear(); + } + + auto sdc = getStatusDebuggerController(); + if ( sdc && sdc->getWidget() ) { + sdc->getUIBreakpoints()->setModel( nullptr ); + sdc->getUIExpressions()->setModel( nullptr ); + sdc->getUIStack()->setModel( nullptr ); + sdc->getUIThreads()->setModel( nullptr ); + sdc->getUIVariables()->setModel( nullptr ); + } +} + void DebuggerPlugin::load( PluginManager* pluginManager ) { Clock clock; AtomicBoolScopedOp loading( mLoading, true ); @@ -311,6 +466,9 @@ void DebuggerPlugin::loadProjectConfiguration( const std::string& path ) { } void DebuggerPlugin::loadProjectConfigurations() { + if ( mProjectPath.empty() ) + return; + { Lock l( mDapsMutex ); mDapConfigs.clear(); @@ -324,7 +482,10 @@ void DebuggerPlugin::loadProjectConfigurations() { if ( FileSystem::fileExists( config ) ) loadProjectConfiguration( config ); - getUISceneNode()->runOnMainThread( [this] { updateDebuggerConfigurationList(); } ); + getUISceneNode()->runOnMainThread( [this] { + updateDebuggerConfigurationList(); + updateSelectedDebugConfig(); + } ); } ); } @@ -345,7 +506,6 @@ PluginRequestHandle DebuggerPlugin::processMessage( const PluginMessage& msg ) { loadProjectConfigurations(); - mBreakpointsModel.reset(); updateUI(); mInitialized = true; break; @@ -438,6 +598,14 @@ void DebuggerPlugin::buildSidePanelTab() { mTabContents->bind( "debugger_list", mUIDebuggerList ); mTabContents->bind( "debugger_conf_list", mUIDebuggerConfList ); + mUIDebuggerList->on( Event::OnValueChange, [this]( const Event* event ) { + mCurDebugger = event->getNode()->asType()->getText().toUtf8(); + } ); + + mUIDebuggerConfList->on( Event::OnValueChange, [this]( const Event* event ) { + mCurConfiguration = event->getNode()->asType()->getText().toUtf8(); + } ); + mTabContents->bind( "panel_debugger_buttons", mPanelBoxButtons.box ); mTabContents->bind( "panel_debugger_continue", mPanelBoxButtons.resume ); mTabContents->bind( "panel_debugger_pause", mPanelBoxButtons.pause ); @@ -460,11 +628,98 @@ void DebuggerPlugin::buildSidePanelTab() { mPanelBoxButtons.stepOut->onClick( [this]( auto ) { getPluginContext()->runCommand( "debugger-step-out" ); } ); + mTabContents->bind( "debugger_run_button", mRunButton ); + + mRunButton->onClick( [this]( auto ) { + if ( mDebugger && mDebugger->started() ) { + exitDebugger(); + } else { + runCurrentConfig(); + } + } ); + setUIDebuggingState( StatusDebuggerController::State::NotStarted ); updateSidePanelTab(); + + updateSelectedDebugConfig(); } +void DebuggerPlugin::updateSelectedDebugConfig() { + if ( mUIDebuggerList && mUIDebuggerConfList && !mCurDebugger.empty() ) { + mUIDebuggerList->runOnMainThread( [this] { + mUIDebuggerList->getListBox()->setSelected( mCurDebugger ); + mUIDebuggerConfList->getListBox()->setSelected( mCurConfiguration ); + } ); + } +} + +void DebuggerPlugin::removeExpression( const std::string& name ) { + auto expIt = std::find( mExpressions.begin(), mExpressions.end(), name ); + if ( expIt == mExpressions.end() ) + return; + + mExpressions.erase( expIt ); + resetExpressions(); +} + +void DebuggerPlugin::openExpressionMenu( UITreeView* uiExpressions, ModelIndex idx, + bool fromMouseClick ) { + UIPopUpMenu* menu = UIPopUpMenu::New(); + auto context = getPluginContext(); + + if ( idx.isValid() ) { + menu->add( context->i18n( "debugger_remove_expression", "Remove Expression" ), + context->findIcon( "remove" ) ) + ->setId( "debugger_remove_expression" ); + } + + menu->add( context->i18n( "debugger_add_expression", "Add Expression..." ), + context->findIcon( "add" ) ) + ->setId( "debugger_add_expression" ); + + menu->on( Event::OnItemClicked, [this, context, idx]( const Event* event ) { + UIMenuItem* item = event->getNode()->asType(); + std::string id( item->getId() ); + if ( id == "debugger_remove_expression" ) { + ModelVariableNode* node = static_cast( idx.internalData() ); + if ( mExpressionsHolder && node->getParent() == nullptr ) + removeExpression( node->var.name ); + } else if ( id == "debugger_add_expression" ) { + auto msgBox = + UIMessageBox::New( UIMessageBox::INPUT, context->i18n( "debugger_add_expression", + "Add Expression..." ) ); + + msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + std::string expression( msgBox->getTextInput()->getText().toUtf8() ); + if ( std::find( mExpressions.begin(), mExpressions.end(), expression ) == + mExpressions.end() ) { + mExpressions.push_back( expression ); + mExpressionsHolder->addChild( + std::make_shared( expression, 0 ) ); + msgBox->closeWindow(); + } + } ); + } + } ); + + UITableCell* cell = uiExpressions->getCellFromIndex( idx ); + if ( fromMouseClick || cell == nullptr ) { + Vector2f pos( context->getWindow()->getInput()->getMousePos().asFloat() ); + menu->nodeToWorldTranslation( pos ); + UIMenu::findBestMenuPos( pos, menu ); + menu->setPixelsPosition( pos ); + } else { + Vector2f pos( 0, cell->getPixelsSize().getHeight() ); + cell->nodeToWorldTranslation( pos ); + UIMenu::findBestMenuPos( pos, menu ); + menu->setPixelsPosition( pos ); + } + + menu->show(); +} void DebuggerPlugin::buildStatusBar() { if ( mProjectPath.empty() ) { hideStatusBarElement(); @@ -486,6 +741,69 @@ void DebuggerPlugin::buildStatusBar() { statusBar->insertStatusBarElement( "status_app_debugger", i18n( "debugger", "Debugger" ), "icon(debug, 11dp)", debuggerStatusElem ); + + debuggerStatusElem->onWidgetCreated = [this]( StatusDebuggerController* sdc, UIWidget* ) { + UITreeView* uiExpressions = sdc->getUIExpressions(); + uiExpressions->setModel( mExpressionsHolder->model ); + uiExpressions->removeEventsOfType( Event::OnModelEvent ); + uiExpressions->onModelEvent( [this, uiExpressions]( const ModelEvent* modelEvent ) { + if ( modelEvent->getModelEventType() == Abstract::ModelEventType::OpenMenu ) { + openExpressionMenu( uiExpressions, modelEvent->getModelIndex(), true ); + } else if ( mDebugger && mListener && + modelEvent->getModelEventType() == Abstract::ModelEventType::OpenTree ) { + ModelVariableNode* node = + static_cast( modelEvent->getModelIndex().internalData() ); + mDebugger->variables( + node->var.variablesReference, Variable::Type::Both, + [this]( const int variablesReference, std::vector&& vars ) { + mExpressionsHolder->addVariables( variablesReference, std::move( vars ) ); + } ); + } + } ); + uiExpressions->removeEventsOfType( Event::MouseClick ); + uiExpressions->onClick( + [this, uiExpressions]( const MouseEvent* ) { + openExpressionMenu( uiExpressions, {}, true ); + }, + MouseButton::EE_BUTTON_RIGHT ); + + auto uiBreakpoints = sdc->getUIBreakpoints(); + if ( nullptr == uiBreakpoints->onBreakpointEnabledChange ) { + uiBreakpoints->onBreakpointEnabledChange = [this]( const std::string& filePath, + int line, bool enabled ) { + breakpointSetEnabled( filePath, line, enabled ); + }; + } + + if ( nullptr == uiBreakpoints->onBreakpointRemove ) { + uiBreakpoints->onBreakpointRemove = [this]( const std::string& filePath, int line ) { + setBreakpoint( filePath, line ); + }; + } + + uiBreakpoints->setModel( mBreakpointsModel ); + + uiBreakpoints->onModelEvent( [this]( const ModelEvent* modelEvent ) { + if ( modelEvent->getModelEventType() == Abstract::ModelEventType::OpenMenu ) { + // Implement + } else if ( modelEvent->getModelEventType() == Abstract::ModelEventType::Open ) { + auto idx( modelEvent->getModelIndex() ); + auto srcIdx( modelEvent->getModel()->index( + idx.row(), BreakpointsModel::Columns::SourcePath, idx.parent() ) ); + auto lineIdx( modelEvent->getModel()->index( + idx.row(), BreakpointsModel::Columns::Line, idx.parent() ) ); + Variant sourcePathVar( modelEvent->getModel()->data( srcIdx, ModelRole::Data ) ); + Variant lineVar( modelEvent->getModel()->data( lineIdx, ModelRole::Data ) ); + TextPosition pos( lineVar.asInt() - 1, 0 ); + getPluginContext()->focusOrLoadFile( sourcePathVar.toString(), { pos, pos } ); + } + } ); + }; + + if ( !mExpressionsHolder ) { + mExpressionsHolder = std::make_shared( getUISceneNode() ); + resetExpressions(); + } } void DebuggerPlugin::updateSidePanelTab() { @@ -505,27 +823,18 @@ void DebuggerPlugin::updateSidePanelTab() { [this]( const Event* ) { updateDebuggerConfigurationList(); } ); } - if ( !empty ) - mUIDebuggerList->getListBox()->setSelected( 0L ); + if ( !empty ) { + if ( !mCurDebugger.empty() ) + mUIDebuggerList->getListBox()->setSelected( mCurDebugger ); + else + mUIDebuggerList->getListBox()->setSelected( 0L ); + } updateDebuggerConfigurationList(); - - mRunButton = mTabContents->find( "debugger_run_button" ); - - if ( !mRunButton->hasEventsOfType( Event::MouseClick ) ) { - mRunButton->onClick( [this]( auto ) { - if ( mDebugger && mDebugger->started() ) { - exitDebugger(); - } else { - runCurrentConfig(); - } - } ); - } } void DebuggerPlugin::runCurrentConfig() { - runConfig( mUIDebuggerList->getListBox()->getItemSelectedText().toUtf8(), - mUIDebuggerConfList->getListBox()->getItemSelectedText().toUtf8() ); + runConfig( mCurDebugger, mCurConfiguration ); } void DebuggerPlugin::sendPendingBreakpoints() { @@ -558,7 +867,12 @@ void DebuggerPlugin::sendFileBreakpoints( const std::string& filePath ) { } void DebuggerPlugin::updateDebuggerConfigurationList() { + if ( nullptr == mUIDebuggerConfList ) + return; + + std::string curConfig( mCurConfiguration ); mUIDebuggerConfList->getListBox()->clear(); + mCurConfiguration = curConfig; std::string debuggerSelected = mUIDebuggerList->getListBox()->getItemSelectedText().toUtf8(); @@ -591,8 +905,12 @@ void DebuggerPlugin::updateDebuggerConfigurationList() { mUIDebuggerConfList->getListBox()->addListBoxItems( confNames ); bool empty = mUIDebuggerConfList->getListBox()->isEmpty(); mUIDebuggerConfList->setEnabled( !empty ); - if ( !empty ) - mUIDebuggerConfList->getListBox()->setSelected( 0L ); + if ( !empty ) { + if ( !mCurConfiguration.empty() ) + mUIDebuggerConfList->getListBox()->setSelected( mCurConfiguration ); + else + mUIDebuggerConfList->getListBox()->setSelected( 0L ); + } } void DebuggerPlugin::replaceKeysInJson( nlohmann::json& json, int randomPort ) { @@ -848,12 +1166,6 @@ bool DebuggerPlugin::setBreakpoint( const std::string& doc, Uint32 lineNumber ) if ( sdc && sdc->getWidget() == nullptr ) { sdc->show(); sdc->hide(); - sdc->getUIBreakpoints()->onBreakpointEnabledChange = [this]( const std::string& filePath, - int line, bool enabled ) { - breakpointSetEnabled( filePath, line, enabled ); - }; - sdc->getUIBreakpoints()->onBreakpointRemove = - [this]( const std::string& filePath, int line ) { setBreakpoint( filePath, line ); }; } if ( !mBreakpointsModel ) @@ -1040,11 +1352,6 @@ void DebuggerPlugin::runConfig( const std::string& debugger, const std::string& getManager()->getPluginContext()->getNotificationCenter()->addNotification( i18n( "debugger_init_failed", "Failed to initialize debugger." ) ); - } else { - mRunButton->runOnMainThread( [this] { - mRunButton->setEnabled( true ); - mRunButton->setText( i18n( "cancel_run", "Cancel Run" ) ); - } ); } } ); } @@ -1165,17 +1472,12 @@ void DebuggerPlugin::run( ProtocolSettings&& protocolSettings, DapRunConfig&& ru void DebuggerPlugin::exitDebugger() { if ( mDebugger && mListener ) mDebugger->removeListener( mListener.get() ); - mThreadPool->run( [this] { - mDebugger.reset(); - mListener.reset(); - } ); - if ( getUISceneNode() && mRunButton ) { - mRunButton->runOnMainThread( [this] { - mRunButton->setText( i18n( "run", "Run" ) ); - mRunButton->setEnabled( true ); + if ( mDebugger || mListener ) { + mThreadPool->run( [this] { + mDebugger.reset(); + mListener.reset(); } ); } - setUIDebuggingState( StatusDebuggerController::State::NotStarted ); } @@ -1201,17 +1503,22 @@ StatusDebuggerController* DebuggerPlugin::getStatusDebuggerController() const { } void DebuggerPlugin::updatePanelUIState( StatusDebuggerController::State state ) { - mPanelBoxButtons.box->setVisible( state != StatusDebuggerController::State::NotStarted ); - mPanelBoxButtons.resume->setVisible( state != StatusDebuggerController::State::NotStarted ) + auto isDebugging = state != StatusDebuggerController::State::NotStarted; + + mPanelBoxButtons.box->setVisible( isDebugging ); + mPanelBoxButtons.resume->setVisible( isDebugging ) ->setEnabled( state == StatusDebuggerController::State::Paused ); - mPanelBoxButtons.pause->setVisible( state != StatusDebuggerController::State::NotStarted ) + mPanelBoxButtons.pause->setVisible( isDebugging ) ->setEnabled( state == StatusDebuggerController::State::Running ); - mPanelBoxButtons.stepOver->setVisible( state != StatusDebuggerController::State::NotStarted ) + mPanelBoxButtons.stepOver->setVisible( isDebugging ) ->setEnabled( state == StatusDebuggerController::State::Paused ); - mPanelBoxButtons.stepInto->setVisible( state != StatusDebuggerController::State::NotStarted ) + mPanelBoxButtons.stepInto->setVisible( isDebugging ) ->setEnabled( state == StatusDebuggerController::State::Paused ); - mPanelBoxButtons.stepOut->setVisible( state != StatusDebuggerController::State::NotStarted ) + mPanelBoxButtons.stepOut->setVisible( isDebugging ) ->setEnabled( state == StatusDebuggerController::State::Paused ); + + mRunButton->setText( isDebugging ? i18n( "Stop Debugging", "Stop Debugging" ) + : i18n( "debug", "Debug" ) ); } void DebuggerPlugin::setUIDebuggingState( StatusDebuggerController::State state ) { @@ -1228,6 +1535,11 @@ void DebuggerPlugin::setUIDebuggingState( StatusDebuggerController::State state else mPanelBoxButtons.box->runOnMainThread( [this, state] { updatePanelUIState( state ); } ); } + + if ( state == StatusDebuggerController::State::NotStarted || + state == StatusDebuggerController::State::Running ) { + resetExpressions(); + } } // Mouse Hover Tooltip diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp index 36984094b..6aaf8a9f4 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp @@ -56,6 +56,12 @@ class DebuggerPlugin : public PluginBase { std::string getDescription() override { return Definition().description; } + void onSaveProject( const std::string& projectFolder, const std::string& projectStatePath, + bool rewriteStateOnlyIfNeeded ) override; + + void onLoadProject( const std::string& projectFolder, + const std::string& projectStatePath ) override; + protected: friend class DebuggerClientListener; @@ -81,6 +87,7 @@ class DebuggerPlugin : public PluginBase { Mutex mBreakpointsMutex; StatusDebuggerController::State mDebuggingState{ StatusDebuggerController::State::NotStarted }; std::vector mExpressions; + std::shared_ptr mExpressionsHolder; // Begin Hover Stuff Uint32 mHoverWaitCb{ 0 }; @@ -104,6 +111,9 @@ class DebuggerPlugin : public PluginBase { UIPushButton* stepOut{ nullptr }; }; PanelBoxButtons mPanelBoxButtons; + std::string mLastStateJsonDump; + std::string mCurDebugger; + std::string mCurConfiguration; DebuggerPlugin( PluginManager* pluginManager, bool sync ); @@ -187,6 +197,16 @@ class DebuggerPlugin : public PluginBase { void loadProjectConfiguration( const std::string& path ); void loadProjectConfigurations(); + + void openExpressionMenu( UITreeView* uiExpressions, ModelIndex idx, bool fromMouseClick ); + + void updateSelectedDebugConfig(); + + void removeExpression( const std::string& name ); + + void resetExpressions(); + + void closeProject(); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/debugger/models/variablesmodel.cpp b/src/tools/ecode/plugins/debugger/models/variablesmodel.cpp index 76d8dc97b..fe9cb14b5 100644 --- a/src/tools/ecode/plugins/debugger/models/variablesmodel.cpp +++ b/src/tools/ecode/plugins/debugger/models/variablesmodel.cpp @@ -165,6 +165,7 @@ VariablesHolder::VariablesHolder( UISceneNode* sceneNode ) : } void VariablesHolder::addVariables( const int variablesReference, std::vector&& vars ) { + Lock l( mutex ); auto parentNode = getNodeByReference( variablesReference ); if ( !parentNode ) { auto node = rootNode->getChildRecursive( variablesReference ); @@ -202,13 +203,34 @@ void VariablesHolder::addVariables( const int variablesReference, std::vectoraddChild( child ); nodeMap[child->var.variablesReference] = child; + model->invalidate( Model::UpdateFlag::InvalidateAllIndexes ); } -void VariablesHolder::clear() { +void VariablesHolder::upsertRootChild( Variable&& var ) { + Lock l( mutex ); + for ( size_t i = 0; i < rootNode->children.size(); i++ ) { + auto child = rootNode->children[i]; + if ( child->getName() == var.name ) { + auto newChild = std::make_shared( std::move( var ), rootNode ); + nodeMap[newChild->var.variablesReference] = newChild; + rootNode->children[i] = std::move( newChild ); + model->invalidate( Model::UpdateFlag::DontInvalidateIndexes ); + return; + } + } + auto newChild = std::make_shared( std::move( var ), rootNode ); + addChild( newChild ); +} + +void VariablesHolder::clear( bool all ) { + Lock l( mutex ); rootNode->clear(); - // modelMap.clear(); + if ( all ) { + nodeMap.clear(); + } } ModelVariableNode::NodePtr VariablesHolder::getNodeByReference( int variablesReference ) { diff --git a/src/tools/ecode/plugins/debugger/models/variablesmodel.hpp b/src/tools/ecode/plugins/debugger/models/variablesmodel.hpp index 899a3342c..d10d24248 100644 --- a/src/tools/ecode/plugins/debugger/models/variablesmodel.hpp +++ b/src/tools/ecode/plugins/debugger/models/variablesmodel.hpp @@ -79,10 +79,13 @@ struct VariablesHolder { void addChild( ModelVariableNode::NodePtr child ); - void clear(); + void upsertRootChild( Variable&& ); + + void clear( bool all = false ); ModelVariableNode::NodePtr getNodeByReference( int variablesReference ); + Mutex mutex; std::shared_ptr rootNode; std::shared_ptr model; std::unordered_map nodeMap; diff --git a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp index ce943b139..326b2b11d 100644 --- a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp +++ b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp @@ -42,8 +42,8 @@ UIWidget* UIBreakpointsTableView::createCell( UIWidget* rowWidget, const ModelIn return setupCell( widget, rowWidget, index ); } else if ( index.column() == BreakpointsModel::Remove ) { auto cell = UITableView::createCell( rowWidget, index ); - auto model = (const BreakpointsModel*)getModel(); - cell->onClick( [model, index, this]( auto ) { + cell->onClick( [index, this]( auto ) { + auto model = getModel(); if ( onBreakpointRemove ) { std::string filePath( model @@ -72,8 +72,11 @@ UIWidget* StatusDebuggerController::getWidget() { } UIWidget* StatusDebuggerController::createWidget() { - if ( nullptr == mContainer ) + if ( nullptr == mContainer ) { createContainer(); + if ( onWidgetCreated ) + onWidgetCreated( this, mContainer ); + } return mContainer; } @@ -93,6 +96,10 @@ UITreeView* StatusDebuggerController::getUIVariables() const { return mUIVariables; } +UITreeView* StatusDebuggerController::getUIExpressions() const { + return mUIExpressions; +} + UITabWidget* StatusDebuggerController::getUITabWidget() const { return mUITabWidget; } @@ -214,6 +221,7 @@ void StatusDebuggerController::createContainer() { mUIExpressions->setAutoColumnsWidth( true ); mUIExpressions->setFitAllColumnsToWidget( true ); + mUIExpressions->setMainColumn( 1 ); } } // namespace ecode diff --git a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp index b766eb348..2e63a957d 100644 --- a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp +++ b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp @@ -56,10 +56,14 @@ class StatusDebuggerController : public StatusBarElement { UITreeView* getUIVariables() const; + UITreeView* getUIExpressions() const; + UITabWidget* getUITabWidget() const; void setDebuggingState( State state ); + std::function onWidgetCreated{ nullptr }; + protected: UILinearLayout* mContainer{ nullptr }; UITableView* mUIThreads{ nullptr }; diff --git a/src/tools/ecode/plugins/plugin.hpp b/src/tools/ecode/plugins/plugin.hpp index 59a2e434e..c7d485efb 100644 --- a/src/tools/ecode/plugins/plugin.hpp +++ b/src/tools/ecode/plugins/plugin.hpp @@ -53,6 +53,13 @@ class Plugin : public UICodeEditorPlugin { void showMessage( LSPMessageType type, const std::string& message, const std::string& title = "" ); + virtual void onSaveProject( const std::string& /*projectFolder*/, + const std::string& /*projectStatePath*/, + bool /*rewriteStateOnlyIfNeeded*/ ) {} + + virtual void onLoadProject( const std::string& /*projectFolder*/, + const std::string& /*projectStatePath*/ ) {} + protected: PluginManager* mManager{ nullptr }; std::shared_ptr mThreadPool; diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index 9f4563796..0f8f4f5d3 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -1,6 +1,6 @@ +#include "pluginmanager.hpp" #include "../filesystemlistener.hpp" #include "plugin.hpp" -#include "pluginmanager.hpp" #include #include #include @@ -330,6 +330,11 @@ bool PluginManager::hasDefinition( const std::string& id ) { return mDefinitions.find( id ) != mDefinitions.end(); } +void PluginManager::forEachPlugin( std::function fn ) { + for ( auto& plugin : mPlugins ) + fn( plugin.second ); +} + std::shared_ptr PluginsModel::New( PluginManager* manager ) { return std::make_shared( manager ); } diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index 36b9f6e16..db73d5ad6 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -365,6 +365,8 @@ class PluginManager { PluginContextProvider* getPluginContext() const { return mPluginContext; } + void forEachPlugin( std::function fn ); + protected: using SubscribedPlugins = std::map>;