diff --git a/include/eepp/ui/models/modelrole.hpp b/include/eepp/ui/models/modelrole.hpp index 00e6f223e..f91752f1f 100644 --- a/include/eepp/ui/models/modelrole.hpp +++ b/include/eepp/ui/models/modelrole.hpp @@ -3,7 +3,7 @@ namespace EE { namespace UI { namespace Models { -enum class ModelRole { Display, Icon, Sort, Class, Tooltip, TooltipClass, Custom }; +enum class ModelRole { Display, Icon, Sort, Class, Tooltip, TooltipClass, Data, Custom }; }}} // namespace EE::UI::Models diff --git a/include/eepp/ui/uipushbutton.hpp b/include/eepp/ui/uipushbutton.hpp index f1f23757c..665e043f6 100644 --- a/include/eepp/ui/uipushbutton.hpp +++ b/include/eepp/ui/uipushbutton.hpp @@ -81,12 +81,17 @@ class EE_API UIPushButton : public UIWidget { void setTextAsFallback( bool textAsFallback ); + bool dontAutoHideEmptyTextBox() const; + + void setDontAutoHideEmptyTextBox( bool dontAutoHideEmptyTextBox ); + protected: UIImage* mIcon; UITextView* mTextBox; Sizei mIconMinSize; InnerWidgetOrientation mInnerWidgetOrientation{ InnerWidgetOrientation::IconTextBoxWidget }; bool mTextAsFallback{ false }; + bool mDontAutoHideEmptyTextBox{ false }; UIPushButton(); @@ -122,6 +127,8 @@ class EE_API UIPushButton : public UIWidget { Vector2f packLayout( const std::array& widgets, const Rectf& padding ); Vector2f calcLayoutSize( const std::array& widgets, const Rectf& padding ) const; + + bool mustBeVisible() const; }; }} // namespace EE::UI diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 82a19874b..dfef7250b 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -1588,6 +1588,8 @@ ../../src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp ../../src/tools/ecode/plugins/debugger/debuggerplugin.cpp ../../src/tools/ecode/plugins/debugger/debuggerplugin.hpp +../../src/tools/ecode/plugins/debugger/models/breakpointsmodel.cpp +../../src/tools/ecode/plugins/debugger/models/breakpointsmodel.hpp ../../src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp ../../src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp ../../src/tools/ecode/plugins/formatter/formatterplugin.cpp diff --git a/src/eepp/ui/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index 2cfad5fae..5ed5b8765 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -310,6 +310,17 @@ void UIPushButton::setTextAsFallback( bool textAsFallback ) { } } +bool UIPushButton::dontAutoHideEmptyTextBox() const { + return mDontAutoHideEmptyTextBox; +} + +void UIPushButton::setDontAutoHideEmptyTextBox( bool dontAutoHideEmptyTextBox ) { + if ( mDontAutoHideEmptyTextBox != dontAutoHideEmptyTextBox ) { + mDontAutoHideEmptyTextBox = dontAutoHideEmptyTextBox; + updateTextBox(); + } +} + void UIPushButton::onPaddingChange() { onSizeChange(); @@ -339,9 +350,13 @@ void UIPushButton::onThemeLoaded() { UIWidget::onThemeLoaded(); } +bool UIPushButton::mustBeVisible() const { + return ( ( !getText().empty() || mDontAutoHideEmptyTextBox ) && !mTextAsFallback ) || + nullptr == mIcon || nullptr == mIcon->getDrawable(); +} + void UIPushButton::updateTextBox() { - bool mustBeVisible = ( !getText().empty() && !mTextAsFallback ) || - ( nullptr == mIcon || nullptr == mIcon->getDrawable() ); + bool mustBeVisible = this->mustBeVisible(); if ( mTextBox->isVisible() != mustBeVisible ) { mTextBox->setVisible( mustBeVisible ); onAutoSize(); @@ -393,8 +408,8 @@ bool UIPushButton::hasIcon() const { UIPushButton* UIPushButton::setText( const String& text ) { if ( text != mTextBox->getText() ) { - mTextBox->setVisible( !text.empty() ); mTextBox->setText( text ); + mTextBox->setVisible( mustBeVisible() ); onAutoSize(); updateLayout(); } diff --git a/src/tools/ecode/iconmanager.cpp b/src/tools/ecode/iconmanager.cpp index 123b250b7..e9b574a8e 100644 --- a/src/tools/ecode/iconmanager.cpp +++ b/src/tools/ecode/iconmanager.cpp @@ -260,7 +260,8 @@ void IconManager::init( UISceneNode* sceneNode, FontTrueType* iconFont, FontTrue { "debug-step-into", 0xead4 }, { "debug-step-out", 0xead5 }, { "debug-step-over", 0xead6 }, - { "debug-stop", 0xead7 } }; + { "debug-stop", 0xead7 }, + { "chrome-close", 0xeab8 } }; for ( const auto& icon : codIcons ) iconTheme->add( UIGlyphIcon::New( icon.first, codIconFont, icon.second ) ); diff --git a/src/tools/ecode/notificationcenter.cpp b/src/tools/ecode/notificationcenter.cpp index 52423e823..2186d3ab5 100644 --- a/src/tools/ecode/notificationcenter.cpp +++ b/src/tools/ecode/notificationcenter.cpp @@ -1,4 +1,10 @@ #include "notificationcenter.hpp" +#include +#include +#include +#include + +using namespace EE::Scene; namespace ecode { diff --git a/src/tools/ecode/notificationcenter.hpp b/src/tools/ecode/notificationcenter.hpp index e5ed81215..29be1beb2 100644 --- a/src/tools/ecode/notificationcenter.hpp +++ b/src/tools/ecode/notificationcenter.hpp @@ -2,7 +2,6 @@ #define ECODE_NOTIFICATIONCENTER_HPP #include "plugins/pluginmanager.hpp" -#include namespace ecode { @@ -10,7 +9,8 @@ class NotificationCenter { public: NotificationCenter( UILayout* layout, PluginManager* pluginManager ); - void addNotification( const String& text, const Time& delay = Seconds( 2.5 ), bool allowCopy = false ); + void addNotification( const String& text, const Time& delay = Seconds( 2.5 ), + bool allowCopy = false ); void addShowRequest( const String& uri, const String& actionText, const Time& delay = Seconds( 2.5 ) ); diff --git a/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp b/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp index 28b3b117d..5b5d8a908 100644 --- a/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp +++ b/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp @@ -487,7 +487,7 @@ bool DebuggerClientDap::disconnect( bool restart ) { bool DebuggerClientDap::threads() { makeRequest( DAP_THREADS, {}, [this]( const Response& response, const nlohmann::json& ) { if ( response.success ) { - std::vector threads( Thread::parseList( response.body[DAP_THREADS] ) ); + std::vector threads( DapThread::parseList( response.body[DAP_THREADS] ) ); for ( auto listener : mListeners ) listener->threads( std::move( threads ) ); } else { diff --git a/src/tools/ecode/plugins/debugger/dap/protocol.cpp b/src/tools/ecode/plugins/debugger/dap/protocol.cpp index ebf7386eb..b3dec45fc 100644 --- a/src/tools/ecode/plugins/debugger/dap/protocol.cpp +++ b/src/tools/ecode/plugins/debugger/dap/protocol.cpp @@ -255,13 +255,13 @@ StoppedEvent::StoppedEvent( const json& body ) : allThreadsStopped( parseOptionalBool( body, "allThreadsStopped" ) ), hitBreakpointsIds( parseOptionalIntList( body, "hitBreakpointsIds" ) ) {} -Thread::Thread( const json& body ) : +DapThread::DapThread( const json& body ) : id( body[DAP_ID].get() ), name( body[DAP_NAME].get() ) {} -Thread::Thread( const int id ) : id( id ), name( std::string() ) {} +DapThread::DapThread( const int id ) : id( id ), name( std::string() ) {} -std::vector Thread::parseList( const json& threads ) { - return parseObjectList( threads ); +std::vector DapThread::parseList( const json& threads ) { + return parseObjectList( threads ); } StackFrame::StackFrame( const json& body ) : diff --git a/src/tools/ecode/plugins/debugger/dap/protocol.hpp b/src/tools/ecode/plugins/debugger/dap/protocol.hpp index 6e63bbf4e..2b8553282 100644 --- a/src/tools/ecode/plugins/debugger/dap/protocol.hpp +++ b/src/tools/ecode/plugins/debugger/dap/protocol.hpp @@ -360,15 +360,15 @@ struct BreakpointEvent { BreakpointEvent( const json& body ); }; -struct Thread { +struct DapThread { int id; std::string name; - Thread() = default; - Thread( const json& body ); - explicit Thread( const int id ); + DapThread() = default; + DapThread( const json& body ); + explicit DapThread( const int id ); - static std::vector parseList( const json& threads ); + static std::vector parseList( const json& threads ); }; struct StackFrame { diff --git a/src/tools/ecode/plugins/debugger/debuggerclient.hpp b/src/tools/ecode/plugins/debugger/debuggerclient.hpp index f136af551..416ddba1d 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclient.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerclient.hpp @@ -30,7 +30,7 @@ class DebuggerClient { const std::optional& message ) = 0; virtual void threadChanged( const ThreadEvent& ) = 0; virtual void moduleChanged( const ModuleEvent& ) = 0; - virtual void threads( std::vector&& ) = 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; diff --git a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp index 88068bb40..1677e1e82 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp @@ -86,8 +86,8 @@ std::unordered_map ModelVariableNode::nodeMap = class VariablesModel : public Model { public: - VariablesModel( ModelVariableNode::NodePtr rootNode, i18nFn fn ) : - rootNode( rootNode ), mi18nFn( fn ) {} + VariablesModel( ModelVariableNode::NodePtr rootNode, UISceneNode* sceneNode ) : + rootNode( rootNode ), mSceneNode( sceneNode ) {} ModelIndex index( int row, int column, const ModelIndex& parent = ModelIndex() ) const override { @@ -147,11 +147,11 @@ class VariablesModel : public Model { std::string columnName( const size_t& colIdx ) const override { switch ( colIdx ) { case 0: - return mi18nFn( "variable_name", "Variable Name" ); + return mSceneNode->i18n( "variable_name", "Variable Name" ); case 1: - return mi18nFn( "value", "Value" ); + return mSceneNode->i18n( "value", "Value" ); case 2: - return mi18nFn( "type", "Type" ); + return mSceneNode->i18n( "type", "Type" ); } return ""; } @@ -179,13 +179,13 @@ class VariablesModel : public Model { protected: ModelVariableNode::NodePtr rootNode; - i18nFn mi18nFn; + UISceneNode* mSceneNode; }; class ThreadsModel : public Model { public: - ThreadsModel( const std::vector& threads, i18nFn fn ) : - mThreads( threads ), mi18nFn( std::move( fn ) ) {} + ThreadsModel( const std::vector& threads, UISceneNode* sceneNode ) : + mThreads( threads ), mSceneNode( sceneNode ) {} virtual size_t rowCount( const ModelIndex& ) const { return mThreads.size(); } virtual size_t columnCount( const ModelIndex& ) const { return 1; } @@ -193,7 +193,7 @@ class ThreadsModel : public Model { virtual std::string columnName( const size_t& colIdx ) const { switch ( colIdx ) { case 0: - return mi18nFn( "thread_id", "Thread ID" ); + return mSceneNode->i18n( "thread_id", "Thread ID" ); } return ""; } @@ -202,11 +202,15 @@ class ThreadsModel : public Model { if ( role == ModelRole::Display && modelIndex.column() == 0 ) { return Variant( String::format( "#%d (%s)", mThreads[modelIndex.row()].id, mThreads[modelIndex.row()].name.c_str() ) ); + } else if ( role == ModelRole::Icon && modelIndex.column() == 0 && + mThreads[modelIndex.row()].id == mCurrentThreadId ) { + static UIIcon* circleFilled = mSceneNode->findIcon( "circle-filled" ); + return Variant( circleFilled ); } return {}; } - void setThreads( std::vector&& threads ) { + void setThreads( std::vector&& threads ) { { Lock l( mResourceLock ); mThreads = std::move( threads ); @@ -223,7 +227,7 @@ class ThreadsModel : public Model { invalidate(); } - const Thread& getThread( size_t index ) const { + const DapThread& getThread( size_t index ) const { Lock l( mResourceLock ); eeASSERT( index < mThreads.size() ); return mThreads[index]; @@ -232,22 +236,30 @@ class ThreadsModel : public Model { ModelIndex fromThreadId( int id ) { Lock l( mResourceLock ); for ( size_t i = 0; i < mThreads.size(); i++ ) { - const Thread& thread = mThreads[i]; + const DapThread& thread = mThreads[i]; if ( thread.id == id ) return index( i ); } return {}; } + void setCurrentThreadId( int id ) { + if ( mCurrentThreadId != id ) { + mCurrentThreadId = id; + invalidate( Model::UpdateFlag::DontInvalidateIndexes ); + } + } + protected: - std::vector mThreads; - i18nFn mi18nFn; + std::vector mThreads; + UISceneNode* mSceneNode{ nullptr }; + int mCurrentThreadId{ 1 }; }; class StackModel : public Model { public: - StackModel( StackTraceInfo&& stack, i18nFn fn ) : - mStack( std::move( stack ) ), mi18nFn( std::move( fn ) ) {} + StackModel( StackTraceInfo&& stack, UISceneNode* sceneNode ) : + mStack( std::move( stack ) ), mSceneNode( sceneNode ) {} virtual size_t rowCount( const ModelIndex& ) const { Lock l( mResourceLock ); @@ -259,15 +271,15 @@ class StackModel : public Model { virtual std::string columnName( const size_t& colIdx ) const { switch ( colIdx ) { case 0: - return mi18nFn( "id", "ID" ); + return mSceneNode->i18n( "id", "ID" ); case 1: - return mi18nFn( "name", "Name" ); + return mSceneNode->i18n( "name", "Name" ); case 2: - return mi18nFn( "source_name", "Source Name" ); + return mSceneNode->i18n( "source_name", "Source Name" ); case 3: - return mi18nFn( "source_path", "Source Path" ); + return mSceneNode->i18n( "source_path", "Source Path" ); case 4: - return mi18nFn( "line", "Line" ); + return mSceneNode->i18n( "line", "Line" ); } return ""; } @@ -294,6 +306,10 @@ class StackModel : public Model { return Variant( String::toString( mStack.stackFrames[modelIndex.row()].column ) ); } + } else if ( role == ModelRole::Icon && modelIndex.column() == 1 && + mCurrentScopeId == mStack.stackFrames[modelIndex.row()].id ) { + static UIIcon* circleFilled = mSceneNode->findIcon( "circle-filled" ); + return Variant( circleFilled ); } return {}; } @@ -321,9 +337,17 @@ class StackModel : public Model { return mStack.stackFrames[index]; } + void setCurrentScopeId( int scope ) { + if ( mCurrentScopeId != scope ) { + mCurrentScopeId = scope; + invalidate( Model::UpdateFlag::DontInvalidateIndexes ); + } + } + protected: StackTraceInfo mStack; - i18nFn mi18nFn; + UISceneNode* mSceneNode{ nullptr }; + int mCurrentScopeId{ 0 }; }; DebuggerClientListener::DebuggerClientListener( DebuggerClient* client, DebuggerPlugin* plugin ) : @@ -359,24 +383,17 @@ void DebuggerClientListener::stateChanged( DebuggerClient::State state ) { UISceneNode* sceneNode = mPlugin->getUISceneNode(); if ( !mThreadsModel ) { - mThreadsModel = std::make_shared( - std::vector{}, [sceneNode]( const auto& key, const auto& val ) { - return sceneNode->i18n( key, val ); - } ); + mThreadsModel = + std::make_shared( std::vector{}, sceneNode ); + mThreadsModel->setCurrentThreadId( mCurrentThreadId ); } if ( !mStackModel ) { - mStackModel = std::make_shared( - StackTraceInfo{}, [sceneNode]( const auto& key, const auto& val ) { - return sceneNode->i18n( key, val ); - } ); + mStackModel = std::make_shared( StackTraceInfo{}, sceneNode ); } if ( !mVariablesModel ) { - mVariablesModel = std::make_shared( - mVariablesRoot, [sceneNode]( const auto& key, const auto& val ) { - return sceneNode->i18n( key, val ); - } ); + mVariablesModel = std::make_shared( mVariablesRoot, sceneNode ); } UITableView* uiThreads = getStatusDebuggerController()->getUIThreads(); @@ -473,9 +490,7 @@ void DebuggerClientListener::debuggeeStopped( const StoppedEvent& event ) { auto sdc = getStatusDebuggerController(); if ( sdc ) { - sdc->getWidget()->runOnMainThread( [sdc] { - sdc->show(); - } ); + sdc->getWidget()->runOnMainThread( [sdc] { sdc->show(); } ); } } @@ -504,13 +519,19 @@ void DebuggerClientListener::threadChanged( const ThreadEvent& ) {} void DebuggerClientListener::moduleChanged( const ModuleEvent& ) {} -void DebuggerClientListener::threads( std::vector&& threads ) { +void DebuggerClientListener::threads( std::vector&& threads ) { + std::sort( threads.begin(), threads.end(), + []( const DapThread& a, const DapThread& b ) { return a.id < b.id; } ); + mThreadsModel->setThreads( std::move( threads ) ); } void DebuggerClientListener::changeScope( const StackFrame& f ) { mClient->scopes( f.id ); + if ( mStackModel ) + mStackModel->setCurrentScopeId( f.id ); + if ( !f.source ) return; @@ -528,6 +549,8 @@ void DebuggerClientListener::changeScope( const StackFrame& f ) { void DebuggerClientListener::changeThread( int id ) { mCurrentThreadId = id; + if ( mThreadsModel ) + mThreadsModel->setCurrentThreadId( id ); if ( getStatusDebuggerController() && getStatusDebuggerController()->getUIThreads() ) { getStatusDebuggerController()->getUIThreads()->setSelection( mThreadsModel->fromThreadId( id ) ); @@ -584,6 +607,8 @@ void DebuggerClientListener::variables( const int variablesReference, parentNode = *node; } + bool invalidateIndexes = false; + for ( auto& var : vars ) { if ( var.name.empty() ) continue; @@ -591,18 +616,23 @@ void DebuggerClientListener::variables( const int variablesReference, auto found = parentNode->getChild( var.name ); if ( found ) { ( *found )->var = std::move( var ); - mVariablesModel->invalidate( Model::UpdateFlag::DontInvalidateIndexes ); + if ( ( *found )->var.variablesReference != 0 ) ModelVariableNode::nodeMap[( *found )->var.variablesReference] = *found; continue; } + invalidateIndexes = true; + auto child = std::make_shared( std::move( var ), parentNode ); parentNode->addChild( child ); if ( child->var.variablesReference != 0 ) ModelVariableNode::nodeMap[child->var.variablesReference] = child; } + + mVariablesModel->invalidate( invalidateIndexes ? Model::UpdateFlag::InvalidateAllIndexes + : Model::UpdateFlag::DontInvalidateIndexes ); } void DebuggerClientListener::modules( ModulesInfo&& ) {} diff --git a/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp b/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp index 383177792..fac91dd80 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp @@ -36,7 +36,7 @@ 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( std::vector&& ); + void threads( std::vector&& ); void stackTrace( const int threadId, StackTraceInfo&& ); void scopes( const int frameId, std::vector&& ); void variables( const int variablesReference, std::vector&& ); diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp index 628d794bd..b3160b828 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp @@ -5,6 +5,7 @@ #include "../../widgetcommandexecuter.hpp" #include "busprocess.hpp" #include "dap/debuggerclientdap.hpp" +#include "models/breakpointsmodel.hpp" #include "statusdebuggercontroller.hpp" #include #include @@ -27,51 +28,6 @@ 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 ) ); } @@ -289,12 +245,14 @@ PluginRequestHandle DebuggerPlugin::processMessage( const PluginMessage& msg ) { case PluginMessageType::WorkspaceFolderChanged: { mProjectPath = msg.asJSON()["folder"]; - if ( getUISceneNode() && mSidePanel ) + if ( getUISceneNode() && mSidePanel ) { getUISceneNode()->runOnMainThread( [this] { if ( mProjectPath.empty() ) hideSidePanel(); } ); + } + mBreakpointsModel.reset(); updateUI(); mInitialized = true; break; @@ -454,9 +412,10 @@ void DebuggerPlugin::sendFileBreakpoints( const std::string& filePath ) { auto fileBps = mBreakpoints.find( filePath ); if ( fileBps == mBreakpoints.end() ) return; - for ( const auto& fileBps : mBreakpoints ) + for ( const auto& fileBps : mBreakpoints ) { mDebugger->setBreakpoints( fileBps.first, DebuggerClientListener::fromSet( fileBps.second ) ); + } } void DebuggerPlugin::updateDebuggerConfigurationList() { @@ -597,7 +556,7 @@ void DebuggerPlugin::drawLineNumbersBefore( UICodeEditor* editor, .blendAlpha( editor->getAlpha() ) ); Float gutterSpace = editor->getGutterSpace( this ); - for ( const SourceBreakpoint& breakpoint : breakpoints ) { + for ( const SourceBreakpointStateful& 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 ) ) @@ -609,9 +568,10 @@ void DebuggerPlugin::drawLineNumbersBefore( UICodeEditor* editor, editor->getDocumentView().getLineYOffset( line, lineHeight ) + lineOffset ) ); - p.setColor( - Color( editor->getColorScheme().getEditorColor( SyntaxStyleTypes::Error ) ) - .blendAlpha( editor->getAlpha() ) ); + p.setColor( Color( editor->getColorScheme().getEditorColor( + breakpoint.enabled ? SyntaxStyleTypes::Error + : SyntaxStyleTypes::LineNumber2 ) ) + .blendAlpha( editor->getAlpha() ) ); p.drawCircle( { lnPos.x + radius + eefloor( ( gutterSpace - radius ) * 0.5f ), lnPos.y + lineHeight * 0.5f }, @@ -649,30 +609,79 @@ void DebuggerPlugin::drawLineNumbersBefore( UICodeEditor* editor, } } +bool DebuggerPlugin::setBreakpoint( const std::string& doc, Uint32 lineNumber ) { + Lock l( mBreakpointsMutex ); + + auto sdc = getStatusDebuggerController(); + 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 ) + mBreakpointsModel = std::make_shared( mBreakpoints, getUISceneNode() ); + + if ( sdc && sdc->getUIBreakpoints()->getModel() == nullptr ) + sdc->getUIBreakpoints()->setModel( mBreakpointsModel ); + + auto& breakpoints = mBreakpoints[doc]; + auto breakpointIt = breakpoints.find( SourceBreakpointStateful( lineNumber ) ); + if ( breakpointIt != breakpoints.end() ) { + breakpoints.erase( breakpointIt ); + mBreakpointsModel->erase( doc, lineNumber ); + } else { + breakpoints.insert( SourceBreakpointStateful( lineNumber ) ); + mBreakpointsModel->insert( doc, lineNumber ); + } + + mThreadPool->run( [this, doc] { sendFileBreakpoints( doc ); } ); + + getUISceneNode()->invalidateDraw(); + + return true; +} + bool DebuggerPlugin::setBreakpoint( TextDocument* doc, Uint32 lineNumber ) { if ( !doc->hasFilepath() ) return false; if ( !isSupportedByAnyDebugger( doc->getSyntaxDefinition().getLSPName() ) ) return false; + return setBreakpoint( doc->getFilePath(), lineNumber ); +} + +bool DebuggerPlugin::setBreakpoint( UICodeEditor* editor, Uint32 lineNumber ) { + if ( setBreakpoint( &editor->getDocument(), lineNumber ) ) + editor->invalidateDraw(); + return true; +} + +bool DebuggerPlugin::breakpointSetEnabled( const std::string& doc, Uint32 lineNumber, + bool enabled ) { Lock l( mBreakpointsMutex ); - auto& breakpoints = mBreakpoints[doc->getFilePath()]; + auto& breakpoints = mBreakpoints[doc]; auto breakpointIt = breakpoints.find( SourceBreakpointStateful( lineNumber ) ); if ( breakpointIt != breakpoints.end() ) { - breakpoints.erase( breakpointIt ); - } else { - breakpoints.insert( SourceBreakpointStateful( lineNumber ) ); + breakpointIt->enabled = enabled; + mBreakpointsModel->enable( doc, lineNumber, breakpointIt->enabled ); + getUISceneNode()->invalidateDraw(); + return true; } - mThreadPool->run( [this, doc] { sendFileBreakpoints( doc->getFilePath() ); } ); + return false; +} - if ( getStatusDebuggerController()->getWidget() == nullptr ) { - getStatusDebuggerController()->show(); - getStatusDebuggerController()->hide(); - } - - getStatusDebuggerController()->getUIBreakpoints()->setModel( - std::make_shared( mBreakpoints ) ); - - return true; +bool DebuggerPlugin::breakpointToggleEnabled( const std::string& doc, Uint32 lineNumber ) { + Lock l( mBreakpointsMutex ); + auto& breakpoints = mBreakpoints[doc]; + auto breakpointIt = breakpoints.find( SourceBreakpointStateful( lineNumber ) ); + if ( breakpointIt != breakpoints.end() ) + return breakpointSetEnabled( doc, lineNumber, !breakpointIt->enabled ); + return false; } bool DebuggerPlugin::breakpointToggleEnabled( TextDocument* doc, Uint32 lineNumber ) { @@ -680,20 +689,7 @@ bool DebuggerPlugin::breakpointToggleEnabled( TextDocument* doc, Uint32 lineNumb 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; + return breakpointToggleEnabled( doc->getFilePath(), lineNumber ); } bool DebuggerPlugin::onMouseDown( UICodeEditor* editor, const Vector2i& position, diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp index 29bcc63ab..afae3d02b 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp @@ -3,6 +3,7 @@ #include "../plugin.hpp" #include "../pluginmanager.hpp" #include "debuggerclientlistener.hpp" +#include "models/breakpointsmodel.hpp" using namespace EE::UI::Models; using namespace EE::UI; @@ -68,6 +69,7 @@ class DebuggerPlugin : public PluginBase { UIPushButton* mRunButton{ nullptr }; UnorderedMap> mBreakpoints; UnorderedSet mPendingBreakpoints; + std::shared_ptr mBreakpointsModel; Mutex mBreakpointsMutex; DebuggerPlugin( PluginManager* pluginManager, bool sync ); @@ -115,6 +117,12 @@ class DebuggerPlugin : public PluginBase { bool breakpointToggleEnabled( TextDocument* doc, Uint32 lineNumber ); + bool setBreakpoint( const std::string& doc, Uint32 lineNumber ); + + bool breakpointToggleEnabled( const std::string& doc, Uint32 lineNumber ); + + bool breakpointSetEnabled( const std::string& doc, Uint32 lineNumber, bool enabled ); + bool onMouseDown( UICodeEditor*, const Vector2i&, const Uint32& flags ) override; bool isSupportedByAnyDebugger( const std::string& language ); diff --git a/src/tools/ecode/plugins/debugger/models/breakpointsmodel.cpp b/src/tools/ecode/plugins/debugger/models/breakpointsmodel.cpp new file mode 100644 index 000000000..00c170798 --- /dev/null +++ b/src/tools/ecode/plugins/debugger/models/breakpointsmodel.cpp @@ -0,0 +1,109 @@ +#include "breakpointsmodel.hpp" +#include + +namespace ecode { + +BreakpointsModel::BreakpointsModel( + const UnorderedMap>& breakpoints, + UISceneNode* sceneNode ) : + mSceneNode( sceneNode ) { + for ( const auto& bpf : breakpoints ) + for ( const auto& bp : bpf.second ) + mBreakpoints.emplace_back( bpf.first, bp ); +} + +size_t BreakpointsModel::rowCount( const ModelIndex& ) const { + Lock l( mResourceLock ); + return mBreakpoints.size(); +} + +size_t BreakpointsModel::columnCount( const ModelIndex& ) const { + return Columns::Count; +} + +std::string BreakpointsModel::columnName( const size_t& index ) const { + Lock l( mResourceLock ); + switch ( index ) { + case Columns::Enabled: + return mSceneNode->i18n( "enabled", "Enabled" ); + case Columns::SourcePath: + return mSceneNode->i18n( "source_path", "Source Path" ); + case Columns::Line: + return mSceneNode->i18n( "line", "Line" ); + case Columns::Remove: + return mSceneNode->i18n( "remove", "Remove" ); + } + return ""; +} + +Variant BreakpointsModel::data( const ModelIndex& modelIndex, ModelRole role ) const { + Lock l( mResourceLock ); + + switch ( role ) { + case ModelRole::Display: { + switch ( modelIndex.column() ) { + case Columns::Enabled: + return Variant( "" ); + case Columns::SourcePath: + return Variant( mBreakpoints[modelIndex.row()].first.c_str() ); + case Columns::Line: + return Variant( + String::toString( mBreakpoints[modelIndex.row()].second.line ) ); + } + break; + } + case ModelRole::Data: { + switch ( modelIndex.column() ) { + case Columns::Enabled: + return Variant( mBreakpoints[modelIndex.row()].second.enabled ); + case Columns::SourcePath: + return Variant( mBreakpoints[modelIndex.row()].first.c_str() ); + case Columns::Line: + return Variant( mBreakpoints[modelIndex.row()].second.line ); + } + break; + } + case ModelRole::Icon: { + if ( modelIndex.column() == Columns::Remove ) { + static UIIcon* eraseIcon = mSceneNode->findIcon( "chrome-close" ); + return Variant( eraseIcon ); + } + break; + } + default: + break; + } + + return {}; +} + +void BreakpointsModel::insert( const std::string& filePath, + const SourceBreakpointStateful& breakpoint ) { + Lock l( mResourceLock ); + mBreakpoints.emplace_back( filePath, breakpoint ); + invalidate( Model::UpdateFlag::DontInvalidateIndexes ); +} + +void BreakpointsModel::erase( const std::string& filePath, + const SourceBreakpointStateful& breakpoint ) { + Lock l( mResourceLock ); + auto found = std::find( mBreakpoints.begin(), mBreakpoints.end(), + std::make_pair( filePath, breakpoint ) ); + if ( found != mBreakpoints.end() ) { + mBreakpoints.erase( found ); + invalidate( Model::UpdateFlag::InvalidateAllIndexes ); + } +} + +void BreakpointsModel::enable( const std::string& filePath, + const SourceBreakpointStateful& breakpoint, bool enable ) { + Lock l( mResourceLock ); + auto found = std::find( mBreakpoints.begin(), mBreakpoints.end(), + std::make_pair( filePath, breakpoint ) ); + if ( found != mBreakpoints.end() ) { + found->second.enabled = enable; + invalidate( Model::UpdateFlag::DontInvalidateIndexes ); + } +} + +} // namespace ecode diff --git a/src/tools/ecode/plugins/debugger/models/breakpointsmodel.hpp b/src/tools/ecode/plugins/debugger/models/breakpointsmodel.hpp new file mode 100644 index 000000000..1cf3653a8 --- /dev/null +++ b/src/tools/ecode/plugins/debugger/models/breakpointsmodel.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "../dap/protocol.hpp" +#include +#include + +using namespace EE; +using namespace EE::UI; +using namespace EE::UI::Models; +using namespace ecode::dap; + +namespace EE::UI { +class UISceneNode; +} + +namespace ecode { + +class BreakpointsModel : public Model { + public: + enum Columns { Enabled, SourcePath, Line, Remove, Count }; + + BreakpointsModel( + const UnorderedMap>& breakpoints, + UISceneNode* sceneNode ); + + virtual size_t rowCount( const ModelIndex& ) const; + + virtual size_t columnCount( const ModelIndex& ) const; + + virtual std::string columnName( const size_t& index ) const; + + virtual Variant data( const ModelIndex& modelIndex, ModelRole role ) const; + + void insert( const std::string& filePath, const SourceBreakpointStateful& breakpoint ); + + void erase( const std::string& filePath, const SourceBreakpointStateful& breakpoint ); + + void enable( const std::string& filePath, const SourceBreakpointStateful& breakpoint, + bool enable ); + + protected: + std::vector> mBreakpoints; + UISceneNode* mSceneNode{ nullptr }; +}; + +} // namespace ecode diff --git a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp index a99f71c22..2d9987ed3 100644 --- a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp +++ b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp @@ -1,8 +1,67 @@ #include "statusdebuggercontroller.hpp" #include "../plugincontextprovider.hpp" +#include "eepp/ui/uiwidgetcreator.hpp" +#include namespace ecode { +std::function +UIBreakpointsTableView::getCheckBoxFn( const ModelIndex& index, const BreakpointsModel* model ) { + return [index, model, this]( UIPushButton* ) -> UITextView* { + UICheckBox* chk = UICheckBox::New(); + bool enabled = + model->data( model->index( index.row(), BreakpointsModel::Enabled ), ModelRole::Data ) + .asBool(); + chk->setChecked( enabled ); + chk->setCheckMode( UICheckBox::Button ); + chk->on( Event::OnValueChange, [this, index, model, chk]( const Event* ) { + bool checked = chk->isChecked(); + if ( !onBreakpointEnabledChange ) + return; + + std::string filePath( + model + ->data( model->index( index.row(), BreakpointsModel::SourcePath ), + ModelRole::Data ) + .asCStr() ); + int line( + model->data( model->index( index.row(), BreakpointsModel::Line ), ModelRole::Data ) + .asInt() ); + onBreakpointEnabledChange( filePath, line, checked ); + } ); + return chk; + }; +} + +UIWidget* UIBreakpointsTableView::createCell( UIWidget* rowWidget, const ModelIndex& index ) { + if ( index.column() == BreakpointsModel::Enabled ) { + UITableCell* widget = UITableCell::NewWithOpt( + mTag + "::cell", getCheckBoxFn( index, (const BreakpointsModel*)getModel() ) ); + widget->getTextBox()->setEnabled( true ); + widget->setDontAutoHideEmptyTextBox( true ); + 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 ) { + if ( onBreakpointRemove ) { + std::string filePath( + model + ->data( model->index( index.row(), BreakpointsModel::SourcePath ), + ModelRole::Data ) + .asCStr() ); + int line( model + ->data( model->index( index.row(), BreakpointsModel::Line ), + ModelRole::Data ) + .asInt() ); + onBreakpointRemove( filePath, line ); + } + } ); + return cell; + } + return UITableView::createCell( rowWidget, index ); +} + StatusDebuggerController::StatusDebuggerController( UISplitter* mainSplitter, UISceneNode* uiSceneNode, PluginContextProvider* pluginContext ) : @@ -26,7 +85,7 @@ UITableView* StatusDebuggerController::getUIStack() { return mUIStack; } -UITableView* StatusDebuggerController::getUIBreakpoints() { +UIBreakpointsTableView* StatusDebuggerController::getUIBreakpoints() { return mUIBreakpoints; } @@ -75,7 +134,7 @@ void StatusDebuggerController::createContainer() { - + @@ -93,6 +152,8 @@ void StatusDebuggerController::createContainer() { )xml"; + UIWidgetCreator::registerWidget( "BreakpointsTableView", UIBreakpointsTableView::New ); + if ( mMainSplitter->getLastWidget() != nullptr ) { mMainSplitter->getLastWidget()->setVisible( false ); mMainSplitter->getLastWidget()->setParent( mUISceneNode ); @@ -138,11 +199,14 @@ void StatusDebuggerController::createContainer() { mUIThreads->setAutoExpandOnSingleColumn( true ); mUIStack->setAutoColumnsWidth( true ); + mUIStack->setFitAllColumnsToWidget( true ); mUIStack->setMainColumn( 1 ); mUIVariables->setAutoColumnsWidth( true ); + mUIVariables->setFitAllColumnsToWidget( true ); mUIBreakpoints->setAutoColumnsWidth( true ); + mUIBreakpoints->setFitAllColumnsToWidget( true ); mUIBreakpoints->setMainColumn( 1 ); } diff --git a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp index 29352409d..2763f416b 100644 --- a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp +++ b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp @@ -2,6 +2,7 @@ #define ECODE_STATUSDEBUGGERCONTROLLER_HPP #include "../../uistatusbar.hpp" +#include "models/breakpointsmodel.hpp" #include #include #include @@ -17,6 +18,23 @@ using namespace EE::UI::Tools; namespace ecode { +class UIBreakpointsTableView : public UITableView { + public: + static UIWidget* New() { return eeNew( UIBreakpointsTableView, () ); } + + UIBreakpointsTableView() : UITableView() {} + + std::function + onBreakpointEnabledChange; + + std::function onBreakpointRemove; + + std::function getCheckBoxFn( const ModelIndex& index, + const BreakpointsModel* model ); + + UIWidget* createCell( UIWidget* rowWidget, const ModelIndex& index ); +}; + class StatusDebuggerController : public StatusBarElement { public: enum class State { NotStarted, Running, Paused }; @@ -34,7 +52,7 @@ class StatusDebuggerController : public StatusBarElement { UITableView* getUIStack(); - UITableView* getUIBreakpoints(); + UIBreakpointsTableView* getUIBreakpoints(); UITreeView* getUIVariables() const; @@ -46,7 +64,7 @@ class StatusDebuggerController : public StatusBarElement { UILinearLayout* mContainer{ nullptr }; UITableView* mUIThreads{ nullptr }; UITableView* mUIStack{ nullptr }; - UITableView* mUIBreakpoints{ nullptr }; + UIBreakpointsTableView* mUIBreakpoints{ nullptr }; UITreeView* mUIVariables{ nullptr }; UISplitter* mUIThreadsSplitter{ nullptr }; UIPushButton* mUIButStart{ nullptr }; diff --git a/src/tools/ecode/statusappoutputcontroller.cpp b/src/tools/ecode/statusappoutputcontroller.cpp index fe6b43976..d0a506477 100644 --- a/src/tools/ecode/statusappoutputcontroller.cpp +++ b/src/tools/ecode/statusappoutputcontroller.cpp @@ -1,6 +1,7 @@ #include "statusappoutputcontroller.hpp" #include "notificationcenter.hpp" #include "plugins/plugincontextprovider.hpp" +#include #include namespace ecode { diff --git a/src/tools/ecode/universallocator.cpp b/src/tools/ecode/universallocator.cpp index 65ee16fc6..5697d28cc 100644 --- a/src/tools/ecode/universallocator.cpp +++ b/src/tools/ecode/universallocator.cpp @@ -132,7 +132,8 @@ UniversalLocator::UniversalLocator( UICodeEditorSplitter* editorSplitter, UIScen mApp->runCommand( cmd ); if ( mSplitter->getCurWidget()->isType( UI_TYPE_CODEEDITOR ) && mSplitter->curEditorIsNotNull() && - mSplitter->getCurEditor()->getDocument().hasCommand( cmd ) ) + mSplitter->getCurEditor()->getDocument().hasCommand( cmd ) && + !mUISceneNode->getRoot()->getLastChild()->isType( UI_TYPE_WINDOW ) ) mSplitter->getCurEditor()->setFocus(); if ( cmd != "open-locatebar" && cmd != "open-workspace-symbol-search" && cmd != "open-document-symbol-search" && cmd != "go-to-line" && @@ -761,7 +762,6 @@ std::shared_ptr UniversalLocator::openDocumentsModel( const std:: return std::make_shared( std::move( ffiles ), std::move( fnames ) ); } - void UniversalLocator::updateOpenDocumentsTable() { mLocateTable->setModel( openDocumentsModel( mLocateInput->getText().substr( 2 ).trim().toUtf8() ) );