mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-06-02 19:46:29 +03:00
336 lines
9.8 KiB
C++
336 lines
9.8 KiB
C++
#include "debuggerplugin.hpp"
|
|
#include "busprocess.hpp"
|
|
#include "dap/debuggerclientdap.hpp"
|
|
#include <eepp/system/filesystem.hpp>
|
|
#include <eepp/system/scopedop.hpp>
|
|
#include <eepp/ui/uidropdownlist.hpp>
|
|
#include <nlohmann/json.hpp>
|
|
|
|
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<std::string> 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<std::string>() );
|
|
}
|
|
}
|
|
}
|
|
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<std::string> 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(
|
|
<vbox id="debugger_panel" lw="mp" lh="wc" padding="4dp">
|
|
<vbox id="debugger_config_view" lw="mp" lh="wc">
|
|
<TextView text="@string(debugger, Debugger)" font-size="15dp" focusable="false" />
|
|
<DropDownList id="debugger_list" layout_width="mp" layout_height="wrap_content" margin-top="2dp" />
|
|
<TextView text="@string(debugger_configuration, Debugger Configuration)" focusable="false" margin-top="8dp" />
|
|
<DropDownList id="debugger_conf_list" layout_width="mp" layout_height="wrap_content" margin-top="2dp" />
|
|
<PushButton id="run_button" lw="mp" lh="wc" text="@string(run, Run)" margin-top="8dp" icon="icon(play, 12dp)" />
|
|
</vbox>
|
|
</vbox>
|
|
)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<String> 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<UIPushButton>( "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<String> 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<BusProcess>( cmd );
|
|
|
|
mDebugger = std::make_unique<DebuggerClientDap>( protocolSettings, std::move( bus ) );
|
|
mListener = std::make_unique<DebuggerClientListener>();
|
|
mDebugger->addListener( mListener.get() );
|
|
mDebugger->start();
|
|
}
|
|
|
|
void DebuggerPlugin::hideSidePanel() {
|
|
if ( mSidePanel && mTab ) {
|
|
mSidePanel->removeTab( mTab, false );
|
|
mTab = nullptr;
|
|
}
|
|
}
|
|
|
|
} // namespace ecode
|