From 74e3dee369456a2ea776801b0f98ceb65a24b565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 7 Jan 2024 15:18:07 -0300 Subject: [PATCH] Hide tab widget scroll when mouse is not over. Add context menu for splitter tabs. Git status count lines of added files and other fixes. Other code clean up regarding Variant class. --- include/eepp/ui/models/variant.hpp | 39 +++++++++++ .../eepp/ui/tools/uicodeeditorsplitter.hpp | 2 + include/eepp/ui/uitab.hpp | 4 ++ include/eepp/ui/uitabwidget.hpp | 9 ++- src/eepp/ui/abstract/uiabstracttableview.cpp | 35 ++-------- src/eepp/ui/models/sortingproxymodel.cpp | 14 ++-- src/eepp/ui/tools/uicodeeditorsplitter.cpp | 6 +- src/eepp/ui/uifiledialog.cpp | 2 +- src/eepp/ui/uitab.cpp | 38 ++++++++++ src/eepp/ui/uitableview.cpp | 8 +-- src/eepp/ui/uitabwidget.cpp | 29 ++++++-- src/eepp/ui/uitreeview.cpp | 35 ++-------- src/tools/ecode/applayout.xml.hpp | 9 +++ src/tools/ecode/ecode.cpp | 57 +++++++++++++-- src/tools/ecode/ecode.hpp | 2 + src/tools/ecode/globalsearchcontroller.cpp | 4 +- src/tools/ecode/plugins/git/git.cpp | 69 ++++++++++++++++--- src/tools/ecode/plugins/git/git.hpp | 34 ++++----- src/tools/ecode/plugins/git/gitplugin.cpp | 5 +- .../ecode/plugins/lsp/lspclientplugin.cpp | 6 +- src/tools/ecode/projectdirectorytree.cpp | 6 +- .../ecode/statusbuildoutputcontroller.cpp | 4 +- src/tools/ecode/universallocator.cpp | 3 +- src/tools/uieditor/uieditor.hpp | 2 + 24 files changed, 301 insertions(+), 121 deletions(-) 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 );