From 0ca36374c4d9007201767ab7f1c71ce71fa8b5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 27 Dec 2025 17:22:12 -0300 Subject: [PATCH] Terminal status bar now is a Tab Widget and can create any number of terminals. Fix a bug when initializing the LSP that provoked to not correctly send the initial commands. Added `expand-text` to UIPushButton. Added `setAcceptsDropOfWidgetFn` to externally control which widgets are accepted as droppable in UITabWidget. Improved draw invalidation in UIStackWidget. --- docs/articles/cssspecification.md | 10 ++ include/eepp/ui/css/propertydefinition.hpp | 1 + include/eepp/ui/uihelper.hpp | 4 +- include/eepp/ui/uipushbutton.hpp | 9 +- include/eepp/ui/uistackwidget.hpp | 6 + include/eepp/ui/uitabwidget.hpp | 5 + src/eepp/ui/css/stylesheetspecification.cpp | 1 + src/eepp/ui/uipushbutton.cpp | 72 ++++++-- src/eepp/ui/uistackwidget.cpp | 33 ++++ src/eepp/ui/uitabwidget.cpp | 20 ++- src/tools/ecode/applayout.xml.hpp | 34 ++-- .../ecode/plugins/lsp/lspclientserver.cpp | 21 +-- .../ecode/plugins/lsp/lspdocumentclient.cpp | 7 +- .../ecode/plugins/lsp/lspdocumentclient.hpp | 1 + src/tools/ecode/projectbuild.cpp | 15 +- src/tools/ecode/settingsmenu.cpp | 10 +- src/tools/ecode/statusterminalcontroller.cpp | 157 +++++++++++++++++- src/tools/ecode/statusterminalcontroller.hpp | 20 ++- src/tools/ecode/terminalmanager.cpp | 9 +- src/tools/ecode/uibuildsettings.cpp | 9 - src/tools/ecode/uistatusbar.cpp | 2 +- 21 files changed, 371 insertions(+), 75 deletions(-) diff --git a/docs/articles/cssspecification.md b/docs/articles/cssspecification.md index 248655270..539a77279 100644 --- a/docs/articles/cssspecification.md +++ b/docs/articles/cssspecification.md @@ -651,6 +651,16 @@ Allows enabling specific behavior flags for the code editor component. Multiple --- +### expand-text + +If `true` the inner element for text is expanded to occupy as much horizontal space as possible. + +* Applicable to: EE::UI::UIPushButton (PushButton) and any element that extends it: UIMenuItem, UISelectButton (SelectButton), UITableCell , UITableHeaderColumn. +* Data Type: [boolean](#boolean-data-type) +* Default value: `false` + +--- + ### font-family Read [font-family](https://developer.mozilla.org/en-US/docs/Web/CSS/font-family) documentation. diff --git a/include/eepp/ui/css/propertydefinition.hpp b/include/eepp/ui/css/propertydefinition.hpp index e2e3a3e71..9c6b14504 100644 --- a/include/eepp/ui/css/propertydefinition.hpp +++ b/include/eepp/ui/css/propertydefinition.hpp @@ -226,6 +226,7 @@ enum class PropertyId : Uint32 { LineWrapType = String::hash( "line-wrap-type" ), DisplayOptions = String::hash( "display-options" ), MenuWidthMode = String::hash( "menu-width-mode" ), + ExpandText = String::hash( "expand-text" ), }; enum class PropertyType : Uint32 { diff --git a/include/eepp/ui/uihelper.hpp b/include/eepp/ui/uihelper.hpp index 599dcd4b4..35c48abbd 100644 --- a/include/eepp/ui/uihelper.hpp +++ b/include/eepp/ui/uihelper.hpp @@ -103,11 +103,13 @@ enum UINodeType { UI_TYPE_LISTVIEW, UI_TYPE_CONSOLE, UI_TYPE_STACK_LAYOUT, + UI_TYPE_STACK_WIDGET, UI_TYPE_IMAGE_VIEWER, UI_TYPE_AUDIO_PLAYER, UI_TYPE_MODULES = 10000, UI_TYPE_TERMINAL = 10001, - UI_TYPE_USER = 200000 + UI_TYPE_USER = 200000, + UI_TYPE_UNKNOWN = 0xFFFFFFFF }; enum class ScrollBarMode : Uint32 { Auto, AlwaysOn, AlwaysOff }; diff --git a/include/eepp/ui/uipushbutton.hpp b/include/eepp/ui/uipushbutton.hpp index 497d3488b..877bc2bca 100644 --- a/include/eepp/ui/uipushbutton.hpp +++ b/include/eepp/ui/uipushbutton.hpp @@ -79,11 +79,15 @@ class EE_API UIPushButton : public UIWidget { bool isTextAsFallback() const; - void setTextAsFallback( bool textAsFallback ); + UIPushButton* setTextAsFallback( bool textAsFallback ); bool dontAutoHideEmptyTextBox() const; - void setDontAutoHideEmptyTextBox( bool dontAutoHideEmptyTextBox ); + UIPushButton* setDontAutoHideEmptyTextBox( bool dontAutoHideEmptyTextBox ); + + bool expandTextView() const; + + UIPushButton* setExpandTextView( bool expand ); protected: UIImage* mIcon; @@ -92,6 +96,7 @@ class EE_API UIPushButton : public UIWidget { InnerWidgetOrientation mInnerWidgetOrientation{ InnerWidgetOrientation::IconTextBoxWidget }; bool mTextAsFallback{ false }; bool mDontAutoHideEmptyTextBox{ false }; + bool mExpandTextView{ false }; UIPushButton(); diff --git a/include/eepp/ui/uistackwidget.hpp b/include/eepp/ui/uistackwidget.hpp index ab4954505..700216059 100644 --- a/include/eepp/ui/uistackwidget.hpp +++ b/include/eepp/ui/uistackwidget.hpp @@ -11,10 +11,16 @@ class EE_API UIStackWidget : public UIWidget { static UIStackWidget* NewWithTag( const std::string& tag = "stackwidget" ); + virtual Uint32 getType() const; + + virtual bool isType( const Uint32& type ) const; + void setActiveWidget( UIWidget* widget ); UIWidget* getActiveWidget() const; + void invalidate( Node* invalidator ); + protected: UIWidget* mActiveWidget{ nullptr }; diff --git a/include/eepp/ui/uitabwidget.hpp b/include/eepp/ui/uitabwidget.hpp index 4d671c926..2c23f3d8a 100644 --- a/include/eepp/ui/uitabwidget.hpp +++ b/include/eepp/ui/uitabwidget.hpp @@ -195,6 +195,8 @@ class EE_API UITabWidget : public UIWidget { virtual bool acceptsDropOfWidget( const UIWidget* widget ); + void setAcceptsDropOfWidgetFn( std::function fn ); + const Color& getDroppableHoveringColor() const; void setDroppableHoveringColor( const Color& droppableHoveringColor ); @@ -227,6 +229,8 @@ class EE_API UITabWidget : public UIWidget { TabJumpMode getTabJumpMode() const { return mTabJumpMode; } + void forEachTab( std::function fn, Uint32 filterOwnedType = UI_TYPE_UNKNOWN ); + protected: friend class UITab; @@ -257,6 +261,7 @@ class EE_API UITabWidget : public UIWidget { Float mSplitEdgePercent{ 0.1 }; UIListView* mTabSwitcher{ nullptr }; TabJumpMode mTabJumpMode{ TabJumpMode::Linear }; + std::function mAcceptsDropOfWidgetFn; void onThemeLoaded(); diff --git a/src/eepp/ui/css/stylesheetspecification.cpp b/src/eepp/ui/css/stylesheetspecification.cpp index f50acd552..8d9250292 100644 --- a/src/eepp/ui/css/stylesheetspecification.cpp +++ b/src/eepp/ui/css/stylesheetspecification.cpp @@ -400,6 +400,7 @@ void StyleSheetSpecification::registerDefaultProperties() { registerProperty( "gravity-owner", "false" ).setType( PropertyType::Bool ); registerProperty( "href", "" ).setType( PropertyType::String ); registerProperty( "focusable", "true" ).setType( PropertyType::Bool ); + registerProperty( "expand-text", "false" ).setType( PropertyType::Bool ); registerProperty( "inner-widget-orientation", "widgeticontextbox" ) .setType( PropertyType::String ); diff --git a/src/eepp/ui/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index 9bf755652..d93ef5ee9 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -65,7 +65,10 @@ UIPushButton::UIPushButton( const std::string& tag, UIWidget( tag ), mIcon( NULL ), mTextBox( NULL ) { mFlags |= ( UI_AUTO_SIZE | UI_VALIGN_CENTER | UI_HALIGN_CENTER ); - auto cb = [this]( const Event* ) { onSizeChange(); }; + auto cb = [this]( const Event* ) { + if ( mTextBox->getLayoutWidthPolicy() != SizePolicy::Fixed ) + onSizeChange(); + }; mTextBox = newTextViewCb ? newTextViewCb( this ) : UITextView::NewWithTag( tag + "::text" ); mTextBox->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent ) @@ -124,6 +127,9 @@ void UIPushButton::onAutoSize() { setInternalHeight( getSkinSize().getHeight() ); } + if ( mExpandTextView && mWidthPolicy == SizePolicy::MatchParent ) + return; + if ( mWidthPolicy == SizePolicy::WrapContent || ( mFlags & UI_AUTO_SIZE ) ) { Sizef size = getContentSize(); @@ -137,18 +143,19 @@ void UIPushButton::onAutoSize() { if ( size.getWidth() != fsize.getWidth() ) { UIWidget* eiw = getExtraInnerWidget(); Float nonTextW = - ( NULL != mIcon ? mIcon->getSize().getWidth() + mIcon->getLayoutMargin().Left + - mIcon->getLayoutMargin().Right - : 0 ) + + ( NULL != mIcon + ? mIcon->getPixelsSize().getWidth() + mIcon->getLayoutPixelsMargin().Left + + mIcon->getLayoutPixelsMargin().Right + : 0 ) + ( NULL != eiw && eiw->isVisible() - ? eiw->getSize().getWidth() + eiw->getLayoutMargin().Left + - eiw->getLayoutMargin().Right + ? eiw->getPixelsSize().getWidth() + eiw->getLayoutPixelsMargin().Left + + eiw->getLayoutPixelsMargin().Right : 0 ) + getSkinSize().getWidth(); - Float textW = mTextBox->getSize().getWidth(); + Float textW = mTextBox->getPixelsSize().getWidth(); - if ( textW > fsize.getWidth() - nonTextW ) { + if ( !mExpandTextView && textW > fsize.getWidth() - nonTextW ) { Float mw = eemax( 0.f, fsize.getWidth() - nonTextW ); getTextView()->setMaxWidthEq( String::format( "%.0fdp", mw ) ); } @@ -183,6 +190,27 @@ Vector2f UIPushButton::calcLayoutSize( const std::array& widgets, } Vector2f UIPushButton::packLayout( const std::array& widgets, const Rectf& padding ) { + if ( mExpandTextView && mTextBox && mTextBox->isVisible() ) { + Float availableWidth = mSize.getWidth() - padding.Left - padding.Right; + Float usedWidth = 0; + + for ( const auto& widget : widgets ) { + if ( widget && widget->isVisible() && widget != mTextBox ) { + usedWidth += widget->getPixelsSize().getWidth() + + widget->getLayoutPixelsMargin().Left + + widget->getLayoutPixelsMargin().Right; + } + } + + usedWidth += + mTextBox->getLayoutPixelsMargin().Left + mTextBox->getLayoutPixelsMargin().Right; + + Float textWidth = eemax( 0.f, availableWidth - usedWidth ); + + if ( textWidth != mTextBox->getPixelsSize().getWidth() ) + mTextBox->setPixelsSize( { textWidth, mTextBox->getPixelsSize().getHeight() } ); + } + std::array pos; Vector2f totSize{ padding.Left, padding.Top + padding.Bottom }; UIWidget* widget; @@ -303,22 +331,24 @@ bool UIPushButton::isTextAsFallback() const { return mTextAsFallback; } -void UIPushButton::setTextAsFallback( bool textAsFallback ) { +UIPushButton* UIPushButton::setTextAsFallback( bool textAsFallback ) { if ( mTextAsFallback != textAsFallback ) { mTextAsFallback = textAsFallback; updateTextBox(); } + return this; } bool UIPushButton::dontAutoHideEmptyTextBox() const { return mDontAutoHideEmptyTextBox; } -void UIPushButton::setDontAutoHideEmptyTextBox( bool dontAutoHideEmptyTextBox ) { +UIPushButton* UIPushButton::setDontAutoHideEmptyTextBox( bool dontAutoHideEmptyTextBox ) { if ( mDontAutoHideEmptyTextBox != dontAutoHideEmptyTextBox ) { mDontAutoHideEmptyTextBox = dontAutoHideEmptyTextBox; updateTextBox(); } + return this; } void UIPushButton::onPaddingChange() { @@ -536,12 +566,28 @@ void UIPushButton::setInnerWidgetOrientation( } } +bool UIPushButton::expandTextView() const { + return mExpandTextView; +} + +UIPushButton* UIPushButton::setExpandTextView( bool expand ) { + if ( mExpandTextView != expand ) { + mExpandTextView = expand; + mTextBox->setLayoutWidthPolicy( expand ? SizePolicy::Fixed : SizePolicy::WrapContent ); + mTextBox->setClipType( expand ? ClipType::ContentBox : ClipType::None ); + updateTextBox(); + } + return this; +} + std::string UIPushButton::getPropertyString( const PropertyDefinition* propertyDef, const Uint32& propertyIndex ) const { if ( NULL == propertyDef ) return ""; switch ( propertyDef->getPropertyId() ) { + case PropertyId::ExpandText: + return mExpandTextView ? "true" : "false"; case PropertyId::InnerWidgetOrientation: return innerWidgetOrientationToString( mInnerWidgetOrientation ); case PropertyId::Text: @@ -605,7 +651,8 @@ std::vector UIPushButton::getPropertiesImplemented() const { PropertyId::TextStrokeColor, PropertyId::TextSelection, PropertyId::TextTransform, - PropertyId::TextOverflow }; + PropertyId::TextOverflow, + PropertyId::ExpandText }; props.insert( props.end(), local.begin(), local.end() ); return props; } @@ -661,6 +708,9 @@ bool UIPushButton::applyProperty( const StyleSheetProperty& attribute ) { case PropertyId::Tint: getIcon()->setColor( attribute.asColor() ); break; + case PropertyId::ExpandText: + setExpandTextView( attribute.asBool() ); + break; case PropertyId::Color: case PropertyId::TextShadowColor: case PropertyId::TextShadowOffset: diff --git a/src/eepp/ui/uistackwidget.cpp b/src/eepp/ui/uistackwidget.cpp index e57a35519..934646125 100644 --- a/src/eepp/ui/uistackwidget.cpp +++ b/src/eepp/ui/uistackwidget.cpp @@ -12,6 +12,14 @@ UIStackWidget* UIStackWidget::NewWithTag( const std::string& tag ) { UIStackWidget::UIStackWidget( const std::string& tag ) : UIWidget( tag ) {} +Uint32 UIStackWidget::getType() const { + return UI_TYPE_STACK_WIDGET; +} + +bool UIStackWidget::isType( const Uint32& type ) const { + return getType() == type || UIWidget::isType( type ); +} + void UIStackWidget::setActiveWidget( UIWidget* widget ) { if ( widget == mActiveWidget ) return; @@ -77,4 +85,29 @@ void UIStackWidget::onChildCountChange( Node* child, const bool& removed ) { } } +void UIStackWidget::invalidate( Node* invalidator ) { + // Only invalidate if the invalidator is actually visible + if ( NULL != invalidator ) { + if ( invalidator == mActiveWidget ) { + if ( mActiveWidget->isVisible() ) + mNodeDrawInvalidator->invalidate( mActiveWidget ); + } else if ( invalidator->getParent() == mActiveWidget ) { + if ( invalidator->isVisible() ) + mNodeDrawInvalidator->invalidate( mActiveWidget ); + } else { + Node* container = invalidator->getParent(); + while ( container->getParent() != NULL && container->getParent() != mActiveWidget ) { + container = container->getParent(); + } + if ( container->getParent() == mActiveWidget && container->isVisible() ) { + mNodeDrawInvalidator->invalidate( mActiveWidget ); + } + } + } else if ( NULL != mNodeDrawInvalidator ) { + mNodeDrawInvalidator->invalidate( this ); + } else if ( NULL != mSceneNode ) { + mSceneNode->invalidate( this ); + } +} + }} // namespace EE::UI diff --git a/src/eepp/ui/uitabwidget.cpp b/src/eepp/ui/uitabwidget.cpp index e76a4316f..128d2c3d8 100644 --- a/src/eepp/ui/uitabwidget.cpp +++ b/src/eepp/ui/uitabwidget.cpp @@ -924,7 +924,8 @@ void UITabWidget::setAllowSwitchTabsInEmptySpaces( bool allowSwitchTabsInEmptySp bool UITabWidget::acceptsDropOfWidget( const UIWidget* widget ) { return mAllowDragAndDropTabs && widget && UI_TYPE_TAB == widget->getType() && !isParentOf( widget ) && - ( mSplitFn || widget->asConstType()->getTabWidget() != this ); + ( mSplitFn || widget->asConstType()->getTabWidget() != this ) && + ( !mAcceptsDropOfWidgetFn || mAcceptsDropOfWidgetFn( widget ) ); } const Color& UITabWidget::getDroppableHoveringColor() const { @@ -1107,7 +1108,9 @@ void UITabWidget::onPaddingChange() { Uint32 UITabWidget::onMessage( const NodeMessage* msg ) { if ( msg->getMsg() == NodeMessage::Drop && mAllowDragAndDropTabs ) { const NodeDropMessage* dropMsg = static_cast( msg ); - if ( dropMsg->getDroppedNode()->isType( UI_TYPE_TAB ) ) { + if ( dropMsg->getDroppedNode()->isType( UI_TYPE_TAB ) && + ( !mAcceptsDropOfWidgetFn || + mAcceptsDropOfWidgetFn( dropMsg->getDroppedNode()->asConstType() ) ) ) { UITab* tab = dropMsg->getDroppedNode()->asType(); auto dir = getDropDirection(); if ( !mSplitFn || !dir ) { @@ -1467,4 +1470,17 @@ Uint32 UITabWidget::getTabSelectedFocusHistoryFreezedIndex() const { return it != mFocusHistoryFreezed.end() ? std::distance( mFocusHistoryFreezed.begin(), it ) : 0; } +void UITabWidget::forEachTab( std::function fn, Uint32 filterOwnedType ) { + for ( const auto& tab : mTabs ) { + if ( filterOwnedType == UI_TYPE_UNKNOWN || + ( tab->getOwnedWidget() && tab->getOwnedWidget()->isType( filterOwnedType ) ) ) { + fn( tab ); + } + } +} + +void UITabWidget::setAcceptsDropOfWidgetFn( std::function fn ) { + mAcceptsDropOfWidgetFn = fn; +} + }} // namespace EE::UI diff --git a/src/tools/ecode/applayout.xml.hpp b/src/tools/ecode/applayout.xml.hpp index 157b72191..627ecf0ba 100644 --- a/src/tools/ecode/applayout.xml.hpp +++ b/src/tools/ecode/applayout.xml.hpp @@ -429,17 +429,17 @@ Anchor.error:hover { .texture-preview { border: 1dp solid var(--list-back); } -#code_container TabWidget { +.tab_widget_cont TabWidget { max-tab-width: 200dp; } -#code_container Tab > Tab::Text { +.tab_widget_cont Tab > Tab::Text { text-overflow: ellipsis; } -#code_container Tab > Tab::close { +.tab_widget_cont Tab > Tab::close { opacity: 0; } -#code_container Tab:selected > Tab::close, -#code_container Tab:hover > Tab::close { +.tab_widget_cont Tab:selected > Tab::close, +.tab_widget_cont Tab:hover > Tab::close { opacity: 1; } #project_view ScrollBar { @@ -451,42 +451,42 @@ Anchor.error:hover { #project_view ScrollBar:focus-within { opacity: 1; } -#code_container Tab > Tab::close { +.tab_widget_cont Tab > Tab::close { foreground-image: url("data:image/svg,"); foreground-tint: var(--tab-close); foreground-size: 10dp 10dp; foreground-position: center; } -#code_container Tab > Tab::close:hover { +.tab_widget_cont Tab > Tab::close:hover { foreground-tint: var(--tab-close-hover); } -#code_container Tab.tab_modified > tab::close { +.tab_widget_cont Tab.tab_modified > tab::close { foreground-image: url("data:image/svg,"); foreground-tint: var(--primary); foreground-size: 6dp 6dp; foreground-position: center; opacity: 1; } -#code_container Tab.tab_modified > Tab::close:hover { +.tab_widget_cont Tab.tab_modified > Tab::close:hover { foreground-image: url("data:image/svg,"); foreground-tint: var(--tab-close-hover); foreground-size: 10dp 10dp; foreground-position: center; } -#code_container Tab { +.tab_widget_cont Tab { text-decoration: none; } -#code_container Tab.tab_file_deleted { +.tab_widget_cont Tab.tab_file_deleted { color: var(--theme-error); text-decoration: strikethrough; } -#code_container TabWidget::TabBar ScrollBarMini { +.tab_widget_cont 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 { +.tab_widget_cont TabWidget::TabBar:hover ScrollBarMini, +.tab_widget_cont TabWidget::TabBar ScrollBarMini.dragging, +.tab_widget_cont TabWidget::TabBar ScrollBarMini:focus-within { opacity: 1; } .notbold { @@ -579,7 +579,7 @@ R"html( - + @@ -661,7 +661,7 @@ R"html( - + diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index 72093c295..459be59a2 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -1264,16 +1264,6 @@ void LSPClientServer::initialize() { didChangeConfiguration( mLSP.settings, mWorkspaceFolder.getFSPath(), true ); sendQueuedMessagesAsync(); - notifyServerInitialized(); - - // Broadcast the language capabilities to all the interested plugins - mManager->getPluginManager()->sendBroadcast( - mManager->getPlugin(), PluginMessageType::LanguageServerCapabilities, - PluginMessageFormat::LanguageServerCapabilities, &mCapabilities ); - - mManager->getPluginManager()->sendBroadcast( - nullptr, PluginMessageType::LanguageServerReady, - PluginMessageFormat::LSPClientServer, this ); }, []( const IdType&, const json& ) {} ); } @@ -2298,6 +2288,17 @@ void LSPClientServer::sendQueuedMessagesAsync() { mWritingStdIn--; } mQueuedMessages.clear(); + + notifyServerInitialized(); + + // Broadcast the language capabilities to all the interested plugins + mManager->getPluginManager()->sendBroadcast( + mManager->getPlugin(), PluginMessageType::LanguageServerCapabilities, + PluginMessageFormat::LanguageServerCapabilities, &mCapabilities ); + + mManager->getPluginManager()->sendBroadcast( nullptr, + PluginMessageType::LanguageServerReady, + PluginMessageFormat::LSPClientServer, this ); } ); } diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp index 4b3100187..af1deabfe 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp @@ -19,14 +19,17 @@ LSPDocumentClient::LSPDocumentClient( LSPClientServer* server, TextDocument* doc notifyOpen(); requestSymbolsDelayed(); requestSemanticHighlightingDelayed(); - if ( mServer->isReady() ) + if ( mServer->isReady() ) { + mAlreadyRequestedFoldingRanges = true; setupFoldRangeService(); + } } void LSPDocumentClient::onServerInitialized() { requestSymbols(); requestSemanticHighlighting(); - setupFoldRangeService(); + if ( !mAlreadyRequestedFoldingRanges ) + setupFoldRangeService(); // requestCodeLens(); } diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp index 537cfcc3d..9fd777ae6 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.hpp @@ -87,6 +87,7 @@ class LSPDocumentClient : public TextDocument::Client, public FoldRangeProvider bool mProcessingSemanticTokensResponse{ false }; bool mShutdown{ false }; bool mFirstHighlight{ true }; + bool mAlreadyRequestedFoldingRanges{ false }; void refreshTag(); diff --git a/src/tools/ecode/projectbuild.cpp b/src/tools/ecode/projectbuild.cpp index 2f687f50e..a177f1fc5 100644 --- a/src/tools/ecode/projectbuild.cpp +++ b/src/tools/ecode/projectbuild.cpp @@ -852,10 +852,17 @@ void ProjectBuildManager::runConfig( StatusAppOutputController* saoc ) { auto cmd = finalBuild.cmd + ( !finalBuild.args.empty() ? ( " " + finalBuild.args ) : "" ); if ( finalBuild.runInTerminal ) { if ( finalBuild.useStatusBarTerminal && mApp->getStatusTerminalController() ) { - mApp->getStatusTerminalController()->show(); - auto term = mApp->getStatusTerminalController()->getUITerminal(); - term->restart(); - term->executeFile( cmd ); + auto stc = mApp->getStatusTerminalController(); + stc->show(); + if ( finalBuild.reusePreviousTerminal ) { + stc->getTabWidget()->setTabSelected( (Uint32)0 ); + auto term = stc->getUITerminal(); + term->restart(); + term->executeFile( cmd ); + } else if ( stc->getTabWidget()->getTabCount() ) { + auto term = stc->createTerminal(); + term->executeFile( cmd ); + } } else { bool mustReuseLastUsedTerm = finalBuild.reusePreviousTerminal && mLastUsedTerm && diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index 9d7b0b016..929b00f5e 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -1018,9 +1018,13 @@ UIMenu* SettingsMenu::createTerminalMenu() { } ); if ( mApp->getStatusTerminalController() && - mApp->getStatusTerminalController()->getUITerminal() ) { - mApp->getStatusTerminalController()->getUITerminal()->getTerm()->setCursorMode( - mApp->getConfig().term.cursorStyle ); + mApp->getStatusTerminalController()->getTabWidget() ) { + mApp->getStatusTerminalController()->getTabWidget()->forEachTab( + [this]( UITab* tab ) { + tab->getOwnedWidget()->asType()->getTerm()->setCursorMode( + mApp->getConfig().term.cursorStyle ); + }, + UI_TYPE_TERMINAL ); } } ); diff --git a/src/tools/ecode/statusterminalcontroller.cpp b/src/tools/ecode/statusterminalcontroller.cpp index 5cf78d50c..6919f91ab 100644 --- a/src/tools/ecode/statusterminalcontroller.cpp +++ b/src/tools/ecode/statusterminalcontroller.cpp @@ -8,17 +8,114 @@ StatusTerminalController::StatusTerminalController( UISplitter* mainSplitter, StatusBarElement( mainSplitter, uiSceneNode, app ), mApp( app ) {} UIWidget* StatusTerminalController::getWidget() { - return mUITerminal; + return mContainer; } UIWidget* StatusTerminalController::createWidget() { - if ( mUITerminal == nullptr ) - mUITerminal = createTerminal(); + if ( mContainer == nullptr ) + mContainer = createContainer(); return getWidget(); } +bool StatusTerminalController::tryTabClose( UITab* tab ) { + if ( !tab->getOwnedWidget()->isWidget() || tab->getOwnedWidget() == nullptr ) + return true; + + UIWidget* widget = tab->getOwnedWidget()->asType(); + if ( widget == nullptr || widget->getData() == 0 ) + return true; + + if ( mContext->getConfig().term.warnBeforeClosingTab && widget->isType( UI_TYPE_TERMINAL ) ) { + UITerminal* term = widget->asType(); + ProcessID pid = term->getTerm()->getTerminal()->getProcess()->pid(); + if ( Sys::processHasChildren( pid ) ) { + UIMessageBox* msgBox = + UIMessageBox::New( UIMessageBox::OK_CANCEL, + mContext->i18n( "terminal_close_warn", + "Are you sure you want to close this " + "terminal?\nIt's still running a process." ) ); + msgBox->on( Event::OnConfirm, [widget]( auto ) { + reinterpret_cast( widget->getData() )->removeTab(); + } ); + msgBox->on( Event::OnClose, [this]( const Event* ) { mContainer->setFocus(); } ); + msgBox->setTitle( "ecode" ); + msgBox->center(); + msgBox->showWhenReady(); + return false; + } + } + return true; +} + +UIHLinearLayoutCommandExecuter* StatusTerminalController::createContainer() { + if ( mContainer ) + return mContainer; + static const auto XML = R"xml( + + + + + + + )xml"; + + if ( mMainSplitter->getLastWidget() != nullptr ) { + mMainSplitter->getLastWidget()->setVisible( false ); + mMainSplitter->getLastWidget()->setParent( mUISceneNode ); + } + + mContainer = mUISceneNode->loadLayoutFromString( XML, mMainSplitter ) + ->asType(); + mContainer->bind( "terminal_panel_tab_widget", mTabWidget ); + mContainer->bind( "terminal_panel_add", mAddBtn ); + mContainer->on( Event::OnFocus, [this]( auto ) { + if ( mTabWidget->getTabSelected() && mTabWidget->getTabSelected()->getOwnedWidget() ) + mTabWidget->getTabSelected()->getOwnedWidget()->setFocus(); + } ); + + mTabWidget->setAcceptsDropOfWidgetFn( []( const UIWidget* widget ) { + return widget->isType( UI_TYPE_TAB ) && + widget->asConstType()->getOwnedWidget()->isType( UI_TYPE_TERMINAL ); + } ); + + mTabWidget->setTabTryCloseCallback( + [this]( UITab* tab, UITabWidget::FocusTabBehavior ) -> bool { + return tryTabClose( tab ); + } ); + + createTerminal(); + + mAddBtn->onClick( [this]( auto ) { createTerminal(); } ); + + const auto onTabCountChange = [this]( auto ) { + if ( SceneManager::instance()->isShuttingDown() ) + return; + auto tabCount = mTabWidget->getTabCount(); + if ( tabCount == 0 ) + createTerminal(); + }; + + mTabWidget->on( Event::OnTabAdded, onTabCountChange ); + mTabWidget->on( Event::OnTabClosed, onTabCountChange ); + + return mContainer; +} + +UITabWidget* StatusTerminalController::getTabWidget() { + return mTabWidget; +} + UITerminal* StatusTerminalController::getUITerminal() { - return mUITerminal; + return mTabWidget->getTabCount() > 0 && + mTabWidget->getTab( 0 )->getOwnedWidget()->isType( UI_TYPE_TERMINAL ) + ? mTabWidget->getTab( 0 )->getOwnedWidget()->asType() + : nullptr; } UITerminal* StatusTerminalController::createTerminal( @@ -56,6 +153,9 @@ UITerminal* StatusTerminalController::createTerminal( ? terminalColorSchemes.at( currentTerminalColorScheme ) : TerminalColorScheme::getDefault() ); mContext->getTerminalManager()->setKeybindings( term ); + + mApp->registerUnlockedCommands( *term ); + term->setCommand( "switch-to-previous-colorscheme", [this] { auto it = mContext->getTerminalManager()->getTerminalColorSchemes().find( mContext->getTerminalManager()->getTerminalCurrentColorScheme() ); @@ -79,10 +179,53 @@ UITerminal* StatusTerminalController::createTerminal( term->setExclusiveMode( !term->getExclusiveMode() ); mApp->updateTerminalMenu(); } ); - mContext->getSplitter()->registerSplitterCommands( *term ); - mApp->registerUnlockedCommands( *term ); + term->setCommand( "close-tab", [this] { + if ( tryTabClose( mTabWidget->getTabSelected() ) ) + mTabWidget->removeTab( mTabWidget->getTabSelected() ); + } ); + term->setCommand( "create-new", [this] { createTerminal(); } ); + term->setCommand( "create-new-terminal", [this] { createTerminal(); } ); + term->setCommand( "next-tab", [this] { mTabWidget->focusNextTab(); } ); + term->setCommand( "previous-tab", [this] { mTabWidget->focusPreviousTab(); } ); + for ( int i = 1; i <= 10; i++ ) { + term->setCommand( String::format( "switch-to-tab-%d", i ), [this, i] { + mTabWidget->setTabSelected( eeclamp( i, 0, mTabWidget->getTabCount() - 1 ) ); + } ); + } + term->setCommand( "switch-to-first-tab", [this] { + if ( mTabWidget->getTabCount() ) + mTabWidget->setTabSelected( (Uint32)0 ); + } ); + term->setCommand( "switch-to-last-tab", [this] { + if ( mTabWidget->getTabCount() ) + mTabWidget->setTabSelected( (Uint32)( mTabWidget->getTabCount() - 1 ) ); + } ); + term->setCommand( "terminal-rename", [this, term] { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, mApp->i18n( "new_terminal_name", "New terminal name:" ) ); + msgBox->setTitle( mApp->getWindowTitle() ); + msgBox->getTextInput()->setHint( mApp->i18n( "any_name_ellipsis", "Any name..." ) ); + msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [msgBox, term]( const Event* ) { + std::string title( msgBox->getTextInput()->getText().toUtf8() ); + term->setTitle( title ); + msgBox->close(); + term->setFocus(); + } ); + } ); + term->setFocus(); - term->setId( "terminal" ); + term->setParent( mTabWidget ); + + UIIcon* icon = mUISceneNode->findIcon( "terminal" ); + auto tab = mTabWidget->add( + program, term, icon != nullptr ? icon->getSize( PixelDensity::dpToPxI( 12 ) ) : nullptr ); + + term->setData( (UintPtr)tab ); + term->on( Event::OnTitleChange, [tab, term]( auto ) { tab->setText( term->getTitle() ); } ); + + mTabWidget->setTabSelected( tab ); return term; } diff --git a/src/tools/ecode/statusterminalcontroller.hpp b/src/tools/ecode/statusterminalcontroller.hpp index 2982f5fe3..8aeb3c07c 100644 --- a/src/tools/ecode/statusterminalcontroller.hpp +++ b/src/tools/ecode/statusterminalcontroller.hpp @@ -2,6 +2,7 @@ #define ECODE_STATUSTERMINALCONTROLLER_HPP #include "uistatusbar.hpp" +#include "widgetcommandexecuter.hpp" #include #include @@ -29,14 +30,25 @@ class StatusTerminalController : public StatusBarElement { UITerminal* getUITerminal(); - protected: - App* mApp; - - UITerminal* mUITerminal{ nullptr }; + UITabWidget* getTabWidget(); UITerminal* createTerminal( const std::string& workingDir = "", std::string program = "", std::vector args = {}, const std::unordered_map& env = {} ); + + protected: + App* mApp; + + UIHLinearLayoutCommandExecuter* mContainer{ nullptr }; + UITabWidget* mTabWidget{ nullptr }; + + UIPushButton* mAddBtn{ nullptr }; + + UITerminal* mUITerminal{ nullptr }; + + UIHLinearLayoutCommandExecuter* createContainer(); + + bool tryTabClose( UITab* tab ); }; } // namespace ecode diff --git a/src/tools/ecode/terminalmanager.cpp b/src/tools/ecode/terminalmanager.cpp index 1bc0c71ac..1ec3c0c97 100644 --- a/src/tools/ecode/terminalmanager.cpp +++ b/src/tools/ecode/terminalmanager.cpp @@ -75,8 +75,13 @@ void TerminalManager::applyTerminalColorScheme( const TerminalColorScheme& color } ); if ( mApp->getStatusTerminalController() && - mApp->getStatusTerminalController()->getUITerminal() ) { - mApp->getStatusTerminalController()->getUITerminal()->setColorScheme( colorScheme ); + mApp->getStatusTerminalController()->getTabWidget() ) { + mApp->getStatusTerminalController()->getTabWidget()->forEachTab( + [colorScheme]( UITab* tab ) { + tab->getOwnedWidget()->asType()->getTerm()->setColorScheme( + colorScheme ); + }, + UI_TYPE_TERMINAL ); } } diff --git a/src/tools/ecode/uibuildsettings.cpp b/src/tools/ecode/uibuildsettings.cpp index 16aac5b14..4f6368502 100644 --- a/src/tools/ecode/uibuildsettings.cpp +++ b/src/tools/ecode/uibuildsettings.cpp @@ -310,15 +310,6 @@ class UIBuildStep : public UILinearLayout { } } ); - useStatusBarTerminal->on( Event::OnValueChange, [runInTerminal, reusePreviousTerminal, - useStatusBarTerminal]( auto ) { - if ( useStatusBarTerminal->isChecked() ) { - reusePreviousTerminal->setChecked( true )->setEnabled( false ); - } else if ( runInTerminal->isChecked() ) { - reusePreviousTerminal->setEnabled( true ); - } - } ); - reusePreviousTerminal->setVisible( true ); reusePreviousTerminal->setEnabled( buildStep->runInTerminal ); reusePreviousTerminal->setChecked( buildStep->reusePreviousTerminal ); diff --git a/src/tools/ecode/uistatusbar.cpp b/src/tools/ecode/uistatusbar.cpp index 650eff6e2..9e8d8d0aa 100644 --- a/src/tools/ecode/uistatusbar.cpp +++ b/src/tools/ecode/uistatusbar.cpp @@ -132,7 +132,7 @@ Uint32 UIStatusBar::onMessage( const NodeMessage* msg ) { } else if ( widget->getId() == "status_global_search_bar" ) { mContext->getGlobalSearchController()->toggleGlobalSearchBar(); ret = 1; - } else if ( widget->getId() == "status_terminal" ) { + } else if ( widget->getId() == "status_terminal_panel" ) { mContext->getStatusTerminalController()->toggle(); ret = 1; } else if ( widget->getId() == "status_build_output" ) {