From 70aecddd187a10f35c9a0b60742b75717a9fad50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 24 May 2025 01:10:50 -0300 Subject: [PATCH] Added support for Tab jump modes (SpartanJ/ecode#469). Including a Tab Switcher (I have no better name for the moment). Added support to hide the Tab Bar (SpartanJ/ecode#477). Updated efsw. Updated JS, TS, C++ grammars. --- bin/assets/plugins/aiassistant.json | 6 +- include/eepp/ui/keyboardshortcut.hpp | 4 +- include/eepp/ui/models/itemlistmodel.hpp | 2 + .../eepp/ui/tools/uicodeeditorsplitter.hpp | 19 +- include/eepp/ui/uitabwidget.hpp | 39 ++++ include/eepp/window/keycodes.hpp | 2 + src/eepp/ui/doc/languages/cpp.cpp | 4 +- src/eepp/ui/doc/languages/javascript.cpp | 3 +- src/eepp/ui/doc/syntaxdefinitionmanager.cpp | 3 + src/eepp/ui/keyboardshortcut.cpp | 7 + src/eepp/ui/tools/uicodeeditorsplitter.cpp | 10 + src/eepp/ui/uieventdispatcher.cpp | 2 +- src/eepp/ui/uiscrollablewidget.cpp | 15 +- src/eepp/ui/uitabwidget.cpp | 218 +++++++++++++++++- src/eepp/window/keycodes.cpp | 75 ++++-- .../src/eepp/ui/doc/languages/typescript.cpp | 3 +- src/thirdparty/efsw | 2 +- src/tools/ecode/appconfig.cpp | 9 + src/tools/ecode/appconfig.hpp | 4 + src/tools/ecode/ecode.cpp | 1 + src/tools/ecode/ecode.hpp | 46 ++++ src/tools/ecode/settingsmenu.cpp | 110 +++++++++ src/tools/ecode/settingsmenu.hpp | 1 + src/tools/ecode/statusterminalcontroller.cpp | 2 +- src/tools/ecode/terminalmanager.cpp | 1 - 25 files changed, 536 insertions(+), 52 deletions(-) diff --git a/bin/assets/plugins/aiassistant.json b/bin/assets/plugins/aiassistant.json index ab5e569ce..6448d0ee6 100644 --- a/bin/assets/plugins/aiassistant.json +++ b/bin/assets/plugins/aiassistant.json @@ -209,8 +209,7 @@ }, { "max_tokens": 128000, - "name": "gpt-4o-mini", - "cheapest": true + "name": "gpt-4o-mini" }, { "max_tokens": 200000, @@ -229,7 +228,8 @@ "name": "o3-mini" }, { - "name": "gpt-4.1-nano" + "name": "gpt-4.1-nano", + "cheapest": true }, { "name": "gpt-4.1-mini" diff --git a/include/eepp/ui/keyboardshortcut.hpp b/include/eepp/ui/keyboardshortcut.hpp index ea0a9884a..61d981a07 100644 --- a/include/eepp/ui/keyboardshortcut.hpp +++ b/include/eepp/ui/keyboardshortcut.hpp @@ -66,7 +66,7 @@ class EE_API KeyBindings { void removeKeybind( const Shortcut& keys ); - void removeKeybind(const std::string& kb ); + void removeKeybind( const std::string& kb ); bool existsKeybind( const Shortcut& keys ); @@ -86,6 +86,8 @@ class EE_API KeyBindings { const std::map getKeybindings() const; + Shortcut getShortcutFromCommand( const std::string& cmd ) const; + std::string getShortcutString( Shortcut shortcut, bool format = false ) const; protected: diff --git a/include/eepp/ui/models/itemlistmodel.hpp b/include/eepp/ui/models/itemlistmodel.hpp index 579cf0d28..b40258a89 100644 --- a/include/eepp/ui/models/itemlistmodel.hpp +++ b/include/eepp/ui/models/itemlistmodel.hpp @@ -109,6 +109,8 @@ template class ItemListOwnerModel final : public Model { return std::make_shared>( data ); } + explicit ItemListOwnerModel( std::vector&& data ) : mData( std::move( data ) ) {} + explicit ItemListOwnerModel( const std::vector& data ) : mData( data ) {} virtual ~ItemListOwnerModel() {} diff --git a/include/eepp/ui/tools/uicodeeditorsplitter.hpp b/include/eepp/ui/tools/uicodeeditorsplitter.hpp index 6a3f13827..60be8ba04 100644 --- a/include/eepp/ui/tools/uicodeeditorsplitter.hpp +++ b/include/eepp/ui/tools/uicodeeditorsplitter.hpp @@ -230,6 +230,8 @@ class EE_API UICodeEditorSplitter { void setHideTabBarOnSingleTab( bool hideTabBarOnSingleTab ); + void setHideTabBar( bool hideTabBar ); + const std::vector& getTabWidgets() const; Node* getBaseLayout() const; @@ -280,21 +282,13 @@ class EE_API UICodeEditorSplitter { } ); t.setCommand( "next-tab", [this] { UITabWidget* tabWidget = tabWidgetFromWidget( mCurWidget ); - if ( tabWidget && tabWidget->getTabCount() > 1 ) { - UITab* tab = (UITab*)mCurWidget->getData(); - Uint32 tabIndex = tabWidget->getTabIndex( tab ); - switchToTab( ( tabIndex + 1 ) % tabWidget->getTabCount() ); - } + if ( tabWidget ) + tabWidget->focusNextTab(); } ); t.setCommand( "previous-tab", [this] { UITabWidget* tabWidget = tabWidgetFromWidget( mCurWidget ); - if ( tabWidget && tabWidget->getTabCount() > 1 ) { - UITab* tab = (UITab*)mCurWidget->getData(); - Uint32 tabIndex = tabWidget->getTabIndex( tab ); - Int32 newTabIndex = (Int32)tabIndex - 1; - switchToTab( newTabIndex < 0 ? tabWidget->getTabCount() - newTabIndex - : newTabIndex ); - } + if ( tabWidget ) + tabWidget->focusPreviousTab(); } ); for ( int i = 1; i <= 10; i++ ) t.setCommand( String::format( "switch-to-tab-%d", i ), @@ -379,6 +373,7 @@ class EE_API UICodeEditorSplitter { std::vector mTabWidgets; Node* mBaseLayout{ nullptr }; Client* mClient; + bool mHideTabBar{ false }; bool mHideTabBarOnSingleTab{ true }; bool mFirstCodeEditor{ true }; bool mVisualSplitting{ true }; diff --git a/include/eepp/ui/uitabwidget.hpp b/include/eepp/ui/uitabwidget.hpp index bde8aefe5..a7e9cd56f 100644 --- a/include/eepp/ui/uitabwidget.hpp +++ b/include/eepp/ui/uitabwidget.hpp @@ -11,6 +11,7 @@ namespace EE { namespace UI { class UIPopUpMenu; class UIScrollBar; +class UIListView; class EE_API TabEvent : public Event { public: @@ -32,6 +33,17 @@ class EE_API UITabWidget : public UIWidget { enum class FocusTabBehavior { Closest, FocusOrder, Default }; + enum class TabJumpMode { Linear, Chronological }; + + static TabJumpMode tabJumpModefromString( std::string_view mode ) { + return String::iequals( mode, "chronological" ) ? TabJumpMode::Chronological + : TabJumpMode::Linear; + } + + static std::string tabJumpModeToString( TabJumpMode mode ) { + return mode == TabJumpMode::Chronological ? "chronological" : "linear"; + } + class StyleConfig { public: Float TabSeparation = 0; @@ -151,10 +163,14 @@ class EE_API UITabWidget : public UIWidget { UITab* setTabSelected( const Uint32& tabIndex ); + const bool& getHideTabBar() const; + const bool& getHideTabBarOnSingleTab() const; void setHideTabBarOnSingleTab( const bool& hideTabBarOnSingleTab ); + void setHideTabBar( const bool& hideTabBar ); + const TabTryCloseCallback& getTabTryCloseCallback() const; void setTabTryCloseCallback( const TabTryCloseCallback& tabTryCloseCallback ); @@ -195,6 +211,20 @@ class EE_API UITabWidget : public UIWidget { void setSplitFunction( SplitFunctionCb cb, Float splitEdgePercent = 0.1 ); + const std::deque& getFocusHistory() const { return mFocusHistory; } + + void setEnableTabSwitcher( bool enable ) { mEnableTabSwitcher = enable; } + + bool isTabSwitcherEnabled() const { return mEnableTabSwitcher; } + + void focusNextTab( const std::vector& tabSwitcherMetaTrigger = {} ); + + void focusPreviousTab( const std::vector& tabSwitcherMetaTrigger = {} ); + + void setTabJumpMode( TabJumpMode mode ) { mTabJumpMode = mode; } + + TabJumpMode getTabJumpMode() const { return mTabJumpMode; } + protected: friend class UITab; @@ -207,11 +237,13 @@ class EE_API UITabWidget : public UIWidget { Uint32 mTabSelectedIndex; TabTryCloseCallback mTabTryCloseCallback; bool mHideTabBarOnSingleTab{ false }; + bool mHideTabBar{ false }; bool mAllowRearrangeTabs{ false }; bool mAllowDragAndDropTabs{ false }; bool mAllowSwitchTabsInEmptySpaces{ false }; bool mDroppableHoveringColorWasSet{ false }; bool mEnabledCreateContextMenu{ false }; + bool mEnableTabSwitcher{ false }; Float mTabVerticalDragResistance; Color mDroppableHoveringColor{ Color::Transparent }; FocusTabBehavior mFocusTabBehavior{ FocusTabBehavior::Closest }; @@ -219,6 +251,8 @@ class EE_API UITabWidget : public UIWidget { UIPopUpMenu* mCurrentMenu{ nullptr }; SplitFunctionCb mSplitFn; Float mSplitEdgePercent{ 0.1 }; + UIListView* mTabSwitcher{ nullptr }; + TabJumpMode mTabJumpMode{ TabJumpMode::Chronological }; void onThemeLoaded(); @@ -268,6 +302,11 @@ class EE_API UITabWidget : public UIWidget { void eraseFocusHistory( UITab* tab ); std::optional getDropDirection() const; + + void createTabSwitcher( const std::vector& tabSwitcherMetaTrigger, + bool fromPrev = false ); + + Uint32 getTabSelectedFocusHistoryIndex() const; }; }} // namespace EE::UI diff --git a/include/eepp/window/keycodes.hpp b/include/eepp/window/keycodes.hpp index 50559ca42..d65f7b535 100644 --- a/include/eepp/window/keycodes.hpp +++ b/include/eepp/window/keycodes.hpp @@ -712,6 +712,8 @@ class EE_API KeyMod { static const std::map& getModMap(); + static std::vector getKeyCodesFromModifier( Uint32 mod ); + private: static Uint32 defaultModifier; }; diff --git a/src/eepp/ui/doc/languages/cpp.cpp b/src/eepp/ui/doc/languages/cpp.cpp index 4bd9faf77..26f9c12db 100644 --- a/src/eepp/ui/doc/languages/cpp.cpp +++ b/src/eepp/ui/doc/languages/cpp.cpp @@ -25,7 +25,9 @@ void addCPP() { { "keyword", "keyword", "literal" } }, { { "cpp_number_parser" }, "number", "", SyntaxPatternMatchType::Parser }, { { "[%+%-=/%*%^%%<>!~|&]" }, "operator" }, - { { "(if|for|while|switch|catch|sizeof|typeid|defined)\\s*(?=\\()" }, + { { "(if|for|while|switch|catch|sizeof|typeid|defined|return|throw|alignof|noexcept|" + "new|delete)" + "\\s*(?=\\()" }, { "normal", "keyword", "keyword" }, "", SyntaxPatternMatchType::RegEx }, diff --git a/src/eepp/ui/doc/languages/javascript.cpp b/src/eepp/ui/doc/languages/javascript.cpp index 22f8f49a5..bd184afce 100644 --- a/src/eepp/ui/doc/languages/javascript.cpp +++ b/src/eepp/ui/doc/languages/javascript.cpp @@ -20,7 +20,8 @@ void addJavaScript() { "string" }, { { "js_number_parser" }, "number", "", SyntaxPatternMatchType::Parser }, { { "[%+%-=/%*%^%%<>!~|&]" }, "operator" }, - { { "(if|for|while|switch|catch|with)\\s*(?=\\()" }, + { { "(if|for|while|switch|catch|with|typeof|delete|void|super|await|function)\\s*(?=" + "\\()" }, { "normal", "keyword", "keyword" }, "", SyntaxPatternMatchType::RegEx }, diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index 6d7020048..7ca5ac429 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -63,6 +63,9 @@ class TextMateScopeMapper { { "constant.other", "literal" }, { "constant.regexp", "string" }, + { "meta.import", "keyword" }, + { "meta.package", "keyword" }, + { "comment.unused", "normal" }, // unused comments pattern { "declaration.package", "literal" }, { "declaration.import", "literal" }, diff --git a/src/eepp/ui/keyboardshortcut.cpp b/src/eepp/ui/keyboardshortcut.cpp index d6210610b..b5e3f8fbd 100644 --- a/src/eepp/ui/keyboardshortcut.cpp +++ b/src/eepp/ui/keyboardshortcut.cpp @@ -119,6 +119,13 @@ bool KeyBindings::hasCommand( const std::string& command ) { return mKeybindingsInvert.find( command ) != mKeybindingsInvert.end(); } +KeyBindings::Shortcut KeyBindings::getShortcutFromCommand( const std::string& cmd ) const { + auto it = mKeybindingsInvert.find( cmd ); + if ( it != mKeybindingsInvert.end() ) + return it->second; + return {}; +} + void KeyBindings::removeCommandKeybind( const std::string& command ) { auto kbIt = mKeybindingsInvert.find( command ); if ( kbIt != mKeybindingsInvert.end() ) { diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp index 07a75b33e..5180716a6 100644 --- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -632,6 +632,7 @@ UITabWidget* UICodeEditorSplitter::createTabWidget( Node* parent ) { tabWidget->setParent( parent ); tabWidget->setTabsClosable( true ); tabWidget->setHideTabBarOnSingleTab( mHideTabBarOnSingleTab ); + tabWidget->setHideTabBar( mHideTabBar ); tabWidget->setAllowRearrangeTabs( true ); tabWidget->setAllowDragAndDropTabs( true ); tabWidget->setAllowSwitchTabsInEmptySpaces( true ); @@ -958,6 +959,15 @@ void UICodeEditorSplitter::setHideTabBarOnSingleTab( bool hideTabBarOnSingleTab } } +void UICodeEditorSplitter::setHideTabBar( bool hideTabBar ) { + if ( hideTabBar != mHideTabBar ) { + mHideTabBar = hideTabBar; + + for ( auto widget : mTabWidgets ) + widget->setHideTabBar( hideTabBar ); + } +} + const std::vector& UICodeEditorSplitter::getTabWidgets() const { return mTabWidgets; } diff --git a/src/eepp/ui/uieventdispatcher.cpp b/src/eepp/ui/uieventdispatcher.cpp index dd54e3c93..efb769b2e 100644 --- a/src/eepp/ui/uieventdispatcher.cpp +++ b/src/eepp/ui/uieventdispatcher.cpp @@ -52,7 +52,7 @@ void UIEventDispatcher::checkTabPress( const Uint32& KeyCode, const Uint32& mod if ( mFocusNode->isWidget() && NULL != win && !mJustGainedFocus ) { if ( mod & KEYMOD_SHIFT ) { mFocusNode->asType()->onFocusPrevWidget(); - } else { + } else if ( mod == 0 ) { mFocusNode->asType()->onFocusNextWidget(); } } diff --git a/src/eepp/ui/uiscrollablewidget.cpp b/src/eepp/ui/uiscrollablewidget.cpp index de070594d..747536b3b 100644 --- a/src/eepp/ui/uiscrollablewidget.cpp +++ b/src/eepp/ui/uiscrollablewidget.cpp @@ -203,7 +203,9 @@ Rectf UIScrollableWidget::getVisibleRect() const { bool UIScrollableWidget::shouldVerticalScrollBeVisible() const { Float totH = getPixelsSize().getHeight() - getPixelsPadding().Top - getPixelsPadding().Bottom - - mHScroll->getPixelsSize().getHeight(); + ( mHScrollMode == ScrollBarMode::AlwaysOff || !mHScroll->isVisible() + ? 0 + : mHScroll->getPixelsSize().getHeight() ); return getContentSize().getHeight() > totH; } @@ -391,6 +393,17 @@ Uint32 UIScrollableWidget::onMessage( const NodeMessage* Msg ) { if ( moved ) return 1; } + case NodeMessage::FocusLoss: { + if ( NULL != getEventDispatcher() ) { + Node* focusNode = getEventDispatcher()->getFocusNode(); + + if ( this != focusNode && !isParentOf( focusNode ) ) { + onWidgetFocusLoss(); + } + + return 1; + } + } } return UIWidget::onMessage( Msg ); } diff --git a/src/eepp/ui/uitabwidget.cpp b/src/eepp/ui/uitabwidget.cpp index feebce2ff..afcca3f6a 100644 --- a/src/eepp/ui/uitabwidget.cpp +++ b/src/eepp/ui/uitabwidget.cpp @@ -1,17 +1,57 @@ -#include "eepp/ui/uiscenenode.hpp" -#include #include #include #include #include +#include +#include #include +#include #include #include #include #include +#include + namespace EE { namespace UI { +class TabsModel final : public Model { + public: + static std::shared_ptr create( const std::deque& data, bool invertView ) { + return std::make_shared( data, invertView ); + } + + explicit TabsModel( const std::deque& data, bool invertView ) : + mData( data ), mInvertView( invertView ) {} + + virtual ~TabsModel() {} + + virtual size_t rowCount( const ModelIndex& ) const { return mData.size(); } + + virtual size_t columnCount( const ModelIndex& ) const { return 1; } + + virtual std::string columnName( const size_t& ) const { return "Data"; } + + virtual ModelIndex index( int row, int column, const ModelIndex& parent = ModelIndex() ) const { + if ( row >= (int)rowCount( parent ) || column >= (int)columnCount( parent ) ) + return {}; + return Model::index( row, column, parent ); + } + + virtual Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const { + if ( role == ModelRole::Display ) + return mInvertView ? Variant( mData[mData.size() - 1 - index.row()]->getText() ) + : Variant( mData[index.row()]->getText() ); + return {}; + } + + bool invertedView() const { return mInvertView; } + + private: + const std::deque& mData; + bool mInvertView{ false }; +}; + UITabWidget* UITabWidget::New() { return eeNew( UITabWidget, () ); } @@ -23,6 +63,7 @@ UITabWidget::UITabWidget() : mTabSelected( NULL ), mTabSelectedIndex( eeINDEX_NOT_FOUND ), mHideTabBarOnSingleTab( false ), + mHideTabBar( false ), mAllowRearrangeTabs( false ), mAllowDragAndDropTabs( false ), mAllowSwitchTabsInEmptySpaces( false ), @@ -101,7 +142,7 @@ void UITabWidget::onThemeLoaded() { } void UITabWidget::setContainerSize() { - if ( getTabCount() < 2 && mHideTabBarOnSingleTab ) { + if ( mHideTabBar || ( getTabCount() < 2 && mHideTabBarOnSingleTab ) ) { mTabBar->setVisible( false ); mTabBar->setEnabled( false ); mNodeContainer->setPosition( mPadding.Left, mPadding.Top ); @@ -222,7 +263,7 @@ void UITabWidget::invalidate( Node* invalidator ) { mSceneNode->invalidate( this ); } } else if ( invalidator == mNodeContainer || invalidator == mTabBar || - mTabBar->inParentTreeOf( invalidator ) ) { + invalidator == mTabSwitcher || mTabBar->inParentTreeOf( invalidator ) ) { mNodeDrawInvalidator->invalidate( mNodeContainer ); } else if ( invalidator->getParent() == mNodeContainer ) { if ( invalidator->isVisible() ) @@ -815,6 +856,10 @@ UITab* UITabWidget::setTabSelected( const Uint32& tabIndex ) { return NULL; } +const bool& UITabWidget::getHideTabBar() const { + return mHideTabBar; +} + const bool& UITabWidget::getHideTabBarOnSingleTab() const { return mHideTabBarOnSingleTab; } @@ -826,6 +871,13 @@ void UITabWidget::setHideTabBarOnSingleTab( const bool& hideTabBarOnSingleTab ) } } +void UITabWidget::setHideTabBar( const bool& hideTabBar ) { + if ( mHideTabBar != hideTabBar ) { + mHideTabBar = hideTabBar; + setContainerSize(); + } +} + const UITabWidget::TabTryCloseCallback& UITabWidget::getTabTryCloseCallback() const { return mTabTryCloseCallback; } @@ -986,7 +1038,8 @@ void UITabWidget::onSizeChange() { } void UITabWidget::onChildCountChange( Node* child, const bool& removed ) { - if ( !removed && child != mTabBar && child != mNodeContainer && child != mTabScroll ) { + if ( !removed && child != mTabBar && child != mNodeContainer && child != mTabScroll && + child != mTabSwitcher ) { if ( child->isType( UI_TYPE_TAB ) ) { // This must be a tab that was dragging. if ( std::find( mTabs.begin(), mTabs.end(), child->asType() ) != mTabs.end() ) @@ -1192,4 +1245,159 @@ void UITabWidget::updateScroll( bool updateFocus ) { } } +void UITabWidget::createTabSwitcher( const std::vector& tabSwitcherMetaTrigger, + bool fromPrev ) { + bool resetSelection = mTabSwitcher == nullptr || !mTabSwitcher->isVisible(); + if ( mTabSwitcher == nullptr ) { + mTabSwitcher = UIListView::New(); + mTabSwitcher->setParent( this ); + mTabSwitcher->setVisible( false ); + mTabSwitcher->setHorizontalScrollMode( ScrollBarMode::AlwaysOff ); + } + + const std::deque& history = mTabJumpMode == TabJumpMode::Linear ? mTabs : mFocusHistory; + + if ( mTabSwitcher->getModel() == nullptr || + ( mTabJumpMode == TabJumpMode::Linear && + static_cast( mTabSwitcher->getModel() )->invertedView() ) || + ( mTabJumpMode == TabJumpMode::Chronological && + !static_cast( mTabSwitcher->getModel() )->invertedView() ) ) { + mTabSwitcher->setModel( + TabsModel::create( mTabJumpMode == TabJumpMode::Linear ? mTabs : mFocusHistory, + mTabJumpMode == TabJumpMode::Chronological ) ); + resetSelection = true; + } + + if ( resetSelection && !history.empty() ) { + switch ( mTabJumpMode ) { + case TabJumpMode::Linear: { + Uint32 index = + fromPrev ? ( (Int32)history.size() - 1 < 0 ? history.size() - 1 + : ( getTabSelectedIndex() - 1 ) ) + : ( ( getTabSelectedIndex() + 1 ) % history.size() ); + mTabSwitcher->setSelection( mTabSwitcher->getModel()->index( index, 0 ) ); + break; + } + case TabJumpMode::Chronological: { + mTabSwitcher->setSelection( mTabSwitcher->getModel()->index( + fromPrev ? history.size() - 1 : ( history.size() == 1 ? 0 : 1 ), 0 ) ); + break; + } + } + } + + mTabSwitcher->setPixelsSize( eeceil( PixelDensity::dpToPx( 300 ) ), + eeceil( mTabSwitcher->getContentSize().getHeight() + + mTabSwitcher->getPixelsPadding().getHeight() ) ); + mTabSwitcher->setMaxSizeEq( "80%", "70%" ); + mTabSwitcher->center(); + mTabSwitcher->toFront(); + mTabSwitcher->removeEventsOfType( Event::OnFocusLoss ); + mTabSwitcher->setVisible( true ); + mTabSwitcher->setFocus(); + mTabSwitcher->on( Event::OnWidgetFocusLoss, [this]( const Event* event ) { + mTabSwitcher->setVisible( false ); + mTabSwitcher->removeEventListener( event->getCallbackId() ); + } ); + + if ( !tabSwitcherMetaTrigger.empty() ) { + mTabSwitcher->removeEventsOfType( Event::KeyUp ); + mTabSwitcher->on( Event::KeyUp, [this, tabSwitcherMetaTrigger]( const Event* event ) { + bool inverted = mTabJumpMode != TabJumpMode::Linear; + const std::deque& history = + mTabJumpMode == TabJumpMode::Linear ? mTabs : mFocusHistory; + auto keyCode = event->asKeyEvent()->getKeyCode(); + if ( std::find( tabSwitcherMetaTrigger.begin(), tabSwitcherMetaTrigger.end(), + keyCode ) != tabSwitcherMetaTrigger.end() || + keyCode == KEY_RETURN ) { + mTabSwitcher->removeEventListener( event->getCallbackId() ); + mTabSwitcher->setVisible( false ); + if ( !mTabSwitcher->getSelection().isEmpty() && + mTabSwitcher->getSelection().first().row() < (Int64)history.size() ) { + if ( inverted ) { + setTabSelected( history[history.size() - 1 - + mTabSwitcher->getSelection().first().row()] ); + } else { + setTabSelected( history[mTabSwitcher->getSelection().first().row()] ); + } + } + } else if ( keyCode == KEY_ESCAPE ) { + mTabSwitcher->removeEventListener( event->getCallbackId() ); + mTabSwitcher->setVisible( false ); + } + } ); + } +} + +void UITabWidget::focusNextTab( const std::vector& tabSwitcherMetaTrigger ) { + if ( mEnableTabSwitcher ) { + bool wasVisible = mTabSwitcher && mTabSwitcher->isVisible(); + + createTabSwitcher( tabSwitcherMetaTrigger ); + + if ( wasVisible && !mTabSwitcher->getSelection().isEmpty() ) { + Int32 index = ( mTabSwitcher->getSelection().first().row() + 1 ) % + mTabSwitcher->getModel()->rowCount(); + mTabSwitcher->setSelection( mTabSwitcher->getModel()->index( index, 0 ) ); + } + + return; + } + + if ( getTabCount() <= 1 ) + return; + + switch ( mTabJumpMode ) { + case TabJumpMode::Linear: { + Int32 index = ( getTabSelectedIndex() + 1 ) % getTabCount(); + setTabSelected( eeclamp( index, 0, getTabCount() - 1 ) ); + break; + } + case TabJumpMode::Chronological: { + Int32 newTabIndex = (Int32)getTabSelectedFocusHistoryIndex() - 1; + Int32 index = newTabIndex < 0 ? mFocusHistory.size() - newTabIndex : newTabIndex; + setTabSelected( mFocusHistory[index] ); + break; + } + } +} + +void UITabWidget::focusPreviousTab( const std::vector& tabSwitcherMetaTrigger ) { + if ( mEnableTabSwitcher ) { + bool wasVisible = mTabSwitcher && mTabSwitcher->isVisible(); + + createTabSwitcher( tabSwitcherMetaTrigger, true ); + + if ( wasVisible && !mTabSwitcher->getSelection().isEmpty() ) { + Int32 newTabIndex = (Int32)mTabSwitcher->getSelection().first().row() - 1; + Int32 index = newTabIndex < 0 ? mTabSwitcher->getModel()->rowCount() - 1 : newTabIndex; + mTabSwitcher->setSelection( mTabSwitcher->getModel()->index( index, 0 ) ); + } + + return; + } + + if ( getTabCount() <= 1 ) + return; + + switch ( mTabJumpMode ) { + case TabJumpMode::Linear: { + Int32 newTabIndex = (Int32)getTabSelectedIndex() - 1; + Int32 index = newTabIndex < 0 ? getTabCount() - newTabIndex : newTabIndex; + setTabSelected( eeclamp( index, 0, getTabCount() - 1 ) ); + break; + } + case TabJumpMode::Chronological: { + Int32 index = ( getTabSelectedFocusHistoryIndex() + 1 ) % mFocusHistory.size(); + setTabSelected( mFocusHistory[index] ); + break; + } + } +} + +Uint32 UITabWidget::getTabSelectedFocusHistoryIndex() const { + auto it = std::find( mFocusHistory.begin(), mFocusHistory.end(), mTabSelected ); + return it != mFocusHistory.end() ? std::distance( mFocusHistory.begin(), it ) : 0; +} + }} // namespace EE::UI diff --git a/src/eepp/window/keycodes.cpp b/src/eepp/window/keycodes.cpp index f13532f96..ca76f9051 100644 --- a/src/eepp/window/keycodes.cpp +++ b/src/eepp/window/keycodes.cpp @@ -4,29 +4,29 @@ namespace EE { namespace Window { Uint32 KeyMod::defaultModifier = KEYMOD_DEFAULT_MODIFIER; -std::map MOD_MAP = { { "lshift", KEYMOD_SHIFT }, - { "rshift", KEYMOD_SHIFT }, - { "left shift", KEYMOD_SHIFT }, - { "right shift", KEYMOD_SHIFT }, - { "shift", KEYMOD_SHIFT }, - { "lctrl", KEYMOD_CTRL }, - { "rctrl", KEYMOD_CTRL }, - { "left ctrl", KEYMOD_CTRL }, - { "right ctrl", KEYMOD_CTRL }, - { "ctrl", KEYMOD_CTRL }, - { "lalt", KEYMOD_ALT }, - { "ralt", KEYMOD_RALT }, - { "left alt", KEYMOD_LALT }, - { "right alt", KEYMOD_RALT }, - { "altgr", KEYMOD_RALT }, - { "alt", KEYMOD_LALT }, - { "lmeta", KEYMOD_META }, - { "lmeta", KEYMOD_META }, - { "left meta", KEYMOD_META }, - { "right meta", KEYMOD_META }, - { "meta", KEYMOD_META }, - { "mod", KeyMod::getDefaultModifier() }, - { "modifier", KeyMod::getDefaultModifier() } }; +static std::map MOD_MAP = { { "lshift", KEYMOD_SHIFT }, + { "rshift", KEYMOD_SHIFT }, + { "left shift", KEYMOD_SHIFT }, + { "right shift", KEYMOD_SHIFT }, + { "shift", KEYMOD_SHIFT }, + { "lctrl", KEYMOD_CTRL }, + { "rctrl", KEYMOD_CTRL }, + { "left ctrl", KEYMOD_CTRL }, + { "right ctrl", KEYMOD_CTRL }, + { "ctrl", KEYMOD_CTRL }, + { "lalt", KEYMOD_ALT }, + { "ralt", KEYMOD_RALT }, + { "left alt", KEYMOD_LALT }, + { "right alt", KEYMOD_RALT }, + { "altgr", KEYMOD_RALT }, + { "alt", KEYMOD_LALT }, + { "lmeta", KEYMOD_META }, + { "lmeta", KEYMOD_META }, + { "left meta", KEYMOD_META }, + { "right meta", KEYMOD_META }, + { "meta", KEYMOD_META }, + { "mod", KeyMod::getDefaultModifier() }, + { "modifier", KeyMod::getDefaultModifier() } }; Uint32 KeyMod::getDefaultModifier() { return defaultModifier; @@ -66,4 +66,33 @@ const std::map& KeyMod::getModMap() { return MOD_MAP; } +std::vector KeyMod::getKeyCodesFromModifier( Uint32 mod ) { + std::vector codes; + if ( mod & KEYMOD_LSHIFT ) { + codes.push_back( KEY_LSHIFT ); + } + if ( mod & KEYMOD_RSHIFT ) { + codes.push_back( KEY_RSHIFT ); + } + if ( mod & KEYMOD_LCTRL ) { + codes.push_back( KEY_LCTRL ); + } + if ( mod & KEYMOD_RCTRL ) { + codes.push_back( KEY_RCTRL ); + } + if ( mod & KEYMOD_LALT ) { + codes.push_back( KEY_LALT ); + } + if ( mod & KEYMOD_RALT ) { + codes.push_back( KEY_RALT ); + } + if ( mod & KEYMOD_LMETA ) { + codes.push_back( KEY_LGUI ); + } + if ( mod & KEYMOD_RMETA ) { + codes.push_back( KEY_RGUI ); + } + return codes; +} + }} // namespace EE::Window diff --git a/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/typescript.cpp b/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/typescript.cpp index 04dc2217d..f8f0b3ace 100644 --- a/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/typescript.cpp +++ b/src/modules/languages-syntax-highlighting/src/eepp/ui/doc/languages/typescript.cpp @@ -23,7 +23,8 @@ void addTypeScript() { { { "[%+%-=/%*%^%%<>!~|&]" }, "operator" }, { { "(interface%s)([%a_][%w_]*)" }, { "normal", "keyword", "keyword2" } }, { { "(type%s)([%a_][%w_]*)" }, { "normal", "keyword", "keyword2" } }, - { { "(if|for|while|switch|catch|with)\\s*(?=\\()" }, + { { "(if|for|while|switch|catch|with|typeof|delete|void|super|await|function)\\s*(?=" + "\\()" }, { "normal", "keyword", "keyword" }, "", SyntaxPatternMatchType::RegEx }, diff --git a/src/thirdparty/efsw b/src/thirdparty/efsw index ad4ac9056..f4ec91017 160000 --- a/src/thirdparty/efsw +++ b/src/thirdparty/efsw @@ -1 +1 @@ -Subproject commit ad4ac9056402940bd41df38597d6046674b1c4a1 +Subproject commit f4ec910175a2abcd8aaa193e7643739a1efad321 diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index 9f1fd97b8..bf640d0c4 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -160,6 +160,11 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath, editor.minimap = ini.getValueB( "editor", "minimap", true ); editor.showDocInfo = ini.getValueB( "editor", "show_doc_info", true ); editor.hideTabBarOnSingleTab = ini.getValueB( "editor", "hide_tab_bar_on_single_tab", false ); + editor.hideTabBar = ini.getValueB( "editor", "hide_tab_bar", false ); + editor.tabSwitcher = ini.getValueB( "editor", "tab_switcher", false ); + editor.tabJumpMode = + UITabWidget::tabJumpModefromString( ini.getValue( "editor", "tab_jump_mode", "linear" ) ); + editor.singleClickNavigation = ini.getValueB( "editor", "single_click_tree_navigation", false ); editor.syncProjectTreeWithEditor = ini.getValueB( "editor", "sync_project_tree_with_editor", true ); @@ -316,6 +321,10 @@ void AppConfig::save( const std::vector& recentFiles, ini.setValueB( "editor", "minimap", editor.minimap ); ini.setValueB( "editor", "show_doc_info", editor.showDocInfo ); ini.setValueB( "editor", "hide_tab_bar_on_single_tab", editor.hideTabBarOnSingleTab ); + ini.setValueB( "editor", "hide_tab_bar", editor.hideTabBar ); + ini.setValueB( "editor", "tab_switcher", editor.tabSwitcher ); + ini.setValue( "editor", "tab_jump_mode", + UITabWidget::tabJumpModeToString( editor.tabJumpMode ) ); ini.setValueB( "editor", "single_click_tree_navigation", editor.singleClickNavigation ); ini.setValueB( "editor", "sync_project_tree_with_editor", editor.syncProjectTreeWithEditor ); ini.setValueB( "editor", "auto_close_xml_tags", editor.autoCloseXMLTags ); diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index 692dbd3b5..0dc78ab50 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -74,6 +74,10 @@ struct CodeEditorConfig { bool minimap{ true }; bool showDocInfo{ true }; bool hideTabBarOnSingleTab{ true }; + bool hideTabBar{ false }; + bool tabSwitcher{ false }; + UITabWidget::TabJumpMode tabJumpMode{ UITabWidget::TabJumpMode::Linear }; + bool singleClickNavigation{ false }; bool syncProjectTreeWithEditor{ true }; bool autoCloseXMLTags{ true }; diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 963c0a2a2..015a1ab69 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -3892,6 +3892,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe mSplitter = UICodeEditorSplitter::New( this, mUISceneNode, mThreadPool, mColorSchemes, mInitColorScheme ); mSplitter->setHideTabBarOnSingleTab( mConfig.editor.hideTabBarOnSingleTab ); + mSplitter->setHideTabBar( mConfig.editor.hideTabBar ); mSplitter->setOnTabWidgetCreateCb( [this]( UITabWidget* tabWidget ) { tabWidget->getTabBar()->onDoubleClick( [this]( const MouseEvent* ) { mSplitter->createEditorInNewTab(); } ); diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 387e8711e..d5d75d603 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -354,7 +354,53 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { } } ); t.setCommand( "create-new-welcome-tab", [this] { createWelcomeTab(); } ); + mSplitter->registerSplitterCommands( t ); + + // Overwrite it + t.setCommand( "next-tab", [this] { + UITabWidget* tabWidget = + getSplitter()->tabWidgetFromWidget( getSplitter()->getCurWidget() ); + if ( tabWidget ) { + tabWidget->setTabJumpMode( mConfig.editor.tabJumpMode ); + tabWidget->setEnableTabSwitcher( mConfig.editor.tabSwitcher ); + std::vector triggerCodes; + auto kb = getKeybind( "next-tab" ); + if ( !kb.empty() ) { + auto shortcut = + KeyBindings::toShortcut( mWindow->getInput(), getKeybind( "next-tab" ) ); + if ( !shortcut.empty() ) { + if ( shortcut.mod & KeyMod::getDefaultModifier() ) { + triggerCodes = + KeyMod::getKeyCodesFromModifier( KeyMod::getDefaultModifier() ); + } + } + } + tabWidget->focusNextTab( triggerCodes ); + } + } ); + + t.setCommand( "previous-tab", [this] { + UITabWidget* tabWidget = + getSplitter()->tabWidgetFromWidget( getSplitter()->getCurWidget() ); + if ( tabWidget ) { + tabWidget->setTabJumpMode( mConfig.editor.tabJumpMode ); + tabWidget->setEnableTabSwitcher( mConfig.editor.tabSwitcher ); + std::vector triggerCodes; + auto kb = getKeybind( "next-tab" ); + if ( !kb.empty() ) { + auto shortcut = + KeyBindings::toShortcut( mWindow->getInput(), getKeybind( "next-tab" ) ); + if ( !shortcut.empty() ) { + if ( shortcut.mod & KeyMod::getDefaultModifier() ) { + triggerCodes = + KeyMod::getKeyCodesFromModifier( KeyMod::getDefaultModifier() ); + } + } + } + tabWidget->focusPreviousTab( triggerCodes ); + } + } ); } PluginManager* getPluginManager() const; diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index a17d30a20..f5cebdbee 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -1492,6 +1492,116 @@ UIMenu* SettingsMenu::createViewMenu() { mViewMenu->addSeparator(); + mTabBarMenu = UIPopUpMenu::New(); + + const auto tabBarMenuRefresh = [this] { + const auto& cfg = mApp->getConfig(); + + mTabBarMenu->getItemId( "hide-tabbar" ) + ->asType() + ->setActive( cfg.editor.hideTabBar ); + + mTabBarMenu->getItemId( "hide-tabbar-on-single-tab" ) + ->asType() + ->setActive( cfg.editor.hideTabBarOnSingleTab ); + + mTabBarMenu->getItemId( "tab-switcher" ) + ->asType() + ->setActive( cfg.editor.tabSwitcher ); + + auto tjmi = mTabBarMenu->getItemId( "tab_jump_mode" ); + UIPopUpMenu* tabJumpModeMenu = + tjmi ? tjmi->asType()->getSubMenu()->asType() : nullptr; + if ( tabJumpModeMenu ) { + tabJumpModeMenu->getItemId( "tab_jump_linear" ) + ->asType() + ->setActive( cfg.editor.tabJumpMode == UITabWidget::TabJumpMode::Linear ); + tabJumpModeMenu->getItemId( "tab_jump_chronological" ) + ->asType() + ->setActive( cfg.editor.tabJumpMode == UITabWidget::TabJumpMode::Chronological ); + } + }; + + mViewMenu->addSubMenu( i18n( "tab_bar", "Tab Bar" ), findIcon( "tabbar" ), mTabBarMenu ) + ->on( Event::OnMenuShow, [this, tabBarMenuRefresh]( auto ) { + if ( mTabBarMenu->getCount() == 0 ) { + mTabBarMenu->addCheckBox( i18n( "hide_tabbar", "Hide Tab Bar" ) ) + ->setActive( mApp->getConfig().editor.hideTabBarOnSingleTab ) + ->setTooltipText( i18n( "hide_tabbar_tooltip", "Always Hide the Tab Bar." ) ) + ->setId( "hide-tabbar" ); + + mTabBarMenu + ->addCheckBox( + i18n( "hide_tabbar_on_single_tab", "Hide Tab Bar on single tab" ) ) + ->setActive( mApp->getConfig().editor.hideTabBarOnSingleTab ) + ->setTooltipText( + i18n( "hide_tabbar_on_single_tab_tooltip", + "Hides the tabbar if there's only one element in the tab widget." ) ) + ->setId( "hide-tabbar-on-single-tab" ); + + mTabBarMenu->addCheckBox( i18n( "display_tab_switcher", "Display Tab Switcher" ) ) + ->setActive( mApp->getConfig().editor.tabSwitcher ) + ->setTooltipText( + i18n( "display_tab_switcher_tooltip", + "Displays a tab switcher at the center of the tab widget." ) ) + ->setId( "tab-switcher" ); + + auto tabJumpModeMenu = UIPopUpMenu::New(); + tabJumpModeMenu->setId( "tab-jump-mode" ); + tabJumpModeMenu->addRadioButton( i18n( "linear", "Linear" ) ) + ->setTooltipText( i18n( + "jump_mode_linear_tooltip", + "Linear Jump Mode will switch tabs in the order they are displayed" ) ) + ->setId( "tab_jump_linear" ); + tabJumpModeMenu->addRadioButton( i18n( "chronological", "Chronological" ) ) + ->setTooltipText( i18n( "jump_mode_chronological_tooltip", + "Chronological Jump Mode will switch tabs in the last " + "focused / visited order." ) ) + ->setId( "tab_jump_chronological" ); + + mTabBarMenu + ->addSubMenu( i18n( "tab_jump_mode", "Tab Jump Mode" ), nullptr, + tabJumpModeMenu ) + ->setId( "tab_jump_mode" ); + + tabJumpModeMenu->on( Event::OnItemClicked, [this]( const Event* event ) { + if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) + return; + UIMenuItem* item = event->getNode()->asType(); + mApp->getConfig().editor.tabJumpMode = + "tab_jump_linear" != item->getId() ? UITabWidget::TabJumpMode::Chronological + : UITabWidget::TabJumpMode::Linear; + } ); + + mTabBarMenu->on( + Event::OnItemClicked, [tabBarMenuRefresh, this]( const Event* event ) { + if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) + return; + UIMenuItem* item = event->getNode()->asType(); + if ( "hide-tabbar" == item->getId() ) { + mApp->getConfig().editor.hideTabBar = + item->asType()->isActive(); + mSplitter->setHideTabBar( mApp->getConfig().editor.hideTabBar ); + if ( mApp->getConfig().editor.hideTabBar ) + mApp->getConfig().editor.tabSwitcher = true; + tabBarMenuRefresh(); + } else if ( "hide-tabbar-on-single-tab" == item->getId() ) { + mApp->getConfig().editor.hideTabBarOnSingleTab = + item->asType()->isActive(); + mSplitter->setHideTabBarOnSingleTab( + mApp->getConfig().editor.hideTabBarOnSingleTab ); + } else if ( "tab-switcher" == item->getId() ) { + mApp->getConfig().editor.tabSwitcher = + item->asType()->isActive(); + } + } ); + } + + tabBarMenuRefresh(); + } ); + + mViewMenu->addSeparator(); + mViewMenu->addCheckBox( i18n( "show_line_numbers", "Show Line Numbers" ) ) ->setActive( mApp->getConfig().editor.showLineNumbers ) ->setId( "show-line-numbers" ); diff --git a/src/tools/ecode/settingsmenu.hpp b/src/tools/ecode/settingsmenu.hpp index 57c00bea1..2853cc73a 100644 --- a/src/tools/ecode/settingsmenu.hpp +++ b/src/tools/ecode/settingsmenu.hpp @@ -113,6 +113,7 @@ class SettingsMenu { UIPopUpMenu* mHelpMenu{ nullptr }; UIPopUpMenu* mLineWrapMenu{ nullptr }; UIPopUpMenu* mCodeFoldingMenu{ nullptr }; + UIPopUpMenu* mTabBarMenu{ nullptr }; UIMenuBar* mMenuBar{ nullptr }; std::vector mFileTypeMenues; Float mFileTypeMenuesCreatedWithHeight{ 0 }; diff --git a/src/tools/ecode/statusterminalcontroller.cpp b/src/tools/ecode/statusterminalcontroller.cpp index 7f2991c6f..c2d4eb4e9 100644 --- a/src/tools/ecode/statusterminalcontroller.cpp +++ b/src/tools/ecode/statusterminalcontroller.cpp @@ -73,8 +73,8 @@ UITerminal* StatusTerminalController::createTerminal( const std::string& working term->setExclusiveMode( !term->getExclusiveMode() ); mApp->updateTerminalMenu(); } ); - mApp->registerUnlockedCommands( *term ); mContext->getSplitter()->registerSplitterCommands( *term ); + mApp->registerUnlockedCommands( *term ); term->setFocus(); term->setId( "terminal" ); return term; diff --git a/src/tools/ecode/terminalmanager.cpp b/src/tools/ecode/terminalmanager.cpp index e5ea27919..42e88cccc 100644 --- a/src/tools/ecode/terminalmanager.cpp +++ b/src/tools/ecode/terminalmanager.cpp @@ -555,7 +555,6 @@ UITerminal* TerminalManager::createNewTerminal( const std::string& title, UITabW mApp->updateTerminalMenu(); } ); mApp->registerUnlockedCommands( *term ); - mApp->getSplitter()->registerSplitterCommands( *term ); term->setFocus(); return term; #endif