diff --git a/.ecode/project_build.json b/.ecode/project_build.json index a1134d4d3..f31128f9b 100644 --- a/.ecode/project_build.json +++ b/.ecode/project_build.json @@ -37,7 +37,7 @@ }, "run": [ { - "args": "", + "args": "-v", "command": "ecode-debug", "name": "ecode-debug", "working_dir": "${project_root}/bin" diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index eca850d6e..c78685edb 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -384,8 +384,12 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { void addUnlockedCommand( const std::string& command ); + void removeUnlockedCommand( const std::string& command ); + void addUnlockedCommands( const std::vector& commands ); + void removeUnlockedCommands( const std::vector& commands ); + bool isUnlockedCommand( const std::string& command ); virtual bool applyProperty( const StyleSheetProperty& attribute ); diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 58eed33f6..ed3ca6e46 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -2468,10 +2468,19 @@ void UICodeEditor::addUnlockedCommand( const std::string& command ) { mUnlockedCmd.insert( command ); } +void UICodeEditor::removeUnlockedCommand( const std::string& command ) { + mUnlockedCmd.erase( command ); +} + void UICodeEditor::addUnlockedCommands( const std::vector& commands ) { mUnlockedCmd.insert( commands.begin(), commands.end() ); } +void UICodeEditor::removeUnlockedCommands( const std::vector& commands ) { + for ( const auto& cmd : commands ) + removeUnlockedCommand( cmd ); +} + bool UICodeEditor::isUnlockedCommand( const std::string& command ) { return mUnlockedCmd.find( command ) != mUnlockedCmd.end(); } diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 213a6c7f5..b5f635e53 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -1185,6 +1185,27 @@ void App::loadFileFromPathOrFocus( } } +void App::focusOrLoadFile( const std::string& path, const TextRange& range ) { + UITab* tab = mSplitter->isDocumentOpen( path, true ); + if ( !tab ) { + FileInfo fileInfo( path ); + if ( fileInfo.exists() && fileInfo.isRegularFile() ) + loadFileFromPath( path, true, nullptr, [this, range]( UICodeEditor* editor, auto ) { + if ( range.isValid() ) { + editor->goToLine( range.start() ); + mSplitter->addEditorPositionToNavigationHistory( editor ); + } + } ); + } else { + tab->getTabWidget()->setTabSelected( tab ); + if ( range.isValid() ) { + UICodeEditor* editor = tab->getOwnedWidget()->asType(); + editor->goToLine( range.start() ); + mSplitter->addEditorPositionToNavigationHistory( editor ); + } + } +} + void App::createPluginManagerUI() { UIPluginManager::New( mUISceneNode, mPluginManager.get(), [this]( const std::string& path ) { loadFileFromPathOrFocus( path ); @@ -1747,7 +1768,7 @@ std::map App::getLocalKeybindings() { { { KEY_O, KeyMod::getDefaultModifier() }, "open-file" }, { { KEY_W, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "download-file-web" }, { { KEY_O, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-folder" }, - { { KEY_F11, KEYMOD_NONE }, "debug-widget-tree-view" }, + { { KEY_F11, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "debug-widget-tree-view" }, { { KEY_K, KeyMod::getDefaultModifier() }, "open-locatebar" }, { { KEY_P, KeyMod::getDefaultModifier() }, "open-command-palette" }, { { KEY_F, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-global-search" }, @@ -1789,18 +1810,16 @@ std::map App::getLocalKeybindings() { // Old keybindings will be rebinded to the new keybindings of they are still set to the old // keybindind std::map App::getMigrateKeybindings() { - return { - { "fullscreen-toggle", "alt+return" }, { "switch-to-tab-1", "alt+1" }, - { "switch-to-tab-2", "alt+2" }, { "switch-to-tab-3", "alt+3" }, - { "switch-to-tab-4", "alt+4" }, { "switch-to-tab-5", "alt+5" }, - { "switch-to-tab-6", "alt+6" }, { "switch-to-tab-7", "alt+7" }, - { "switch-to-tab-8", "alt+8" }, { "switch-to-tab-9", "alt+9" }, - { "switch-to-last-tab", "alt+0" }, + return { { "fullscreen-toggle", "alt+return" }, { "switch-to-tab-1", "alt+1" }, + { "switch-to-tab-2", "alt+2" }, { "switch-to-tab-3", "alt+3" }, + { "switch-to-tab-4", "alt+4" }, { "switch-to-tab-5", "alt+5" }, + { "switch-to-tab-6", "alt+6" }, { "switch-to-tab-7", "alt+7" }, + { "switch-to-tab-8", "alt+8" }, { "switch-to-tab-9", "alt+9" }, + { "switch-to-last-tab", "alt+0" }, #if EE_PLATFORM == EE_PLATFORM_MACOS - { "menu-toggle", "mod+shift+m" }, + { "menu-toggle", "mod+shift+m" }, #endif - { "lock-toggle", "mod+shift+l" }, - }; + { "lock-toggle", "mod+shift+l" }, { "debug-widget-tree-view", "f11" } }; } std::vector App::getUnlockedCommands() { @@ -1869,11 +1888,6 @@ std::vector App::getUnlockedCommands() { "create-new-window" }; } -bool App::isUnlockedCommand( const std::string& command ) { - auto cmds = getUnlockedCommands(); - return std::find( cmds.begin(), cmds.end(), command ) != cmds.end(); -} - void App::saveProject( bool onlyIfNeeded, bool sessionSnapshotEnabled ) { if ( !mCurrentProject.empty() ) { mConfig.saveProject( diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 4afe8e8c8..4ac1f1e5e 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -78,8 +78,6 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { std::vector getUnlockedCommands(); - bool isUnlockedCommand( const std::string& command ); - void saveAll(); ProjectDirectoryTree* getDirTree() const; @@ -331,6 +329,8 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { std::function onLoaded = std::function() ); + void focusOrLoadFile( const std::string& path, const TextRange& range = {} ); + UISceneNode* getUISceneNode() const { return mUISceneNode; } void updateRecentFiles(); diff --git a/src/tools/ecode/plugins/debugger/config.cpp b/src/tools/ecode/plugins/debugger/config.cpp index 2b5a021be..f33136ec4 100644 --- a/src/tools/ecode/plugins/debugger/config.cpp +++ b/src/tools/ecode/plugins/debugger/config.cpp @@ -23,8 +23,8 @@ bool BusSettings::hasConnection() const { } ProtocolSettings::ProtocolSettings( const nlohmann::json& configuration ) : - linesStartAt1( false ), - columnsStartAt1( false ), + linesStartAt1( true ), + columnsStartAt1( true ), pathFormatURI( false ), redirectStderr( configuration.value( REDIRECT_STDERR, false ) ), redirectStdout( configuration.value( REDIRECT_STDOUT, false ) ), diff --git a/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp b/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp index 49178c04e..28b3b117d 100644 --- a/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp +++ b/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp @@ -417,8 +417,11 @@ bool DebuggerClientDap::pause( int threadId ) { void DebuggerClientDap::processResponseNext( const Response& response, const nlohmann::json& request ) { if ( response.success ) { - ContinuedEvent continuedEvent( request.value( DAP_THREAD_ID, 1 ), - !response.body.value( DAP_SINGLE_THREAD, false ) ); + bool all = false; + if ( response.body.is_object() && response.body.contains( DAP_ALL_THREADS_CONTINUED ) ) + all = response.body.value( DAP_ALL_THREADS_CONTINUED, false ); + + ContinuedEvent continuedEvent( request.value( DAP_THREAD_ID, 1 ), all ); for ( auto listener : mListeners ) listener->debuggeeContinued( continuedEvent ); } @@ -484,9 +487,9 @@ bool DebuggerClientDap::disconnect( bool restart ) { bool DebuggerClientDap::threads() { makeRequest( DAP_THREADS, {}, [this]( const Response& response, const nlohmann::json& ) { if ( response.success ) { - auto threads( Thread::parseList( response.body[DAP_THREADS] ) ); + std::vector threads( Thread::parseList( response.body[DAP_THREADS] ) ); for ( auto listener : mListeners ) - listener->threads( threads ); + listener->threads( std::move( threads ) ); } else { for ( auto listener : mListeners ) listener->threads( {} ); @@ -505,11 +508,11 @@ bool DebuggerClientDap::stackTrace( int threadId, int startFrame, int levels ) { if ( response.success ) { StackTraceInfo stackTraceInfo( response.body ); for ( auto listener : mListeners ) - listener->stackTrace( threadId, stackTraceInfo ); + listener->stackTrace( threadId, std::move( stackTraceInfo ) ); } else { StackTraceInfo stackTraceInfo; for ( auto listener : mListeners ) - listener->stackTrace( threadId, stackTraceInfo ); + listener->stackTrace( threadId, std::move( stackTraceInfo ) ); } } ); return true; @@ -523,11 +526,11 @@ bool DebuggerClientDap::scopes( int frameId ) { if ( response.success ) { auto scopes( Scope::parseList( response.body[DAP_SCOPES] ) ); for ( auto listener : mListeners ) - listener->scopes( frameId, scopes ); + listener->scopes( frameId, std::move( scopes ) ); } else { std::vector scopes; for ( auto listener : mListeners ) - listener->scopes( frameId, scopes ); + listener->scopes( frameId, std::move( scopes ) ); } } ); return true; @@ -559,11 +562,11 @@ bool DebuggerClientDap::variables( int variablesReference, Variable::Type filter if ( response.success ) { auto variableList( Variable::parseList( response.body[DAP_VARIABLES] ) ); for ( auto listener : mListeners ) - listener->variables( variablesReference, variableList ); + listener->variables( variablesReference, std::move( variableList ) ); } else { std::vector variableList; for ( auto listener : mListeners ) - listener->variables( variablesReference, variableList ); + listener->variables( variablesReference, std::move( variableList ) ); } } ); @@ -576,11 +579,11 @@ bool DebuggerClientDap::modules( int start, int count ) { if ( response.success ) { ModulesInfo info( response.body ); for ( auto listener : mListeners ) - listener->modules( info ); + listener->modules( std::move( info ) ); } else { ModulesInfo info; for ( auto listener : mListeners ) - listener->modules( info ); + listener->modules( std::move( info ) ); } } ); return true; diff --git a/src/tools/ecode/plugins/debugger/debuggerclient.hpp b/src/tools/ecode/plugins/debugger/debuggerclient.hpp index ddb4bf5d0..f136af551 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclient.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerclient.hpp @@ -30,11 +30,11 @@ class DebuggerClient { const std::optional& message ) = 0; virtual void threadChanged( const ThreadEvent& ) = 0; virtual void moduleChanged( const ModuleEvent& ) = 0; - virtual void threads( const std::vector& ) = 0; - virtual void stackTrace( const int threadId, const StackTraceInfo& ) = 0; - virtual void scopes( const int frameId, const std::vector& ) = 0; - virtual void variables( const int variablesReference, const std::vector& ) = 0; - virtual void modules( const ModulesInfo& ) = 0; + virtual void threads( std::vector&& ) = 0; + virtual void stackTrace( const int threadId, StackTraceInfo&& ) = 0; + virtual void scopes( const int frameId, std::vector&& ) = 0; + virtual void variables( const int variablesReference, std::vector&& ) = 0; + virtual void modules( ModulesInfo&& ) = 0; virtual void serverDisconnected() = 0; virtual void sourceContent( const std::string& path, int reference = 0, const SourceContent& content = SourceContent() ) = 0; diff --git a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp index f6570ec90..ed758f939 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp @@ -14,14 +14,24 @@ DebuggerClientListener::fromSet( const EE::UnorderedSet; + class ThreadsModel : public Model { public: - ThreadsModel( const std::vector& threads ) : mThreads( threads ) {} + ThreadsModel( const std::vector& threads, i18nFn fn ) : + mThreads( threads ), mi18nFn( std::move( fn ) ) {} + virtual size_t rowCount( const ModelIndex& ) const { return mThreads.size(); } virtual size_t columnCount( const ModelIndex& ) const { return 2; } virtual std::string columnName( const size_t& colIdx ) const { - return colIdx == 0 ? "ID" : "Name"; + switch ( colIdx ) { + case 0: + return mi18nFn( "id", "ID" ); + case 1: + return mi18nFn( "name", "Name" ); + } + return ""; } virtual Variant data( const ModelIndex& modelIndex, ModelRole role ) const { @@ -33,35 +43,58 @@ class ThreadsModel : public Model { return {}; } + void setThreads( std::vector&& threads ) { + { + Lock l( mResourceLock ); + mThreads = std::move( threads ); + } + invalidate(); + } + + void resetThreads() { + + { + Lock l( mResourceLock ); + mThreads = {}; + } + invalidate(); + } + protected: std::vector mThreads; + i18nFn mi18nFn; }; class StackModel : public Model { public: - StackModel( const StackTraceInfo& stack ) : mStack( stack ) {} - virtual size_t rowCount( const ModelIndex& ) const { return mStack.stackFrames.size(); } - virtual size_t columnCount( const ModelIndex& ) const { return 6; } + StackModel( StackTraceInfo&& stack, i18nFn fn ) : + mStack( std::move( stack ) ), mi18nFn( std::move( fn ) ) {} + + virtual size_t rowCount( const ModelIndex& ) const { + Lock l( mResourceLock ); + return mStack.stackFrames.size(); + } + + virtual size_t columnCount( const ModelIndex& ) const { return 5; } virtual std::string columnName( const size_t& colIdx ) const { switch ( colIdx ) { case 0: - return "ID"; + return mi18nFn( "id", "ID" ); case 1: - return "Name"; + return mi18nFn( "name", "Name" ); case 2: - return "Source Name"; + return mi18nFn( "source_name", "Source Name" ); case 3: - return "Source Path"; + return mi18nFn( "source_path", "Source Path" ); case 4: - return "Line"; - case 5: - return "Column"; + return mi18nFn( "line", "Line" ); } return ""; } virtual Variant data( const ModelIndex& modelIndex, ModelRole role ) const { + Lock l( mResourceLock ); if ( role == ModelRole::Display ) { switch ( modelIndex.column() ) { case 0: @@ -86,8 +119,26 @@ class StackModel : public Model { return {}; } + void setStack( StackTraceInfo&& stack ) { + { + Lock l( mResourceLock ); + mStack = std::move( stack ); + } + invalidate(); + } + + void resetStack() { + + { + Lock l( mResourceLock ); + mStack = {}; + } + invalidate(); + } + protected: StackTraceInfo mStack; + i18nFn mi18nFn; }; DebuggerClientListener::DebuggerClientListener( DebuggerClient* client, DebuggerPlugin* plugin ) : @@ -95,6 +146,10 @@ DebuggerClientListener::DebuggerClientListener( DebuggerClient* client, Debugger eeASSERT( mClient && mPlugin ); } +DebuggerClientListener::~DebuggerClientListener() { + resetState(); +} + void DebuggerClientListener::stateChanged( DebuggerClient::State state ) { if ( state == DebuggerClient::State::Initializing ) { mPlugin->getManager()->getUISceneNode()->runOnMainThread( [this] { @@ -104,6 +159,23 @@ void DebuggerClientListener::stateChanged( DebuggerClient::State state ) { ->getPluginContext() ->getStatusAppOutputController() ->initNewOutput( {}, false ); + + if ( !mThreadsModel ) { + mThreadsModel = std::make_shared( + std::vector{}, [this]( const auto& key, const auto& val ) { + return mPlugin->i18n( key, val ); + } ); + } + + if ( !mStackModel ) { + mStackModel = std::make_shared( + StackTraceInfo{}, [this]( const auto& key, const auto& val ) { + return mPlugin->i18n( key, val ); + } ); + } + + getStatusDebuggerController()->getUIThreads()->setModel( mThreadsModel ); + getStatusDebuggerController()->getUIStack()->setModel( mStackModel ); } ); } } @@ -126,14 +198,21 @@ void DebuggerClientListener::debuggeeTerminated() {} void DebuggerClientListener::capabilitiesReceived( const Capabilities& /*capabilities*/ ) {} +void DebuggerClientListener::resetState() { + mThreadsModel->resetThreads(); + mStackModel->resetStack(); +} + void DebuggerClientListener::debuggeeExited( int /*exitCode*/ ) { mPlugin->exitDebugger(); + resetState(); } void DebuggerClientListener::debuggeeStopped( const StoppedEvent& event ) { Log::debug( "DebuggerClientListener::debuggeeStopped: reason %s", event.reason ); mStoppedData = event; + mCurrentThreadId = mStoppedData->threadId ? *mStoppedData->threadId : 1; if ( mPausedToRefreshBreakpoints ) { mPlugin->sendPendingBreakpoints(); @@ -143,29 +222,29 @@ void DebuggerClientListener::debuggeeStopped( const StoppedEvent& event ) { } mClient->threads(); + mClient->stackTrace( mCurrentThreadId ); - if ( event.threadId ) - mClient->stackTrace( *event.threadId ); + UISceneNode* sceneNode = mPlugin->getUISceneNode(); + sceneNode->runOnMainThread( [sceneNode] { sceneNode->getWindow()->raise(); } ); } void DebuggerClientListener::debuggeeContinued( const ContinuedEvent& ) { mStoppedData = {}; + mCurrentScopePos = {}; // Reset models mScope.clear(); - getStatusDebuggerController()->getUIThreads()->setModel( - std::make_shared( std::vector{} ) ); + resetState(); - getStatusDebuggerController()->getUIStack()->setModel( - std::make_shared( StackTraceInfo{} ) ); + UISceneNode* sceneNode = mPlugin->getUISceneNode(); + sceneNode->runOnMainThread( [sceneNode] { sceneNode->invalidateDraw(); } ); } void DebuggerClientListener::outputProduced( const Output& output ) { if ( Output::Category::Stdout == output.category || Output::Category::Stderr == output.category ) { - mPlugin->getManager()->getPluginContext()->getStatusAppOutputController()->insertBuffer( - output.output ); + mPlugin->getPluginContext()->getStatusAppOutputController()->insertBuffer( output.output ); } } @@ -178,20 +257,32 @@ void DebuggerClientListener::threadChanged( const ThreadEvent& ) {} void DebuggerClientListener::moduleChanged( const ModuleEvent& ) {} -void DebuggerClientListener::threads( const std::vector& threads ) { - getStatusDebuggerController()->getUIThreads()->setModel( - std::make_shared( threads ) ); +void DebuggerClientListener::threads( std::vector&& threads ) { + mThreadsModel->setThreads( std::move( threads ) ); } -void DebuggerClientListener::stackTrace( const int /*threadId*/, const StackTraceInfo& stack ) { - getStatusDebuggerController()->getUIStack()->setModel( std::make_shared( stack ) ); - +void DebuggerClientListener::stackTrace( const int /*threadId*/, StackTraceInfo&& stack ) { if ( !stack.stackFrames.empty() ) { - mClient->scopes( stack.stackFrames[0].id ); + auto& f = stack.stackFrames[0]; + + // mClient->scopes( f.id ); + + if ( f.source ) { + TextRange range{ { f.line - 1, f.column }, { f.line - 1, f.column } }; + std::string path( f.source->path ); + + mPlugin->getUISceneNode()->runOnMainThread( [this, path, range] { + mPlugin->getPluginContext()->focusOrLoadFile( path, range ); + } ); + + mCurrentScopePos = { f.source->path, f.line }; + } } + + mStackModel->setStack( std::move( stack ) ); } -void DebuggerClientListener::scopes( const int /*frameId*/, const std::vector& scopes ) { +void DebuggerClientListener::scopes( const int /*frameId*/, std::vector&& scopes ) { if ( !scopes.empty() ) { for ( const auto& scope : scopes ) { ModelScope mscope; @@ -204,7 +295,7 @@ void DebuggerClientListener::scopes( const int /*frameId*/, const std::vector& vars ) { + std::vector&& vars ) { auto scopeIt = std::find_if( mScope.begin(), mScope.end(), [variablesReference]( const ModelScope& cur ) { return cur.variablesReference == variablesReference; @@ -214,7 +305,7 @@ void DebuggerClientListener::variables( const int variablesReference, scopeIt->variables = vars; } -void DebuggerClientListener::modules( const ModulesInfo& ) {} +void DebuggerClientListener::modules( ModulesInfo&& ) {} void DebuggerClientListener::serverDisconnected() {} @@ -240,12 +331,16 @@ std::optional DebuggerClientListener::getStoppedData() const { return mStoppedData; } +int DebuggerClientListener::getCurrentThreadId() const { + return mCurrentThreadId; +} + +std::optional> DebuggerClientListener::getCurrentScopePos() const { + return mCurrentScopePos; +} + StatusDebuggerController* DebuggerClientListener::getStatusDebuggerController() const { - auto debuggerElement = - mPlugin->getManager()->getPluginContext()->getStatusBar()->getStatusBarElement( - "status_app_debugger" ); - eeASSERT( debuggerElement ); - return static_cast( debuggerElement.get() ); + return mPlugin->getStatusDebuggerController(); } } // namespace ecode diff --git a/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp b/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp index 08ee88b6e..e3818fda2 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp @@ -5,6 +5,8 @@ namespace ecode { class DebuggerPlugin; +class ThreadsModel; +class StackModel; struct ModelScope { std::string name; @@ -14,11 +16,13 @@ struct ModelScope { class DebuggerClientListener : public DebuggerClient::Listener { public: - static std::vector - fromSet( const EE::UnorderedSet& set ); + static std::vector + fromSet( const EE::UnorderedSet& set ); DebuggerClientListener( DebuggerClient* client, DebuggerPlugin* plugin ); + virtual ~DebuggerClientListener(); + void stateChanged( DebuggerClient::State ); void initialized(); void launched(); @@ -36,11 +40,11 @@ class DebuggerClientListener : public DebuggerClient::Listener { void errorResponse( const std::string& summary, const std::optional& message ); void threadChanged( const ThreadEvent& ); void moduleChanged( const ModuleEvent& ); - void threads( const std::vector& ); - void stackTrace( const int threadId, const StackTraceInfo& ); - void scopes( const int frameId, const std::vector& ); - void variables( const int variablesReference, const std::vector& ); - void modules( const ModulesInfo& ); + void threads( std::vector&& ); + void stackTrace( const int threadId, StackTraceInfo&& ); + void scopes( const int frameId, std::vector&& ); + void variables( const int variablesReference, std::vector&& ); + void modules( ModulesInfo&& ); void serverDisconnected(); void sourceContent( const std::string& path, int reference, const SourceContent& content = SourceContent() ); @@ -57,14 +61,24 @@ class DebuggerClientListener : public DebuggerClient::Listener { void setPausedToRefreshBreakpoints() { mPausedToRefreshBreakpoints = true; } + int getCurrentThreadId() const; + + std::optional> getCurrentScopePos() const; + protected: DebuggerClient* mClient{ nullptr }; DebuggerPlugin* mPlugin{ nullptr }; std::optional mStoppedData; + std::optional> mCurrentScopePos; std::vector mScope; bool mPausedToRefreshBreakpoints{ false }; + int mCurrentThreadId{ 1 }; + std::shared_ptr mThreadsModel; + std::shared_ptr mStackModel; StatusDebuggerController* getStatusDebuggerController() const; + + void resetState(); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp index 03858f0d2..e0f96025e 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp @@ -25,6 +25,51 @@ using json = nlohmann::json; namespace ecode { +class BreakpointsModel : public Model { + public: + BreakpointsModel( + const UnorderedMap>& breakpoints ) { + for ( const auto& bpf : breakpoints ) + for ( const auto& bp : bpf.second ) + mBreakpoints.emplace_back( bpf.first, bp ); + } + + virtual size_t rowCount( const ModelIndex& ) const { return mBreakpoints.size(); } + + virtual size_t columnCount( const ModelIndex& ) const { return 3; } + + virtual std::string columnName( const size_t& index ) const { + switch ( index ) { + case 0: + return "Enabled"; + case 1: + return "Source Path"; + case 2: + return "Line"; + } + return ""; + } + + virtual Variant data( const ModelIndex& modelIndex, ModelRole role ) const { + if ( role != ModelRole::Display ) + return {}; + + switch ( modelIndex.column() ) { + case 0: + return Variant( mBreakpoints[modelIndex.row()].second.enabled ? "Enabled" : "" ); + case 1: + return Variant( mBreakpoints[modelIndex.row()].first.c_str() ); + case 2: + return Variant( String::toString( mBreakpoints[modelIndex.row()].second.line ) ); + } + + return {}; + } + + protected: + std::vector> mBreakpoints; +}; + Plugin* DebuggerPlugin::New( PluginManager* pluginManager ) { return eeNew( DebuggerPlugin, ( pluginManager, false ) ); } @@ -53,9 +98,14 @@ DebuggerPlugin::~DebuggerPlugin() { if ( mSidePanel && mTab ) mSidePanel->removeTab( mTab ); - if ( getManager()->getPluginContext()->getStatusBar() ) { - getManager()->getPluginContext()->getStatusBar()->removeStatusBarElement( - "status_app_debugger" ); + if ( getPluginContext()->getStatusBar() ) + getPluginContext()->getStatusBar()->removeStatusBarElement( "status_app_debugger" ); + + mManager->unsubscribeMessages( this ); + + for ( auto editor : mEditors ) { + onBeforeUnregister( editor.first ); + onUnregisterEditor( editor.first ); } mDebugger.reset(); @@ -171,11 +221,23 @@ void DebuggerPlugin::loadDAPConfig( const std::string& path, bool updateConfigFi if ( mKeyBindings.empty() ) { mKeyBindings["debugger-continue-interrupt"] = "f5"; + mKeyBindings["debugger-breakpoint-toggle"] = "f9"; + mKeyBindings["debugger-breakpoint-enable-toggle"] = "mod+f9"; + mKeyBindings["debugger-step-over"] = "f10"; + mKeyBindings["debugger-step-into"] = "f11"; + mKeyBindings["debugger-step-out"] = "shift+f11"; + mKeyBindings["toggle-status-app-debugger"] = "alt+6"; } if ( j.contains( "keybindings" ) ) { auto& kb = j["keybindings"]; - std::initializer_list list = { "debugger-continue-interrupt" }; + std::initializer_list list = { "debugger-continue-interrupt", + "debugger-breakpoint-toggle", + "debugger-breakpoint-enable-toggle", + "debugger-step-over", + "debugger-step-into", + "debugger-step-out", + "toggle-status-app-debugger" }; for ( const auto& key : list ) { if ( kb.contains( key ) ) { if ( !kb[key].empty() ) @@ -279,15 +341,15 @@ void DebuggerPlugin::buildStatusBar() { hideStatusBarElement(); return; } - if ( getManager()->getPluginContext()->getStatusBar() ) { - auto but = getManager()->getPluginContext()->getStatusBar()->find( "status_app_debugger" ); + if ( getPluginContext()->getStatusBar() ) { + auto but = getPluginContext()->getStatusBar()->find( "status_app_debugger" ); if ( but ) { but->setVisible( true ); return; } } - auto context = getManager()->getPluginContext(); + auto context = getPluginContext(); UIStatusBar* statusBar = context->getStatusBar(); auto debuggerStatusElem = std::make_shared( @@ -398,8 +460,7 @@ void DebuggerPlugin::replaceKeysInJson( nlohmann::json& json ) { static constexpr auto KEY_CWD = "${cwd}"; static constexpr auto KEY_ENV = "${env}"; static constexpr auto KEY_STOPONENTRY = "${stopOnEntry}"; - auto runConfig = - getManager()->getPluginContext()->getProjectBuildManager()->getCurrentRunConfig(); + auto runConfig = getPluginContext()->getProjectBuildManager()->getCurrentRunConfig(); for ( auto& j : json ) { if ( j.is_object() ) { @@ -426,12 +487,10 @@ void DebuggerPlugin::replaceKeysInJson( nlohmann::json& json ) { } void DebuggerPlugin::onRegisterDocument( TextDocument* doc ) { - doc->setCommand( "debugger-continue-interrupt", [this]() { + doc->setCommand( "debugger-continue-interrupt", [this] { if ( mDebugger && mListener ) { if ( mListener->isStopped() ) { - mDebugger->resume( mListener->getStoppedData()->threadId - ? *mListener->getStoppedData()->threadId - : 1 ); + mDebugger->resume( mListener->getCurrentThreadId() ); } else { mDebugger->pause( 1 ); } @@ -439,15 +498,53 @@ void DebuggerPlugin::onRegisterDocument( TextDocument* doc ) { runCurrentConfig(); } } ); + + doc->setCommand( "debugger-breakpoint-toggle", [doc, this] { + if ( setBreakpoint( doc, doc->getSelection().start().line() ) ) + getUISceneNode()->invalidateDraw(); + } ); + + doc->setCommand( "debugger-breakpoint-enable-toggle", [this, doc] { + if ( breakpointToggleEnabled( doc, doc->getSelection().start().line() + 1 ) ) + getUISceneNode()->invalidateDraw(); + } ); + + doc->setCommand( "debugger-step-over", [this] { + if ( mDebugger && mListener && mListener->isStopped() ) + mDebugger->stepOver( mListener->getCurrentThreadId() ); + } ); + + doc->setCommand( "debugger-step-into", [this] { + if ( mDebugger && mListener && mListener->isStopped() ) + mDebugger->stepInto( mListener->getCurrentThreadId() ); + } ); + + doc->setCommand( "debugger-step-out", [this] { + if ( mDebugger && mListener && mListener->isStopped() ) + mDebugger->stepOut( mListener->getCurrentThreadId() ); + } ); + + doc->setCommand( "toggle-status-app-debugger", [this] { + if ( getStatusDebuggerController() ) + getStatusDebuggerController()->toggle(); + } ); } void DebuggerPlugin::onRegisterEditor( UICodeEditor* editor ) { editor->registerGutterSpace( this, PixelDensity::dpToPx( 8 ), 0 ); + editor->addUnlockedCommands( { "debugger-continue-interrupt", + "debugger-breakpoint-enable-toggle", + "toggle-status-app-debugger" } ); + PluginBase::onRegisterEditor( editor ); } void DebuggerPlugin::onUnregisterEditor( UICodeEditor* editor ) { + editor->removeUnlockedCommands( { "debugger-continue-interrupt", + "debugger-breakpoint-enable-toggle", + "toggle-status-app-debugger" } ); + editor->unregisterGutterSpace( this ); } @@ -456,62 +553,116 @@ void DebuggerPlugin::drawLineNumbersBefore( UICodeEditor* editor, const Vector2f& startScroll, const Vector2f& screenStart, const Float& lineHeight, const Float&, const int&, const Float& ) { - if ( !editor->getDocument().hasFilepath() ) - return; - auto docIt = mBreakpoints.find( editor->getDocument().getFilePath() ); - if ( docIt == mBreakpoints.end() || docIt->second.empty() ) - return; - const auto& breakpoints = docIt->second; Primitives p; + Float radius = PixelDensity::dpToPx( 4 ); Float lineOffset = editor->getLineOffset(); - p.setColor( Color( editor->getColorScheme().getEditorColor( SyntaxStyleTypes::Error ) ) - .blendAlpha( editor->getAlpha() ) ); - Float gutterSpace = editor->getGutterSpace( this ); - Float radius = PixelDensity::dpToPx( 3 ); Float offset = editor->getGutterLocalStartOffset( this ); - for ( const SourceBreakpoint& breakpoint : breakpoints ) { - if ( breakpoint.line >= lineRange.first && breakpoint.line <= lineRange.second ) { - if ( !editor->getDocumentView().isLineVisible( breakpoint.line ) ) - continue; - - auto lnPos( Vector2f( - screenStart.x - editor->getPluginsGutterSpace() + offset, - startScroll.y + - editor->getDocumentView().getLineYOffset( breakpoint.line, lineHeight ) + - lineOffset ) ); - - // p.setColor( Color::Gray ); - // p.drawRectangle( { lnPos, Sizef{ gutterSpace, lineHeight } } ); + if ( editor->getDocument().hasFilepath() ) { + auto docIt = mBreakpoints.find( editor->getDocument().getFilePath() ); + if ( docIt != mBreakpoints.end() && !docIt->second.empty() ) { + const auto& breakpoints = docIt->second; p.setColor( Color( editor->getColorScheme().getEditorColor( SyntaxStyleTypes::Error ) ) .blendAlpha( editor->getAlpha() ) ); + Float gutterSpace = editor->getGutterSpace( this ); - p.drawCircle( { lnPos.x + radius + eefloor( ( gutterSpace - radius ) * 0.5f ), - lnPos.y + lineHeight * 0.5f }, - radius ); + for ( const SourceBreakpoint& breakpoint : breakpoints ) { + int line = breakpoint.line - 1; // Breakpoints start at 1 + if ( line >= 0 && line >= lineRange.first && line <= lineRange.second ) { + if ( !editor->getDocumentView().isLineVisible( line ) ) + continue; + + auto lnPos( + Vector2f( screenStart.x - editor->getPluginsGutterSpace() + offset, + startScroll.y + + editor->getDocumentView().getLineYOffset( line, lineHeight ) + + lineOffset ) ); + + p.setColor( + Color( editor->getColorScheme().getEditorColor( SyntaxStyleTypes::Error ) ) + .blendAlpha( editor->getAlpha() ) ); + + p.drawCircle( { lnPos.x + radius + eefloor( ( gutterSpace - radius ) * 0.5f ), + lnPos.y + lineHeight * 0.5f }, + radius ); + } + } } } + if ( mDebugger && mListener && mListener->isStopped() && mListener->getCurrentScopePos() && + editor->getDocument().getFilePath() == mListener->getCurrentScopePos()->first ) { + int line = mListener->getCurrentScopePos()->second - 1; + if ( line >= 0 && line >= lineRange.first && line <= lineRange.second && + editor->getDocumentView().isLineVisible( line ) ) { + auto lnPos( Vector2f( screenStart.x - editor->getPluginsGutterSpace() + offset, + startScroll.y + + editor->getDocumentView().getLineYOffset( line, lineHeight ) + + lineOffset ) ); + p.setColor( + Color( editor->getColorScheme().getEditorColor( SyntaxStyleTypes::Warning ) ) + .blendAlpha( editor->getAlpha() ) ); + + Float dim = radius * 2; + Float gutterSpace = editor->getGutterSpace( this ); + lnPos.x += ( gutterSpace - dim ) * 0.5f; + lnPos.y += ( lineHeight - dim ) * 0.5f; + + Triangle2f tri; + tri.V[0] = lnPos + Vector2f{ 0, 0 }; + tri.V[1] = lnPos + Vector2f{ 0, dim }; + tri.V[2] = lnPos + Vector2f{ dim, dim * 0.5f }; + p.drawTriangle( tri ); + } + } } -bool DebuggerPlugin::setBreakpoint( UICodeEditor* editor, Uint32 lineNumber ) { - if ( !editor->getDocument().hasFilepath() ) +bool DebuggerPlugin::setBreakpoint( TextDocument* doc, Uint32 lineNumber ) { + if ( !doc->hasFilepath() ) return false; - if ( !isSupportedByAnyDebugger( editor->getDocument().getSyntaxDefinition().getLSPName() ) ) + if ( !isSupportedByAnyDebugger( doc->getSyntaxDefinition().getLSPName() ) ) return false; Lock l( mBreakpointsMutex ); - auto& breakpoints = mBreakpoints[editor->getDocument().getFilePath()]; + auto& breakpoints = mBreakpoints[doc->getFilePath()]; auto breakpointIt = breakpoints.find( SourceBreakpointStateful( lineNumber ) ); if ( breakpointIt != breakpoints.end() ) { breakpoints.erase( breakpointIt ); } else { breakpoints.insert( SourceBreakpointStateful( lineNumber ) ); } - mThreadPool->run( - [this, editor] { sendFileBreakpoints( editor->getDocument().getFilePath() ); } ); - editor->invalidateDraw(); + mThreadPool->run( [this, doc] { sendFileBreakpoints( doc->getFilePath() ); } ); + + if ( getStatusDebuggerController()->getWidget() == nullptr ) { + getStatusDebuggerController()->show(); + getStatusDebuggerController()->hide(); + } + + getStatusDebuggerController()->getUIBreakpoints()->setModel( + std::make_shared( mBreakpoints ) ); + + return true; +} + +bool DebuggerPlugin::breakpointToggleEnabled( TextDocument* doc, Uint32 lineNumber ) { + if ( !doc->hasFilepath() ) + return false; + if ( !isSupportedByAnyDebugger( doc->getSyntaxDefinition().getLSPName() ) ) + return false; + Lock l( mBreakpointsMutex ); + auto& breakpoints = mBreakpoints[doc->getFilePath()]; + auto breakpointIt = breakpoints.find( SourceBreakpointStateful( lineNumber ) ); + if ( breakpointIt != breakpoints.end() ) { + breakpointIt->enabled = !breakpointIt->enabled; + return true; + } + return false; +} + +bool DebuggerPlugin::setBreakpoint( UICodeEditor* editor, Uint32 lineNumber ) { + if ( setBreakpoint( &editor->getDocument(), lineNumber ) ) + editor->invalidateDraw(); return true; } @@ -526,7 +677,7 @@ bool DebuggerPlugin::onMouseDown( UICodeEditor* editor, const Vector2i& position localPos.y > editor->getPluginsTopSpace() ) { if ( editor->getUISceneNode()->getEventDispatcher()->isFirstPress() ) { auto cursorPos( editor->resolveScreenPosition( position.asFloat() ) ); - setBreakpoint( editor, cursorPos.line() ); + setBreakpoint( editor, cursorPos.line() + 1 ); } return true; } @@ -613,11 +764,17 @@ void DebuggerPlugin::hideSidePanel() { } void DebuggerPlugin::hideStatusBarElement() { - if ( getManager()->getPluginContext()->getStatusBar() ) { - auto but = getManager()->getPluginContext()->getStatusBar()->find( "status_app_debugger" ); + if ( getPluginContext()->getStatusBar() ) { + auto but = getPluginContext()->getStatusBar()->find( "status_app_debugger" ); if ( but ) but->setVisible( false ); } } +StatusDebuggerController* DebuggerPlugin::getStatusDebuggerController() const { + auto debuggerElement = + getPluginContext()->getStatusBar()->getStatusBarElement( "status_app_debugger" ); + eeASSERT( debuggerElement ); + return static_cast( debuggerElement.get() ); +} } // namespace ecode diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp index f931c4f0b..6fe7bb89d 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp @@ -109,6 +109,10 @@ class DebuggerPlugin : public PluginBase { bool setBreakpoint( UICodeEditor* editor, Uint32 lineNumber ); + bool setBreakpoint( TextDocument* doc, Uint32 lineNumber ); + + bool breakpointToggleEnabled( TextDocument* doc, Uint32 lineNumber ); + bool onMouseDown( UICodeEditor*, const Vector2i&, const Uint32& flags ) override; bool isSupportedByAnyDebugger( const std::string& language ); @@ -116,7 +120,10 @@ class DebuggerPlugin : public PluginBase { void runCurrentConfig(); void sendFileBreakpoints( const std::string& filePath ); + void sendPendingBreakpoints(); + + StatusDebuggerController* getStatusDebuggerController() const; }; } // namespace ecode diff --git a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp index 911be75c0..f33026064 100644 --- a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp +++ b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp @@ -34,7 +34,7 @@ void StatusDebuggerController::createContainer() { if ( mContainer ) return; const auto XML = R"xml( - + @@ -57,7 +57,10 @@ void StatusDebuggerController::createContainer() { mContainer->bind( "debugger_stack", mUIStack ); mContainer->bind( "debugger_breakpoints", mUIBreakpoints ); - mContainer->runOnMainThread( [this] { + mContainer->on( Event::OnSizeChange, [this]( const Event* event ) { + if ( !mContainer->isVisible() || mContainer->getSize().getWidth() == 0.f ) + return; + const Float width = mContainer->getPixelsSize().getWidth(); mUIThreads->setColumnWidth( 0, width * 0.1 ); @@ -69,6 +72,12 @@ void StatusDebuggerController::createContainer() { mUIStack->setColumnWidth( 3, eefloor( width * 0.3 ) ); mUIStack->setColumnWidth( 4, width * 0.08 ); mUIStack->setColumnWidth( 5, width * 0.08 ); + + mUIBreakpoints->setColumnWidth( 0, width * 0.1 ); + mUIBreakpoints->setColumnWidth( 1, eefloor( width * 0.7 ) ); + mUIBreakpoints->setColumnWidth( 2, eefloor( width * 0.1 ) ); + + mContainer->removeEventListener( event->getCallbackId() ); } ); } diff --git a/src/tools/ecode/plugins/plugin.cpp b/src/tools/ecode/plugins/plugin.cpp index 5d627ac9f..6b1265c8a 100644 --- a/src/tools/ecode/plugins/plugin.cpp +++ b/src/tools/ecode/plugins/plugin.cpp @@ -45,6 +45,10 @@ PluginManager* Plugin::getManager() const { return mManager; } +PluginContextProvider* Plugin::getPluginContext() const { + return mManager ? mManager->getPluginContext() : nullptr; +} + UISceneNode* Plugin::getUISceneNode() const { return mManager->getUISceneNode(); } @@ -117,8 +121,8 @@ void Plugin::setReady( Time loadTime ) { PluginBase::~PluginBase() { mShuttingDown = true; unsubscribeFileSystemListener(); + for ( auto editor : mEditors ) { - onBeforeUnregister( editor.first ); for ( auto listener : editor.second ) editor.first->removeEventListener( listener ); editor.first->unregisterPlugin( this ); diff --git a/src/tools/ecode/plugins/plugin.hpp b/src/tools/ecode/plugins/plugin.hpp index 09c1cf26e..59a2e434e 100644 --- a/src/tools/ecode/plugins/plugin.hpp +++ b/src/tools/ecode/plugins/plugin.hpp @@ -12,6 +12,7 @@ using namespace EE::UI::Models; namespace ecode { class PluginManager; +class PluginContextProvider; class Plugin : public UICodeEditorPlugin { public: @@ -33,6 +34,8 @@ class Plugin : public UICodeEditorPlugin { PluginManager* getManager() const; + PluginContextProvider* getPluginContext() const; + UISceneNode* getUISceneNode() const; virtual String::HashType getConfigFileHash() { return 0; } diff --git a/src/tools/ecode/plugins/plugincontextprovider.hpp b/src/tools/ecode/plugins/plugincontextprovider.hpp index d16763e63..c52a5c996 100644 --- a/src/tools/ecode/plugins/plugincontextprovider.hpp +++ b/src/tools/ecode/plugins/plugincontextprovider.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace EE { @@ -33,6 +34,7 @@ using namespace EE; using namespace EE::Graphics; using namespace EE::UI; using namespace EE::UI::Tools; +using namespace EE::UI::Doc; namespace ecode { @@ -50,7 +52,6 @@ class TerminalConfig; class PluginContextProvider { public: - virtual UIStatusBar* getStatusBar() const = 0; virtual UISplitter* getMainSplitter() const = 0; @@ -115,6 +116,8 @@ class PluginContextProvider { virtual const std::string& getCurrentProject() const = 0; virtual std::string getCurrentWorkingDir() const = 0; + + virtual void focusOrLoadFile( const std::string& path, const TextRange& range = {} ) = 0; }; } // namespace ecode diff --git a/src/tools/ecode/universallocator.cpp b/src/tools/ecode/universallocator.cpp index b9547496b..65ee16fc6 100644 --- a/src/tools/ecode/universallocator.cpp +++ b/src/tools/ecode/universallocator.cpp @@ -590,7 +590,7 @@ void UniversalLocator::initLocateBar( UILocateBar* locateBar, UITextInput* locat range = { pathAndPos.second, pathAndPos.second }; } - focusOrLoadFile( path, range ); + mApp->focusOrLoadFile( path, range ); mLocateBarLayout->execute( "close-locatebar" ); } } ); @@ -761,27 +761,6 @@ std::shared_ptr UniversalLocator::openDocumentsModel( const std:: return std::make_shared( std::move( ffiles ), std::move( fnames ) ); } -void UniversalLocator::focusOrLoadFile( const std::string& path, const TextRange& range ) { - UITab* tab = mSplitter->isDocumentOpen( path, true ); - if ( !tab ) { - FileInfo fileInfo( path ); - if ( fileInfo.exists() && fileInfo.isRegularFile() ) - mApp->loadFileFromPath( - path, true, nullptr, [this, range]( UICodeEditor* editor, auto ) { - if ( range.isValid() ) { - editor->goToLine( range.start() ); - mSplitter->addEditorPositionToNavigationHistory( editor ); - } - } ); - } else { - tab->getTabWidget()->setTabSelected( tab ); - if ( range.isValid() ) { - UICodeEditor* editor = tab->getOwnedWidget()->asType(); - editor->goToLine( range.start() ); - mSplitter->addEditorPositionToNavigationHistory( editor ); - } - } -} void UniversalLocator::updateOpenDocumentsTable() { mLocateTable->setModel( diff --git a/src/tools/ecode/universallocator.hpp b/src/tools/ecode/universallocator.hpp index 0cc9953b2..def105aef 100644 --- a/src/tools/ecode/universallocator.hpp +++ b/src/tools/ecode/universallocator.hpp @@ -132,8 +132,6 @@ class UniversalLocator { std::shared_ptr openDocumentsModel( const std::string& match ); - void focusOrLoadFile( const std::string& path, const TextRange& range = {} ); - std::shared_ptr> openBuildModel( const std::string& match ); std::shared_ptr> openBuildTypeModel( const std::string& match );