diff --git a/include/eepp/ui/models/variant.hpp b/include/eepp/ui/models/variant.hpp index 3ba35ef29..e686b4a99 100644 --- a/include/eepp/ui/models/variant.hpp +++ b/include/eepp/ui/models/variant.hpp @@ -72,6 +72,9 @@ class EE_API Variant { UIIcon* asIcon() const { return mValue.asIcon; } void* asDataPtr() const { return mValue.asDataPtr; } bool is( const Type& type ) const { return type == mType; } + bool isString() const { + return mType == Type::StdString || mType == Type::cstr || mType == Type::String; + } void reset() { switch ( mType ) { case Type::StdString: @@ -212,6 +215,42 @@ class EE_API Variant { return false; } + size_t size() const { + switch ( mType ) { + case Type::Bool: + return 1; + case Type::Int: + return sizeof( int ); + case Type::Uint: + return sizeof( unsigned int ); + case Type::Int64: + return sizeof( Int64 ); + case Type::Uint64: + return sizeof( Uint64 ); + case Type::Float: + return sizeof( Float ); + case Type::StdString: + return asStdString().size(); + case Type::String: + return asString().size(); + case Type::Drawable: + return sizeof( mValue.asDrawable ); + case Type::Icon: + return asIcon()->getName().size(); + case Type::DataPtr: + return sizeof( mValue.asDataPtr ); + case Type::Vector2f: + return sizeof( mValue.asVector2f ); + case Type::Rectf: + return sizeof( mValue.asRectf ); + case Type::cstr: + return strlen( asCStr() ); + case Type::Invalid: + break; + } + return 0; + } + private: union { void* asDataPtr; diff --git a/include/eepp/ui/tools/uicodeeditorsplitter.hpp b/include/eepp/ui/tools/uicodeeditorsplitter.hpp index c6bf14dfc..f45d04fc5 100644 --- a/include/eepp/ui/tools/uicodeeditorsplitter.hpp +++ b/include/eepp/ui/tools/uicodeeditorsplitter.hpp @@ -23,6 +23,8 @@ class EE_API UICodeEditorSplitter { public: virtual ~Client(){}; + virtual void onTabCreated( UITab* tab, UIWidget* widget ) = 0; + virtual void onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) = 0; virtual void onCodeEditorFocusChange( UICodeEditor* editor ) = 0; diff --git a/include/eepp/ui/uitab.hpp b/include/eepp/ui/uitab.hpp index 9c9083a62..d2170ea1d 100644 --- a/include/eepp/ui/uitab.hpp +++ b/include/eepp/ui/uitab.hpp @@ -64,6 +64,8 @@ class EE_API UITab : public UISelectButton { Uint32 onDragStop( const Vector2i& position, const Uint32& flags ); + virtual Uint32 onMouseUp( const Vector2i& position, const Uint32& flags ); + virtual Uint32 onMessage( const NodeMessage* message ); virtual void onStateChange(); @@ -74,6 +76,8 @@ class EE_API UITab : public UISelectButton { virtual void onSizeChange(); + virtual bool onCreateContextMenu( const Vector2i& position, const Uint32& flags ); + void setOwnedNode(); void updateTab(); diff --git a/include/eepp/ui/uitabwidget.hpp b/include/eepp/ui/uitabwidget.hpp index e6acc5113..0e0907052 100644 --- a/include/eepp/ui/uitabwidget.hpp +++ b/include/eepp/ui/uitabwidget.hpp @@ -7,6 +7,7 @@ namespace EE { namespace UI { +class UIPopUpMenu; class UIScrollBar; class EE_API TabEvent : public Event { @@ -111,7 +112,7 @@ class EE_API UITabWidget : public UIWidget { bool getTabCloseButtonVisible() const; - void setTabCloseButtonVisible( bool visible); + void setTabCloseButtonVisible( bool visible ); bool getSpecialBorderTabs() const; @@ -178,6 +179,10 @@ class EE_API UITabWidget : public UIWidget { void setFocusTabBehavior( FocusTabBehavior focusTabBehavior ); + bool getEnabledCreateContextMenu() const; + + void setEnabledCreateContextMenu( bool enabledCreateContextMenu ); + protected: friend class UITab; @@ -194,10 +199,12 @@ class EE_API UITabWidget : public UIWidget { bool mAllowDragAndDropTabs{ false }; bool mAllowSwitchTabsInEmptySpaces{ false }; bool mDroppableHoveringColorWasSet{ false }; + bool mEnabledCreateContextMenu{ false }; Float mTabVerticalDragResistance; Color mDroppableHoveringColor{ Color::Transparent }; FocusTabBehavior mFocusTabBehavior{ FocusTabBehavior::Closest }; std::deque mFocusHistory; + UIPopUpMenu* mCurrentMenu{ nullptr }; void onThemeLoaded(); diff --git a/src/eepp/ui/abstract/uiabstracttableview.cpp b/src/eepp/ui/abstract/uiabstracttableview.cpp index 5e4933ca4..efecdaa6d 100644 --- a/src/eepp/ui/abstract/uiabstracttableview.cpp +++ b/src/eepp/ui/abstract/uiabstracttableview.cpp @@ -586,23 +586,10 @@ UIWidget* UIAbstractTableView::updateCell( const int& rowIndex, const ModelIndex Variant cls( getModel()->data( index, ModelRole::Class ) ); cell->setLoadingState( true ); if ( cls.isValid() ) { - // We analize each case to avoid unnecessary allocations - if ( cls.is( Variant::Type::StdString ) ) { - needsReloadStyle = cell->getClasses().empty() || - cell->getClasses().size() != 1 || - cls.asStdString() != cell->getClasses()[0]; - cell->setClass( cls.asStdString() ); - } else if ( cls.is( Variant::Type::String ) ) { - needsReloadStyle = cell->getClasses().empty() || - cell->getClasses().size() != 1 || - cls.asString().toUtf8() != cell->getClasses()[0]; - cell->setClass( cls.asString() ); - } else if ( cls.is( Variant::Type::cstr ) ) { - needsReloadStyle = cell->getClasses().empty() || - cell->getClasses().size() != 1 || - cls.asCStr() != cell->getClasses()[0]; - cell->setClass( cls.asCStr() ); - } + std::string clsStr( cls.toString() ); + needsReloadStyle = cell->getClasses().empty() || cell->getClasses().size() != 1 || + clsStr != cell->getClasses()[0]; + cell->setClass( clsStr ); } else { needsReloadStyle = !cell->getClasses().empty(); cell->resetClass(); @@ -613,18 +600,8 @@ UIWidget* UIAbstractTableView::updateCell( const int& rowIndex, const ModelIndex } Variant txt( getModel()->data( index, ModelRole::Display ) ); - if ( txt.isValid() ) { - if ( txt.is( Variant::Type::StdString ) ) - cell->setText( txt.asStdString() ); - else if ( txt.is( Variant::Type::String ) ) - cell->setText( txt.asString() ); - else if ( txt.is( Variant::Type::cstr ) ) - cell->setText( txt.asCStr() ); - else if ( txt.is( Variant::Type::Bool ) || txt.is( Variant::Type::Float ) || - txt.is( Variant::Type::Int ) || txt.is( Variant::Type::Uint ) || - txt.is( Variant::Type::Int64 ) || txt.is( Variant::Type::Uint64 ) ) - cell->setText( txt.toString() ); - } + if ( txt.isValid() ) + cell->setText( txt.toString() ); bool isVisible = false; Variant icon( getModel()->data( index, ModelRole::Icon ) ); diff --git a/src/eepp/ui/models/sortingproxymodel.cpp b/src/eepp/ui/models/sortingproxymodel.cpp index e3414eccf..371a8963b 100644 --- a/src/eepp/ui/models/sortingproxymodel.cpp +++ b/src/eepp/ui/models/sortingproxymodel.cpp @@ -217,11 +217,15 @@ bool SortingProxyModel::isColumnSortable( const size_t& columnIndex ) const { bool SortingProxyModel::lessThan( const ModelIndex& index1, const ModelIndex& index2 ) const { auto data1 = mSource->data( index1, mSortRole ); auto data2 = mSource->data( index2, mSortRole ); - if ( data1.is( Variant::Type::StdString ) && data2.is( Variant::Type::StdString ) ) - return String::toLower( data1.asString() ) < String::toLower( data2.asString() ); - if ( data1.is( Variant::Type::cstr ) && data2.is( Variant::Type::cstr ) ) - return String::toLower( std::string( data1.asCStr() ) ) < - String::toLower( std::string( data2.asCStr() ) ); + if ( data1.isString() && data2.isString() ) { + if ( data1.is( Variant::Type::StdString ) && data2.is( Variant::Type::StdString ) ) + return String::toLower( data1.asStdString() ) < String::toLower( data2.asStdString() ); + if ( data1.is( Variant::Type::String ) && data2.is( Variant::Type::String ) ) + return String::toLower( data1.asString() ) < String::toLower( data2.asString() ); + if ( data1.is( Variant::Type::cstr ) && data2.is( Variant::Type::cstr ) ) + return String::toLower( std::string( data1.asCStr() ) ) < + String::toLower( std::string( data2.asCStr() ) ); + } return data1 < data2; } diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp index 516a9ed77..8098a6ef4 100644 --- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -512,6 +512,7 @@ UICodeEditorSplitter::createCodeEditorInTabWidget( UITabWidget* tabWidget ) { editor->sendEvent( static_cast( &docEvent ) ); mAboutToAddEditor = nullptr; mFirstCodeEditor = false; + mClient->onTabCreated( tab, editor ); return std::make_pair( tab, editor ); } @@ -557,6 +558,7 @@ UICodeEditorSplitter::createWidgetInTabWidget( UITabWidget* tabWidget, UIWidget* } ); if ( focus ) tabWidget->setTabSelected( tab ); + mClient->onTabCreated( tab, widget ); return std::make_pair( tab, widget ); } @@ -613,6 +615,7 @@ UITabWidget* UICodeEditorSplitter::createEditorWithTabWidget( Node* parent, bool tabWidget->setAllowRearrangeTabs( true ); tabWidget->setAllowDragAndDropTabs( true ); tabWidget->setAllowSwitchTabsInEmptySpaces( true ); + tabWidget->setEnabledCreateContextMenu( true ); tabWidget->setFocusTabBehavior( UITabWidget::FocusTabBehavior::FocusOrder ); tabWidget->addEventListener( Event::OnTabSelected, [this]( const Event* event ) { UITabWidget* tabWidget = event->getNode()->asType(); @@ -974,7 +977,8 @@ void UICodeEditorSplitter::closeTab( UIWidget* widget, } } else { UITabWidget* tabWidget = tabWidgetFromWidget( widget ); - tabWidget->removeTab( (UITab*)widget->getData(), true, false, focusTabBehavior ); + if ( tabWidget ) + tabWidget->removeTab( (UITab*)widget->getData(), true, false, focusTabBehavior ); } if ( mCurEditor == widget ) mCurEditor = nullptr; diff --git a/src/eepp/ui/uifiledialog.cpp b/src/eepp/ui/uifiledialog.cpp index 64fc3d3bb..f5b560fce 100644 --- a/src/eepp/ui/uifiledialog.cpp +++ b/src/eepp/ui/uifiledialog.cpp @@ -151,7 +151,7 @@ UIFileDialog::UIFileDialog( Uint32 dialogFlags, const std::string& defaultFilePa if ( modelEvent->getModelEventType() == ModelEventType::Open ) { Variant vPath( modelEvent->getModel()->data( modelEvent->getModelIndex(), ModelRole::Custom ) ); - if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { + if ( vPath.isValid() && vPath.isString() ) { bool shouldOpenFolder = false; if ( allowFolderSelect() && modelEvent->getTriggerEvent() && modelEvent->getTriggerEvent()->getType() == Event::EventType::KeyDown ) { diff --git a/src/eepp/ui/uitab.cpp b/src/eepp/ui/uitab.cpp index d1e0df9d1..cf2838d0b 100644 --- a/src/eepp/ui/uitab.cpp +++ b/src/eepp/ui/uitab.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -314,6 +315,43 @@ bool UITab::applyProperty( const StyleSheetProperty& attribute ) { return true; } +Uint32 UITab::onMouseUp( const Vector2i& position, const Uint32& flags ) { + if ( ( flags & EE_BUTTON_RMASK ) && getTabWidget() && + getTabWidget()->getEnabledCreateContextMenu() ) { + onCreateContextMenu( position, flags ); + } + return UISelectButton::onMouseUp( position, flags ); +} + +bool UITab::onCreateContextMenu( const Vector2i& position, const Uint32& flags ) { + UITabWidget* tTabW = getTabWidget(); + if ( !tTabW ) + return false; + + if ( tTabW->mCurrentMenu ) + return false; + + UIPopUpMenu* menu = UIPopUpMenu::New(); + ContextMenuEvent event( this, menu, Event::OnCreateContextMenu, position, flags ); + sendEvent( &event ); + + if ( menu->getCount() == 0 ) { + menu->close(); + return false; + } + + menu->setCloseOnHide( true ); + Vector2f pos( position.asFloat() ); + menu->nodeToWorldTranslation( pos ); + UIMenu::findBestMenuPos( pos, menu ); + menu->setPixelsPosition( pos ); + menu->show(); + menu->addEventListener( Event::OnClose, + [tTabW]( const Event* ) { tTabW->mCurrentMenu = nullptr; } ); + tTabW->mCurrentMenu = menu; + return true; +} + Uint32 UITab::onMessage( const NodeMessage* message ) { UITabWidget* tTabW = getTabWidget(); if ( !tTabW || !mEnabled || !mVisible ) diff --git a/src/eepp/ui/uitableview.cpp b/src/eepp/ui/uitableview.cpp index 663839fae..a6093bd45 100644 --- a/src/eepp/ui/uitableview.cpp +++ b/src/eepp/ui/uitableview.cpp @@ -125,18 +125,14 @@ Float UITableView::getMaxColumnContentWidth( const size_t& colIndex, bool bestGu // TODO: Improve best guess if ( bestGuess && getItemCount() > 10 ) { Variant dataTest( getModel()->data( getModel()->index( 0, colIndex ) ) ); - bool isStdString = dataTest.is( Variant::Type::StdString ); - bool isString = dataTest.is( Variant::Type::String ); - if ( isStdString || isString || dataTest.is( Variant::Type::cstr ) ) { + if ( dataTest.isString() ) { std::map lengths; for ( size_t i = 0; i < getItemCount(); i++ ) { ModelIndex index( getModel()->index( i, colIndex ) ); Variant data( getModel()->data( index ) ); if ( !data.isValid() ) continue; - size_t length = - isStdString ? data.asStdString().length() - : ( isString ? data.asString().length() : strlen( data.asCStr() ) ); + size_t length = data.size(); if ( lengths.empty() || length > lengths.rbegin()->first ) lengths[length] = index; } diff --git a/src/eepp/ui/uitabwidget.cpp b/src/eepp/ui/uitabwidget.cpp index 2c2e17b68..eae4e59fd 100644 --- a/src/eepp/ui/uitabwidget.cpp +++ b/src/eepp/ui/uitabwidget.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,7 @@ UITabWidget::UITabWidget() : ->setPosition( 0, mStyleConfig.TabHeight ); mTabScroll = UIScrollBar::NewHorizontalWithTag( "scrollbarmini" ); - mTabScroll->setParent( this ); + mTabScroll->setParent( mTabBar ); mTabScroll->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent ); mTabScroll->on( Event::OnSizeChange, [this]( const Event* ) { updateScrollBar(); } ); mTabScroll->on( Event::OnValueChange, [this]( const Event* ) { updateScroll(); } ); @@ -53,7 +54,12 @@ UITabWidget::UITabWidget() : applyDefaultTheme(); } -UITabWidget::~UITabWidget() {} +UITabWidget::~UITabWidget() { + if ( mCurrentMenu ) { + mCurrentMenu->clearEventListener(); + mCurrentMenu = nullptr; + } +} Uint32 UITabWidget::getType() const { return UI_TYPE_TABWIDGET; @@ -453,6 +459,8 @@ void UITabWidget::zorderTabs() { if ( mTabSelectedIndex + 1 < mTabs.size() ) mTabs[mTabSelectedIndex + 1]->addClass( "next" ); } + + mTabScroll->toFront(); } void UITabWidget::orderTabs() { @@ -871,6 +879,14 @@ void UITabWidget::setFocusTabBehavior( UITabWidget::FocusTabBehavior focusTabBeh mFocusTabBehavior = focusTabBehavior; } +bool UITabWidget::getEnabledCreateContextMenu() const { + return mEnabledCreateContextMenu; +} + +void UITabWidget::setEnabledCreateContextMenu( bool enabledCreateContextMenu ) { + mEnabledCreateContextMenu = enabledCreateContextMenu; +} + void UITabWidget::refreshOwnedWidget( UITab* tab ) { if ( NULL != tab && NULL != tab->getOwnedWidget() ) { tab->getOwnedWidget()->setParent( mNodeContainer ); @@ -1042,9 +1058,12 @@ void UITabWidget::updateScrollBar() { } void UITabWidget::updateScroll() { - if ( mTabScroll->isVisible() ) - mTabBar->setPixelsPosition( - { mPaddingPx.Left + -mTabScroll->getValue(), mTabBar->getPixelsPosition().y } ); + if ( mTabScroll->isVisible() ) { + Vector2f newPos{ mPaddingPx.Left + -mTabScroll->getValue(), + mTabBar->getPixelsPosition().y }; + mTabBar->setPixelsPosition( newPos ); + mTabScroll->setPixelsPosition( { -newPos.x, mTabScroll->getPixelsPosition().y } ); + } } }} // namespace EE::UI diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index 6b711719b..84c617126 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -239,23 +239,10 @@ UIWidget* UITreeView::updateCell( const int& rowIndex, const ModelIndex& index, Variant cls( getModel()->data( index, ModelRole::Class ) ); cell->setLoadingState( true ); if ( cls.isValid() ) { - // We analize each case to avoid unnecessary allocations - if ( cls.is( Variant::Type::StdString ) ) { - needsReloadStyle = cell->getClasses().empty() || - cell->getClasses().size() != 1 || - cls.asStdString() != cell->getClasses()[0]; - cell->setClass( cls.asStdString() ); - } else if ( cls.is( Variant::Type::String ) ) { - needsReloadStyle = cell->getClasses().empty() || - cell->getClasses().size() != 1 || - cls.asString().toUtf8() != cell->getClasses()[0]; - cell->setClass( cls.asString() ); - } else if ( cls.is( Variant::Type::cstr ) ) { - needsReloadStyle = cell->getClasses().empty() || - cell->getClasses().size() != 1 || - cls.asCStr() != cell->getClasses()[0]; - cell->setClass( cls.asCStr() ); - } + std::string clsStr( cls.toString() ); + needsReloadStyle = cell->getClasses().empty() || cell->getClasses().size() != 1 || + clsStr != cell->getClasses()[0]; + cell->setClass( clsStr ); } else { needsReloadStyle = !cell->getClasses().empty(); cell->resetClass(); @@ -266,18 +253,8 @@ UIWidget* UITreeView::updateCell( const int& rowIndex, const ModelIndex& index, } Variant txt( getModel()->data( index, ModelRole::Display ) ); - if ( txt.isValid() ) { - if ( txt.is( Variant::Type::StdString ) ) - cell->setText( txt.asStdString() ); - else if ( txt.is( Variant::Type::String ) ) - cell->setText( txt.asString() ); - else if ( txt.is( Variant::Type::cstr ) ) - cell->setText( txt.asCStr() ); - else if ( txt.is( Variant::Type::Bool ) || txt.is( Variant::Type::Float ) || - txt.is( Variant::Type::Int ) || txt.is( Variant::Type::Uint ) || - txt.is( Variant::Type::Int64 ) || txt.is( Variant::Type::Uint64 ) ) - cell->setText( txt.toString() ); - } + if ( txt.isValid() ) + cell->setText( txt.toString() ); bool hasChilds = false; diff --git a/src/tools/ecode/applayout.xml.hpp b/src/tools/ecode/applayout.xml.hpp index 51e5ad63b..8411c3280 100644 --- a/src/tools/ecode/applayout.xml.hpp +++ b/src/tools/ecode/applayout.xml.hpp @@ -411,6 +411,15 @@ Anchor.error:hover { #project_view ScrollBar:focus-within { opacity: 1; } +#code_container TabWidget::TabBar ScrollBarMini { + opacity: 0; + transition: opacity 0.15; +} +#code_container TabWidget::TabBar:hover ScrollBarMini, +#code_container TabWidget::TabBar ScrollBarMini.dragging, +#code_container TabWidget::TabBar ScrollBarMini:focus-within { + opacity: 1; +} )html" diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index a88785399..28bbd2bb3 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -1648,6 +1648,55 @@ void App::onCodeEditorFocusChange( UICodeEditor* editor ) { syncProjectTreeWithEditor( editor ); } +void App::onTabCreated( UITab* tab, UIWidget* ) { + tab->on( Event::OnCreateContextMenu, [this]( const Event* event ) { + if ( !event->getNode()->isType( UI_TYPE_TAB ) ) + return; + const ContextMenuEvent* menuEvent = static_cast( event ); + UIPopUpMenu* menu = menuEvent->getMenu(); + if ( nullptr == menu ) + return; + UITab* tab = event->getNode()->asType(); + if ( !tab->getTabWidget() ) + return; + menu->add( i18n( "editor_tab_menu_close_tab", "Close Tab" ) )->setId( "close-tab" ); + menu->add( i18n( "editor_tab_menu_close_other_tabs", "Close Other Tabs" ) ) + ->setId( "close-other-tabs" ); + menu->add( i18n( "editor_tab_menu_close_all_tabs", "Close All Tabs" ) ) + ->setId( "close-all-tabs" ); + menu->addEventListener( Event::OnItemClicked, [tab, this]( const Event* event ) { + if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) + return; + UIMenuItem* item = event->getNode()->asType(); + UITabWidget* tabW = tab->getTabWidget(); + if ( "close-tab" == item->getId() ) { + mSplitter->closeTab( tab->getOwnedWidget()->asType(), + UITabWidget::FocusTabBehavior::Closest ); + } else if ( "close-other-tabs" == item->getId() ) { + size_t tabCount = tabW->getTabCount(); + std::vector tabs; + for ( size_t i = 0; i < tabCount; i++ ) { + if ( tabW->getTab( i ) != tab ) + tabs.push_back( tabW->getTab( i ) ); + } + for ( auto* tab : tabs ) { + mSplitter->closeTab( tab->getOwnedWidget()->asType(), + UITabWidget::FocusTabBehavior::Closest ); + } + } else if ( "close-all-tabs" == item->getId() ) { + size_t tabCount = tabW->getTabCount(); + std::vector tabs; + for ( size_t i = 0; i < tabCount; i++ ) + tabs.push_back( tabW->getTab( i ) ); + for ( auto* tab : tabs ) { + mSplitter->closeTab( tab->getOwnedWidget()->asType(), + UITabWidget::FocusTabBehavior::Closest ); + } + } + } ); + } ); +} + void App::onColorSchemeChanged( const std::string& ) { mSettings->updateColorSchemeMenu(); mGlobalSearchController->updateColorScheme( mSplitter->getCurrentColorScheme() ); @@ -2942,8 +2991,8 @@ void App::initProjectTreeView( std::string path, bool openClean ) { if ( type == ModelEventType::Open || type == ModelEventType::OpenMenu ) { Variant vPath( modelEvent->getModel()->data( modelEvent->getModelIndex(), ModelRole::Custom ) ); - if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { - std::string path( vPath.asCStr() ); + if ( vPath.isValid() && vPath.isString() ) { + std::string path( vPath.toString() ); if ( type == ModelEventType::Open ) { UITab* tab = mSplitter->isDocumentOpen( path ); if ( !tab ) { @@ -2977,8 +3026,8 @@ void App::initProjectTreeView( std::string path, bool openClean ) { if ( !modelIndex.isValid() ) return; Variant vPath( mProjectTreeView->getModel()->data( modelIndex, ModelRole::Custom ) ); - if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { - FileInfo fileInfo( vPath.asCStr() ); + if ( vPath.isValid() && vPath.isString() ) { + FileInfo fileInfo( vPath.toString() ); if ( keyEvent->getKeyCode() == KEY_F2 ) { renameFile( fileInfo ); } else { diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 6ad19b2a6..739417021 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -570,6 +570,8 @@ class App : public UICodeEditorSplitter::Client { void onCodeEditorFocusChange( UICodeEditor* editor ); + void onTabCreated( UITab* tab, UIWidget* widget ); + bool trySendUnlockedCmd( const KeyEvent& keyEvent ); FontTrueType* loadFont( const std::string& name, std::string fontPath, diff --git a/src/tools/ecode/globalsearchcontroller.cpp b/src/tools/ecode/globalsearchcontroller.cpp index 90f2c7143..2a8dbb9d1 100644 --- a/src/tools/ecode/globalsearchcontroller.cpp +++ b/src/tools/ecode/globalsearchcontroller.cpp @@ -562,8 +562,8 @@ void GlobalSearchController::initGlobalSearchTree( UITreeViewGlobalSearch* searc Variant vPath( model->data( model->index( modelEvent->getModelIndex().internalId(), ProjectSearch::ResultModel::FileOrPosition ), ModelRole::Custom ) ); - if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { - std::string path( vPath.asCStr() ); + if ( vPath.isValid() && vPath.isString() ) { + std::string path( vPath.toString() ); UITab* tab = mSplitter->isDocumentOpen( path ); Variant lineNum( model->data( model->index( modelEvent->getModelIndex().row(), diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp index c4e206f50..86a7a58a2 100644 --- a/src/tools/ecode/plugins/git/git.cpp +++ b/src/tools/ecode/plugins/git/git.cpp @@ -13,6 +13,18 @@ using namespace std::literals; namespace ecode { +static int countLines( const std::string& text ) { + const char* startPtr = text.c_str(); + const char* endPtr = text.c_str() + text.size(); + size_t count = 0; + if ( startPtr != endPtr ) { + count = 1 + *startPtr == '\n' ? 1 : 0; + while ( ++startPtr && startPtr != endPtr ) + count += ( '\n' == *startPtr ) ? 1 : 0; + } + return count; +} + static constexpr auto sNotCommitedYetHash = "0000000000000000000000000000000000000000"; Git::Blame::Blame( const std::string& error ) : error( error ), line( 0 ) {} @@ -35,12 +47,15 @@ Git::Git( const std::string& projectDir, const std::string& gitPath ) : mGitPath setProjectPath( projectDir ); } -void Git::git( const std::string& args, const std::string& projectDir, std::string& buf ) const { +bool Git::git( const std::string& args, const std::string& projectDir, std::string& buf ) const { buf.clear(); Process p; p.create( mGitPath, args, Process::CombinedStdoutStderr | Process::Options::NoWindow, { { "LC_ALL", "en_US.UTF-8" } }, projectDir.empty() ? mProjectPath : projectDir ); p.readAllStdOut( buf ); + int retCode; + p.join( &retCode ); + return EXIT_SUCCESS == retCode; } void Git::gitSubmodules( const std::string& args, const std::string& projectDir, @@ -50,8 +65,9 @@ void Git::gitSubmodules( const std::string& args, const std::string& projectDir, std::string Git::branch( const std::string& projectDir ) { std::string buf; - git( "rev-parse --abbrev-ref HEAD", projectDir, buf ); - return String::rTrim( buf, '\n' ); + if ( git( "rev-parse --abbrev-ref HEAD", projectDir, buf ) ) + return String::rTrim( buf, '\n' ); + return "HEAD"; } bool Git::setProjectPath( const std::string& projectPath ) { @@ -89,6 +105,13 @@ const std::string& Git::getGitFolder() const { return mGitFolder; } +std::string Git::setSafeDirectory( const std::string& projectDir ) const { + std::string dir( projectDir.empty() ? mProjectPath : projectDir ); + std::string buf; + git( String::format( "config --global --add safe.directory %s", dir ), dir, buf ); + return buf; +} + bool Git::hasSubmodules( const std::string& projectDir ) { return ( !projectDir.empty() && FileSystem::fileExists( projectDir + ".gitmodules" ) ) || ( !mProjectPath.empty() && FileSystem::fileExists( mProjectPath + ".gitmodules" ) ); @@ -99,7 +122,8 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) static constexpr auto STATUS_CMD = "-c color.status=never status -b -u -s"; Status s; std::string buf; - git( DIFF_CMD, projectDir, buf ); + if ( !git( DIFF_CMD, projectDir, buf ) ) + return s; auto parseNumStat = [&s, &buf]() { auto lastNL = 0; auto nextNL = buf.find_first_of( '\n' ); @@ -114,7 +138,14 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) int deletes; if ( String::fromString( inserts, inserted ) && String::fromString( deletes, deleted ) ) { - s.modified.push_back( { std::move( file ), inserts, deletes } ); + auto fileIt = s.files.find( file ); + if ( fileIt != s.files.end() ) { + fileIt->second.file = std::move( file ); + fileIt->second.inserts = inserts; + fileIt->second.deletes = deletes; + } else { + s.files.insert( { file, { file, inserts, deletes } } ); + } s.totalInserts += inserts; s.totalDeletions += deletes; } @@ -138,13 +169,13 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) auto lastNL = 0; auto nextNL = buf.find_first_of( '\n' ); while ( nextNL != std::string_view::npos ) { - LuaPattern pattern( "\n([%s?][MARTUD?])%s(.*)" ); + LuaPattern pattern( "\n([%sA?][MARTUD?%s])%s(.*)" ); LuaPattern::Range matches[3]; if ( pattern.matches( buf.c_str(), lastNL, matches, nextNL ) ) { auto status = buf.substr( matches[1].start, matches[1].end - matches[1].start ); String::trimInPlace( status ); auto file = buf.substr( matches[2].start, matches[2].end - matches[2].start ); - FileStatus rstatus = FileStatus::Unidentified; + FileStatus rstatus = FileStatus::Unknown; if ( "??" == status ) rstatus = FileStatus::Untracked; else if ( "M" == status ) @@ -162,11 +193,16 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) else if ( "m" == status ) rstatus = FileStatus::ModifiedSubmodule; - if ( rstatus != FileStatus::Unidentified ) { + if ( rstatus != FileStatus::Unknown ) { if ( rstatus == FileStatus::ModifiedSubmodule ) modifiedSubmodule = true; - else - s.files.insert( { std::move( file ), rstatus } ); + else { + auto fileIt = s.files.find( file ); + if ( fileIt != s.files.end() ) + fileIt->second.status = rstatus; + else + s.files.insert( { file, { file, 0, 0, rstatus } } ); + } } } lastNL = nextNL; @@ -182,6 +218,15 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) parseStatus(); } + for ( auto [_, val] : s.files ) { + if ( val.status == FileStatus::Added && val.inserts == 0 ) { + std::string fileText; + FileSystem::fileGet( ( projectDir.empty() ? mProjectPath : projectDir ) + val.file, + fileText ); + val.inserts = countLines( fileText ); + } + } + return s; } @@ -200,7 +245,9 @@ Git::Blame Git::blame( const std::string& filepath, std::size_t line ) const { }; std::string workingDir( FileSystem::fileRemoveFileName( filepath ) ); - git( String::format( "blame %s -p -L%zu,%zu", filepath.data(), line, line ), workingDir, buf ); + if ( !git( String::format( "blame %s -p -L%zu,%zu", filepath.data(), line, line ), workingDir, + buf ) ) + return { buf }; if ( String::startsWith( buf, "fatal: " ) ) return { buf.substr( 7 ) }; diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp index c8315a7f3..a3cb3edad 100644 --- a/src/tools/ecode/plugins/git/git.hpp +++ b/src/tools/ecode/plugins/git/git.hpp @@ -1,6 +1,5 @@ #include #include -#include namespace ecode { @@ -23,18 +22,8 @@ class Git { std::size_t line{ 0 }; }; - struct DiffFile { - std::string file; - int inserts{ 0 }; - int deletes{ 0 }; - - bool operator==( const DiffFile& other ) const { - return file == other.file && inserts == other.inserts && deletes == other.deletes; - } - }; - - enum FileStatus { - Unidentified, + enum class FileStatus { + Unknown, Modified = 'M', Added = 'A', Renamed = 'R', @@ -44,22 +33,31 @@ class Git { Untracked = '?', ModifiedSubmodule = 'm', }; + struct DiffFile { + std::string file; + int inserts{ 0 }; + int deletes{ 0 }; + FileStatus status{ FileStatus::Unknown }; + + bool operator==( const DiffFile& other ) const { + return file == other.file && inserts == other.inserts && deletes == other.deletes; + } + }; struct Status { - std::vector modified; int totalInserts{ 0 }; int totalDeletions{ 0 }; - std::map files; + std::map files; bool operator==( const Status& other ) const { return totalInserts == other.totalInserts && totalDeletions == other.totalDeletions && - modified == other.modified && files == other.files; + files == other.files; } }; Git( const std::string& projectDir = "", const std::string& gitPath = "" ); - void git( const std::string& args, const std::string& projectDir, std::string& buf ) const; + bool git( const std::string& args, const std::string& projectDir, std::string& buf ) const; void gitSubmodules( const std::string& args, const std::string& projectDir, std::string& buf ); @@ -77,6 +75,8 @@ class Git { const std::string& getGitFolder() const; + std::string setSafeDirectory( const std::string& projectDir ) const; + protected: std::string mGitPath; std::string mProjectPath; diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index b7256fba0..6e89329d7 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -1,5 +1,5 @@ -#include "eepp/ui/uistyle.hpp" #include "gitplugin.hpp" +#include "eepp/ui/uistyle.hpp" #include #include #include @@ -274,7 +274,8 @@ void GitPlugin::onFileSystemEvent( const FileEvent& ev, const FileInfo& file ) { if ( mShuttingDown || isLoading() ) return; - if ( String::startsWith( file.getFilepath(), mGit->getGitFolder() ) ) + if ( String::startsWith( file.getFilepath(), mGit->getGitFolder() ) && + file.getExtension() == "lock" ) return; updateUI(); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 09feca0f5..b77ee7ce2 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -1463,6 +1463,9 @@ void LSPClientPlugin::displayTooltip( UICodeEditor* editor, const LSPHover& resp editor->getColorScheme().getEditorColor( "background"_sst ).toHexString(), true, StyleSheetSelectorRule::SpecificityImportant ) ); + if ( tooltip->getText().empty() ) + return; + const auto& syntaxDef = resp.contents[0].kind == LSPMarkupKind::MarkDown ? SyntaxDefinitionManager::instance()->getByLSPName( "markdown" ) : editor->getSyntaxDefinition(); @@ -1472,7 +1475,8 @@ void LSPClientPlugin::displayTooltip( UICodeEditor* editor, const LSPHover& resp tooltip->notifyTextChangedFromTextCache(); - if ( editor->hasFocus() && !tooltip->isVisible() ) + if ( editor->hasFocus() && !tooltip->isVisible() && + !tooltip->getTextCache()->getString().empty() ) tooltip->show(); } diff --git a/src/tools/ecode/projectdirectorytree.cpp b/src/tools/ecode/projectdirectorytree.cpp index 1336da534..bc9e14ebf 100644 --- a/src/tools/ecode/projectdirectorytree.cpp +++ b/src/tools/ecode/projectdirectorytree.cpp @@ -505,9 +505,9 @@ PluginRequestHandle ProjectDirectoryTree::processMessage( const PluginMessage& m for ( size_t i = 0; i < rowCount; ++i ) { Variant dataName = model->data( model->index( i, 0 ) ); Variant dataPath = model->data( model->index( i, 1 ) ); - if ( dataName.is( Variant::Type::cstr ) && dataPath.is( Variant::Type::cstr ) ) { - std::string fileName( dataName.asCStr() ); - std::string filePath( dataPath.asCStr() ); + if ( dataName.isString() && dataPath.isString() ) { + std::string fileName( dataName.toString() ); + std::string filePath( dataPath.toString() ); if ( std::find( expectedNames.begin(), expectedNames.end(), fileName ) != expectedNames.end() ) { std::string closestDataPath; diff --git a/src/tools/ecode/statusbuildoutputcontroller.cpp b/src/tools/ecode/statusbuildoutputcontroller.cpp index b430af721..07697835c 100644 --- a/src/tools/ecode/statusbuildoutputcontroller.cpp +++ b/src/tools/ecode/statusbuildoutputcontroller.cpp @@ -518,8 +518,8 @@ void StatusBuildOutputController::createContainer() { auto idx = modelEvent->getModelIndex(); if ( modelEvent->getModelEventType() == ModelEventType::Open ) { Variant vPath( model->data( idx, ModelRole::Custom ) ); - if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { - std::string path( vPath.asCStr() ); + if ( vPath.isValid() && vPath.isString() ) { + std::string path( vPath.toString() ); UITab* tab = mSplitter->isDocumentOpen( path ); Variant lineNum( model->data( model->index( modelEvent->getModelIndex().row(), 1 ), ModelRole::Custom ) ); diff --git a/src/tools/ecode/universallocator.cpp b/src/tools/ecode/universallocator.cpp index bd07a528b..ca0039bc4 100644 --- a/src/tools/ecode/universallocator.cpp +++ b/src/tools/ecode/universallocator.cpp @@ -367,8 +367,7 @@ void UniversalLocator::initLocateBar( UILocateBar* locateBar, UITextInput* locat modelEvent->getModel()->index( modelEvent->getModelIndex().row(), 1 ), ModelRole::Display ) ); if ( vPath.isValid() && !String::startsWith( mLocateInput->getText(), ". " ) ) { - std::string path( vPath.is( Variant::Type::cstr ) ? vPath.asCStr() - : vPath.asStdString() ); + std::string path( vPath.toString() ); if ( path.empty() ) return; diff --git a/src/tools/uieditor/uieditor.hpp b/src/tools/uieditor/uieditor.hpp index fef1ffb1e..ab080540e 100644 --- a/src/tools/uieditor/uieditor.hpp +++ b/src/tools/uieditor/uieditor.hpp @@ -42,6 +42,8 @@ class App : public UICodeEditorSplitter::Client { virtual void onDocumentLoaded( UICodeEditor*, const std::string& ); + virtual void onTabCreated( UITab*, UIWidget* ) {} + void updateLayoutFunc( const InvalidationType& invalidator ); void updateStyleSheetFunc( const InvalidationType& invalidator ); void updateBaseStyleSheetFunc( const InvalidationType& invalidator );