#include "debuggerplugin.hpp" #include "busprocess.hpp" #include "dap/debuggerclientdap.hpp" #include #include #include #include using namespace EE::UI; using namespace EE::UI::Doc; using namespace std::literals; using json = nlohmann::json; #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) #define DEBUGGER_THREADED 1 #else #define DEBUGGER_THREADED 0 #endif namespace ecode { Plugin* DebuggerPlugin::New( PluginManager* pluginManager ) { return eeNew( DebuggerPlugin, ( pluginManager, false ) ); } Plugin* DebuggerPlugin::NewSync( PluginManager* pluginManager ) { return eeNew( DebuggerPlugin, ( pluginManager, true ) ); } DebuggerPlugin::DebuggerPlugin( PluginManager* pluginManager, bool sync ) : PluginBase( pluginManager ) { if ( sync ) { load( pluginManager ); } else { #if defined( DEBUGGER_THREADED ) && DEBUGGER_THREADED == 1 mThreadPool->run( [this, pluginManager] { load( pluginManager ); } ); #else load( pluginManager ); #endif } } DebuggerPlugin::~DebuggerPlugin() { waitUntilLoaded(); mShuttingDown = true; if ( mSidePanel && mTab ) mSidePanel->removeTab( mTab ); } void DebuggerPlugin::load( PluginManager* pluginManager ) { Clock clock; AtomicBoolScopedOp loading( mLoading, true ); pluginManager->subscribeMessages( this, [this]( const auto& notification ) -> PluginRequestHandle { return processMessage( notification ); } ); std::vector paths; std::string path( pluginManager->getResourcesPath() + "plugins/debugger.json" ); if ( FileSystem::fileExists( path ) ) paths.emplace_back( path ); path = pluginManager->getPluginsPath() + "debugger.json"; if ( FileSystem::fileExists( path ) || FileSystem::fileWrite( path, "{\n \"config\":{},\n \"keybindings\":{},\n \"dap\":[]\n}\n" ) ) { mConfigPath = path; paths.emplace_back( path ); } if ( paths.empty() ) return; for ( const auto& ipath : paths ) { try { loadDAPConfig( ipath, mConfigPath == ipath ); } catch ( const json::exception& e ) { Log::error( "Parsing Debugger \"%s\" failed:\n%s", ipath.c_str(), e.what() ); } } if ( getUISceneNode() ) updateUI(); subscribeFileSystemListener(); mReady = true; fireReadyCbs(); setReady( clock.getElapsedTime() ); } void DebuggerPlugin::loadDAPConfig( const std::string& path, bool updateConfigFile ) { std::string data; if ( !FileSystem::fileGet( path, data ) ) return; if ( updateConfigFile ) mConfigHash = String::hash( data ); json j; try { j = json::parse( data, nullptr, true, true ); } catch ( const json::exception& e ) { Log::error( "DebuggerPlugin::load - Error parsing config from path %s, error: %s, config " "file content:\n%s", path.c_str(), e.what(), data.c_str() ); // Recreate it j = json::parse( "{\n \"config\":{},\n \"dap\":{},\n \"keybindings\":{},\n}\n", nullptr, true, true ); } if ( j.contains( "dap" ) ) { auto& dapArr = j["dap"]; mDaps.reserve( dapArr.size() ); for ( const auto& dap : dapArr ) { DapTool dapTool; dapTool.name = dap.value( "name", "" ); dapTool.url = dap.value( "url", "" ); if ( dap.contains( "run" ) ) { auto& run = dap["run"]; dapTool.run.command = run.value( "command", "" ); if ( run.contains( "command_arguments" ) && run["command_arguments"].is_array() ) { auto& args = run["command_arguments"]; dapTool.run.args.reserve( args.size() ); for ( auto& arg : args ) { if ( args.is_string() ) dapTool.run.args.emplace_back( arg.get() ); } } } if ( dap.contains( "configurations" ) ) { auto& configs = dap["configurations"]; dapTool.configurations.reserve( configs.size() ); for ( auto& config : configs ) { DapConfig dapConfig; dapConfig.name = config.value( "name", "" ); if ( !dapConfig.name.empty() ) { dapConfig.command = config.value( "command", "launch" ); dapConfig.args = config["arguments"]; } dapTool.configurations.emplace_back( std::move( dapConfig ) ); } } mDaps.emplace_back( std::move( dapTool ) ); } } if ( j.contains( "config" ) ) { } if ( mKeyBindings.empty() ) { } if ( j.contains( "keybindings" ) ) { auto& kb = j["keybindings"]; std::initializer_list list = {}; for ( const auto& key : list ) { if ( kb.contains( key ) ) { if ( !kb[key].empty() ) mKeyBindings[key] = kb[key]; } else { kb[key] = mKeyBindings[key]; updateConfigFile = true; } } } if ( updateConfigFile ) { std::string newData = j.dump( 2 ); if ( newData != data ) { FileSystem::fileWrite( path, newData ); mConfigHash = String::hash( newData ); } } } PluginRequestHandle DebuggerPlugin::processMessage( const PluginMessage& msg ) { switch ( msg.type ) { case PluginMessageType::WorkspaceFolderChanged: { break; } case ecode::PluginMessageType::UIReady: { updateUI(); break; } default: break; } return PluginRequestHandle::empty(); } void DebuggerPlugin::updateUI() { if ( !getUISceneNode() ) return; getUISceneNode()->runOnMainThread( [this] { buildSidePanelTab(); } ); } void DebuggerPlugin::buildSidePanelTab() { if ( mTabContents && !mTab ) { UIIcon* icon = findIcon( "debug" ); mTab = mSidePanel->add( i18n( "debugger", "Debugger" ), mTabContents, icon ? icon->getSize( PixelDensity::dpToPx( 12 ) ) : nullptr ); mTab->setId( "debugger" ); mTab->setTextAsFallback( true ); return; } if ( mTab ) return; if ( mSidePanel == nullptr ) getUISceneNode()->bind( "panel", mSidePanel ); static constexpr auto STYLE = R"html( )html"; UIIcon* icon = findIcon( "debug" ); mTabContents = getUISceneNode()->loadLayoutFromString( STYLE ); mTab = mSidePanel->add( i18n( "debugger", "Debugger" ), mTabContents, icon ? icon->getSize( PixelDensity::dpToPx( 12 ) ) : nullptr ); mTab->setId( "source_control" ); mTab->setTextAsFallback( true ); mTabContents->bind( "debugger_list", mUIDebuggerList ); mTabContents->bind( "debugger_conf_list", mUIDebuggerConfList ); updateSidePanelTab(); } void DebuggerPlugin::updateSidePanelTab() { mUIDebuggerList->getListBox()->clear(); std::vector debuggerNames; debuggerNames.reserve( mDaps.size() ); for ( const auto& dap : mDaps ) debuggerNames.emplace_back( dap.name ); std::sort( debuggerNames.begin(), debuggerNames.end() ); mUIDebuggerList->getListBox()->addListBoxItems( debuggerNames ); bool empty = mUIDebuggerList->getListBox()->isEmpty(); mUIDebuggerList->setEnabled( !empty ); if ( !mUIDebuggerList->hasEventsOfType( Event::OnItemSelected ) ) { mUIDebuggerList->on( Event::OnItemSelected, [this]( const Event* ) { updateDebuggerConfigurationList(); } ); } if ( !empty ) mUIDebuggerList->getListBox()->setSelected( 0L ); updateDebuggerConfigurationList(); UIPushButton* runButton = mTabContents->find( "run_button" ); if ( !runButton->hasEventsOfType( Event::MouseClick ) ) { runButton->onClick( [this]( auto ) { runConfig( mUIDebuggerList->getListBox()->getItemSelectedText().toUtf8(), mUIDebuggerConfList->getListBox()->getItemSelectedText().toUtf8() ); } ); } } void DebuggerPlugin::updateDebuggerConfigurationList() { mUIDebuggerConfList->getListBox()->clear(); std::string debuggerSelected = mUIDebuggerList->getListBox()->getItemSelectedText().toUtf8(); auto debuggerIt = std::find_if( mDaps.begin(), mDaps.end(), [&debuggerSelected]( const DapTool& dap ) { return dap.name == debuggerSelected; } ); if ( debuggerIt == mDaps.end() ) { mUIDebuggerConfList->setEnabled( false ); return; } std::vector confNames; confNames.reserve( mDaps.size() ); for ( const auto& conf : debuggerIt->configurations ) confNames.emplace_back( conf.name ); std::sort( confNames.begin(), confNames.end() ); mUIDebuggerConfList->getListBox()->addListBoxItems( confNames ); bool empty = mUIDebuggerConfList->getListBox()->isEmpty(); mUIDebuggerConfList->setEnabled( !empty ); if ( !empty ) mUIDebuggerConfList->getListBox()->setSelected( 0L ); } void DebuggerPlugin::runConfig( const std::string& debugger, const std::string& configuration ) { auto debuggerIt = std::find_if( mDaps.begin(), mDaps.end(), [&debugger]( const DapTool& dap ) { return dap.name == debugger; } ); if ( debuggerIt == mDaps.end() ) { return; } auto configIt = std::find_if( debuggerIt->configurations.begin(), debuggerIt->configurations.end(), [&configuration]( const DapConfig& conf ) { return conf.name == configuration; } ); if ( configIt == debuggerIt->configurations.end() ) { return; } ProtocolSettings protocolSettings; protocolSettings.launchCommand = configIt->command; protocolSettings.launchRequest = configIt->args; Command cmd; cmd.command = debuggerIt->run.command; cmd.arguments = debuggerIt->run.args; auto bus = std::make_unique( cmd ); mDebugger = std::make_unique( protocolSettings, std::move( bus ) ); mListener = std::make_unique(); mDebugger->addListener( mListener.get() ); mDebugger->start(); } void DebuggerPlugin::hideSidePanel() { if ( mSidePanel && mTab ) { mSidePanel->removeTab( mTab, false ); mTab = nullptr; } } } // namespace ecode