From a0d2f73864644f44eba0889d435c771dbb5d8b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 4 Jan 2021 01:46:48 -0300 Subject: [PATCH] ecode code refactor. --- include/eepp/math/size.hpp | 14 +- include/eepp/math/vector2.hpp | 10 + .../eepp/ui/abstract/uiabstracttableview.hpp | 9 + include/eepp/ui/uitreeview.hpp | 2 + projects/linux/ee.files | 7 + src/eepp/ui/abstract/uiabstracttableview.cpp | 35 +- src/eepp/ui/uitreeview.cpp | 26 +- src/tools/codeeditor/appconfig.cpp | 2 + src/tools/codeeditor/appconfig.hpp | 1 + src/tools/codeeditor/codeeditor.cpp | 890 ++---------------- src/tools/codeeditor/codeeditor.hpp | 171 +--- src/tools/codeeditor/docsearchcontroller.cpp | 279 ++++++ src/tools/codeeditor/docsearchcontroller.hpp | 55 ++ src/tools/codeeditor/filelocator.cpp | 147 +++ src/tools/codeeditor/filelocator.hpp | 34 + .../codeeditor/globalsearchcontroller.cpp | 434 +++++++++ .../codeeditor/globalsearchcontroller.hpp | 56 ++ .../codeeditor/widgetcommandexecuter.hpp | 78 ++ 18 files changed, 1244 insertions(+), 1006 deletions(-) create mode 100644 src/tools/codeeditor/docsearchcontroller.cpp create mode 100644 src/tools/codeeditor/docsearchcontroller.hpp create mode 100644 src/tools/codeeditor/filelocator.cpp create mode 100644 src/tools/codeeditor/filelocator.hpp create mode 100644 src/tools/codeeditor/globalsearchcontroller.cpp create mode 100644 src/tools/codeeditor/globalsearchcontroller.hpp create mode 100644 src/tools/codeeditor/widgetcommandexecuter.hpp diff --git a/include/eepp/math/size.hpp b/include/eepp/math/size.hpp index cdac28f58..d1aefff9c 100644 --- a/include/eepp/math/size.hpp +++ b/include/eepp/math/size.hpp @@ -31,17 +31,19 @@ template class tSize : public Vector2 { /** Set a new height */ void setHeight( const T& height ); + + tSize& operator=( const tSize& right ); }; template tSize::tSize() : Vector2( 0, 0 ) {} template -tSize::tSize( const T& Width, const T& Height ) : Vector2( Width, Height ) {} +tSize::tSize( const T& width, const T& height ) : Vector2( width, height ) {} template -tSize::tSize( const tSize& Size ) : Vector2( Size.getWidth(), Size.getHeight() ) {} +tSize::tSize( const tSize& size ) : Vector2( size.getWidth(), size.getHeight() ) {} -template tSize::tSize( const Vector2& Vec ) : Vector2( Vec.x, Vec.y ) {} +template tSize::tSize( const Vector2& vec ) : Vector2( vec.x, vec.y ) {} template const T& tSize::getWidth() const { return this->x; @@ -59,6 +61,12 @@ template void tSize::setHeight( const T& height ) { this->y = height; } +template tSize& tSize::operator=( const tSize& right ) { + this->x = right.x; + this->y = right.y; + return *this; +} + typedef tSize Sizei; typedef tSize Sizef; diff --git a/include/eepp/math/vector2.hpp b/include/eepp/math/vector2.hpp index 0291e2705..21a57e28f 100644 --- a/include/eepp/math/vector2.hpp +++ b/include/eepp/math/vector2.hpp @@ -19,6 +19,8 @@ template class Vector2 { /** Creates a vector from its coordinates */ Vector2( T X, T Y ); + Vector2( const Vector2& copy ) : x( copy.x ), y( copy.y ) {} + /** @return A copy of the Vector2 */ Vector2 copy(); @@ -102,6 +104,8 @@ template class Vector2 { Vector2 asInt() const; + Vector2& operator=( const Vector2& right ); + T x; T y; @@ -394,6 +398,12 @@ template Vector2 Vector2::asInt() const { return Vector2( x, y ); } +template Vector2& Vector2::operator=( const Vector2& right ) { + this->x = right.x; + this->y = right.y; + return *this; +} + template bool Vector2::nearDist( const Vector2& Vec, T Dist ) const { return 0 != ( distanceSq( Vec ) < Dist * Dist ); } diff --git a/include/eepp/ui/abstract/uiabstracttableview.hpp b/include/eepp/ui/abstract/uiabstracttableview.hpp index ce0f67147..4ba08a6e1 100644 --- a/include/eepp/ui/abstract/uiabstracttableview.hpp +++ b/include/eepp/ui/abstract/uiabstracttableview.hpp @@ -6,6 +6,7 @@ #include #include #include +#include using namespace EE::Math; @@ -99,6 +100,10 @@ class EE_API UIAbstractTableView : public UIAbstractView { * possible. */ void setMainColumn( const size_t& mainColumn ); + bool getSingleClickNavigation() const; + + void setSingleClickNavigation( bool singleClickNavigation ); + protected: friend class EE::UI::UITableHeaderColumn; @@ -122,9 +127,11 @@ class EE_API UIAbstractTableView : public UIAbstractView { bool mAutoExpandOnSingleColumn{ false }; bool mAutoColumnsWidth{ false }; bool mRowSearchByName{ true }; + bool mSingleClickNavigation{ false }; Action* mSearchTextAction{ nullptr }; std::string mSearchText; size_t mMainColumn{ 0 }; + std::unordered_map mWidgetsClickCbId; virtual ~UIAbstractTableView(); @@ -164,6 +171,8 @@ class EE_API UIAbstractTableView : public UIAbstractView { virtual Uint32 onTextInput( const TextInputEvent& event ); + virtual void bindNavigationClick( UIWidget* widget ); + void updateHeaderSize(); int visibleColumn(); diff --git a/include/eepp/ui/uitreeview.hpp b/include/eepp/ui/uitreeview.hpp index e19fac6a4..cb6784dba 100644 --- a/include/eepp/ui/uitreeview.hpp +++ b/include/eepp/ui/uitreeview.hpp @@ -190,6 +190,8 @@ class EE_API UITreeView : public UIAbstractTableView { const ModelIndex& index ); virtual void onModelSelectionChange(); + + virtual void bindNavigationClick( UIWidget* widget ); }; }} // namespace EE::UI diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 816ecc084..6dc4231b1 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -1097,8 +1097,14 @@ ../../src/tools/codeeditor/autocompletemodule.hpp ../../src/tools/codeeditor/codeeditor.cpp ../../src/tools/codeeditor/codeeditor.hpp +../../src/tools/codeeditor/docsearchcontroller.cpp +../../src/tools/codeeditor/docsearchcontroller.hpp +../../src/tools/codeeditor/filelocator.cpp +../../src/tools/codeeditor/filelocator.hpp ../../src/tools/codeeditor/filesystemlistener.cpp ../../src/tools/codeeditor/filesystemlistener.hpp +../../src/tools/codeeditor/globalsearchcontroller.cpp +../../src/tools/codeeditor/globalsearchcontroller.hpp ../../src/tools/codeeditor/ignorematcher.cpp ../../src/tools/codeeditor/ignorematcher.hpp ../../src/tools/codeeditor/lintermodule.cpp @@ -1111,6 +1117,7 @@ ../../src/tools/codeeditor/uicodeeditorsplitter.hpp ../../src/tools/codeeditor/uitreeviewglobalsearch.cpp ../../src/tools/codeeditor/uitreeviewglobalsearch.hpp +../../src/tools/codeeditor/widgetcommandexecuter.hpp ../../src/tools/mapeditor/mapeditor.cpp ../../src/tools/textureatlaseditor/textureatlaseditor.cpp ../../src/tools/texturepacker/texturepacker.cpp diff --git a/src/eepp/ui/abstract/uiabstracttableview.cpp b/src/eepp/ui/abstract/uiabstracttableview.cpp index 04c7e1e7d..a6931ff75 100644 --- a/src/eepp/ui/abstract/uiabstracttableview.cpp +++ b/src/eepp/ui/abstract/uiabstracttableview.cpp @@ -374,6 +374,18 @@ void UIAbstractTableView::onScrollChange() { mHeader->setPixelsPosition( -mScrollOffset.x, 0 ); } +void UIAbstractTableView::bindNavigationClick( UIWidget* widget ) { + mWidgetsClickCbId[widget] = widget->addEventListener( + mSingleClickNavigation ? Event::MouseClick : Event::MouseDoubleClick, + [&]( const Event* event ) { + auto mouseEvent = static_cast( event ); + auto idx = mouseEvent->getNode()->getParent()->asType()->getCurIndex(); + if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) { + onOpenModelIndex( idx, event ); + } + } ); +} + UIWidget* UIAbstractTableView::createCell( UIWidget* rowWidget, const ModelIndex& index ) { UITableCell* widget = UITableCell::New( mTag + "::cell" ); widget->setParent( rowWidget ); @@ -382,13 +394,7 @@ UIWidget* UIAbstractTableView::createCell( UIWidget* rowWidget, const ModelIndex widget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); widget->setTextAlign( UI_HALIGN_LEFT ); widget->setCurIndex( index ); - widget->addEventListener( Event::MouseDoubleClick, [&]( const Event* event ) { - auto mouseEvent = static_cast( event ); - auto idx = mouseEvent->getNode()->getParent()->asType()->getCurIndex(); - if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) { - onOpenModelIndex( idx, event ); - } - } ); + bindNavigationClick( widget ); return widget; } @@ -589,4 +595,19 @@ void UIAbstractTableView::setMainColumn( const size_t& mainColumn ) { mMainColumn = mainColumn; } +bool UIAbstractTableView::getSingleClickNavigation() const { + return mSingleClickNavigation; +} + +void UIAbstractTableView::setSingleClickNavigation( bool singleClickNavigation ) { + if ( singleClickNavigation != mSingleClickNavigation ) { + mSingleClickNavigation = singleClickNavigation; + // Rebind the clicks + for ( const auto& widgetIt : mWidgetsClickCbId ) { + widgetIt.first->removeEventListener( widgetIt.second ); + bindNavigationClick( widgetIt.first ); + } + } +} + }}} // namespace EE::UI::Abstract diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index 196fee617..aaffa9ace 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -109,16 +109,10 @@ void UITreeView::updateContentSize() { onContentSizeChange(); } -UIWidget* UITreeView::setupCell( UITableCell* widget, UIWidget* rowWidget, - const ModelIndex& index ) { - widget->setParent( rowWidget ); - widget->unsetFlags( UI_AUTO_SIZE ); - widget->clipEnable(); - widget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); - widget->setTextAlign( UI_HALIGN_LEFT ); - widget->setCurIndex( index ); - if ( index.column() == (Int64)getModel()->treeColumn() ) { - widget->addEventListener( Event::MouseDoubleClick, [&]( const Event* event ) { +void UITreeView::bindNavigationClick( UIWidget* widget ) { + mWidgetsClickCbId[widget] = widget->addEventListener( + mSingleClickNavigation ? Event::MouseClick : Event::MouseDoubleClick, + [&]( const Event* event ) { auto mouseEvent = static_cast( event ); auto idx = mouseEvent->getNode()->getParent()->asType()->getCurIndex(); if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) { @@ -132,6 +126,18 @@ UIWidget* UITreeView::setupCell( UITableCell* widget, UIWidget* rowWidget, } } } ); +} + +UIWidget* UITreeView::setupCell( UITableCell* widget, UIWidget* rowWidget, + const ModelIndex& index ) { + widget->setParent( rowWidget ); + widget->unsetFlags( UI_AUTO_SIZE ); + widget->clipEnable(); + widget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + widget->setTextAlign( UI_HALIGN_LEFT ); + widget->setCurIndex( index ); + if ( index.column() == (Int64)getModel()->treeColumn() ) { + bindNavigationClick( widget ); widget->addEventListener( Event::MouseClick, [&]( const Event* event ) { auto mouseEvent = static_cast( event ); UIWidget* icon = mouseEvent->getNode()->asType()->getExtraInnerWidget(); diff --git a/src/tools/codeeditor/appconfig.cpp b/src/tools/codeeditor/appconfig.cpp index d557e5cb7..325819788 100644 --- a/src/tools/codeeditor/appconfig.cpp +++ b/src/tools/codeeditor/appconfig.cpp @@ -72,6 +72,7 @@ void AppConfig::load( std::string& confPath, std::string& keybindingsPath, editor.linter = ini.getValueB( "editor", "linter", true ); editor.showDocInfo = ini.getValueB( "editor", "show_doc_info", true ); editor.hideTabBarOnSingleTab = ini.getValueB( "editor", "hide_tab_bar_on_single_tab", true ); + editor.singleClickTreeNavigation = ini.getValueB( "editor", "single_click_tree_navigation", false ); } void AppConfig::save( const std::vector& recentFiles, @@ -117,6 +118,7 @@ void AppConfig::save( const std::vector& recentFiles, ini.setValueB( "editor", "linter", editor.linter ); ini.setValueB( "editor", "show_doc_info", editor.showDocInfo ); ini.setValueB( "editor", "hide_tab_bar_on_single_tab", editor.hideTabBarOnSingleTab ); + ini.setValueB( "editor", "single_click_tree_navigation", editor.singleClickTreeNavigation ); ini.writeFile(); iniState.writeFile(); } diff --git a/src/tools/codeeditor/appconfig.hpp b/src/tools/codeeditor/appconfig.hpp index 5f6f79553..0dd058c3c 100644 --- a/src/tools/codeeditor/appconfig.hpp +++ b/src/tools/codeeditor/appconfig.hpp @@ -52,6 +52,7 @@ struct CodeEditorConfig { bool showDocInfo{ true }; bool linter{ true }; bool hideTabBarOnSingleTab{ true }; + bool singleClickTreeNavigation{ false }; std::string autoCloseBrackets{ "" }; int indentWidth{ 4 }; int tabWidth{ 4 }; diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index af8963f2b..f937bcbe1 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -253,112 +253,6 @@ UIFileDialog* App::saveFileDialog( UICodeEditor* editor, bool focusOnClose ) { return dialog; } -bool App::findPrevText( SearchState& search ) { - if ( search.text.empty() ) - search.text = mLastSearch; - if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) || search.text.empty() ) - return false; - - search.editor->getDocument().setActiveClient( search.editor ); - mLastSearch = search.text; - TextDocument& doc = search.editor->getDocument(); - TextRange range = doc.getDocRange(); - TextPosition from = doc.getSelection( true ).start(); - if ( search.range.isValid() ) { - range = doc.sanitizeRange( search.range ).normalized(); - from = from < range.start() ? range.start() : from; - } - - TextPosition found = - doc.findLast( search.text, from, search.caseSensitive, search.wholeWord, search.range ); - if ( found.isValid() ) { - doc.setSelection( { doc.positionOffset( found, search.text.size() ), found } ); - return true; - } else { - found = doc.findLast( search.text, range.end() ); - if ( found.isValid() ) { - doc.setSelection( { doc.positionOffset( found, search.text.size() ), found } ); - return true; - } - } - return false; -} - -bool App::findNextText( SearchState& search ) { - if ( search.text.empty() ) - search.text = mLastSearch; - if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) || search.text.empty() ) - return false; - - search.editor->getDocument().setActiveClient( search.editor ); - mLastSearch = search.text; - TextDocument& doc = search.editor->getDocument(); - TextRange range = doc.getDocRange(); - TextPosition from = doc.getSelection( true ).end(); - if ( search.range.isValid() ) { - range = doc.sanitizeRange( search.range ).normalized(); - from = from < range.start() ? range.start() : from; - } - - TextRange found = - doc.find( search.text, from, search.caseSensitive, search.wholeWord, search.type, range ); - if ( found.isValid() ) { - doc.setSelection( found.reversed() ); - return true; - } else { - found = doc.find( search.text, range.start(), search.caseSensitive, search.wholeWord, - search.type, range ); - if ( found.isValid() ) { - doc.setSelection( found.reversed() ); - return true; - } - } - return false; -} - -bool App::replaceSelection( SearchState& search, const String& replacement ) { - if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) || - !search.editor->getDocument().hasSelection() ) - return false; - search.editor->getDocument().setActiveClient( search.editor ); - search.editor->getDocument().replaceSelection( replacement ); - return true; -} - -int App::replaceAll( SearchState& search, const String& replace ) { - if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) ) - return 0; - if ( search.text.empty() ) - search.text = mLastSearch; - if ( search.text.empty() ) - return 0; - search.editor->getDocument().setActiveClient( search.editor ); - mLastSearch = search.text; - TextDocument& doc = search.editor->getDocument(); - TextPosition startedPosition = doc.getSelection().start(); - int count = doc.replaceAll( search.text, replace, search.caseSensitive, search.wholeWord, - search.type, search.range ); - doc.setSelection( startedPosition ); - return count; -} - -bool App::findAndReplace( SearchState& search, const String& replace ) { - if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) ) - return false; - if ( search.text.empty() ) - search.text = mLastSearch; - if ( search.text.empty() ) - return false; - search.editor->getDocument().setActiveClient( search.editor ); - mLastSearch = search.text; - TextDocument& doc = search.editor->getDocument(); - if ( doc.hasSelection() && doc.getSelectedText() == search.text ) { - return replaceSelection( search, replace ); - } else { - return findNextText( search ); - } -} - void App::runCommand( const std::string& command ) { if ( mEditorSplitter->getCurEditor() ) mEditorSplitter->getCurEditor()->getDocument().execute( command ); @@ -399,33 +293,12 @@ std::string App::getKeybind( const std::string& command ) { return ""; } -static int LOCATEBAR_MAX_VISIBLE_ITEMS = 18; -static int LOCATEBAR_MAX_RESULTS = 100; - -void App::hideLocateBar() { - mLocateBarLayout->setVisible( false ); - mLocateTable->setVisible( false ); +ProjectDirectoryTree* App::getDirTree() const { + return mDirTree ? mDirTree.get() : nullptr; } -void App::updateLocateTable() { - if ( !mLocateInput->getText().empty() ) { -#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) - mDirTree->asyncFuzzyMatchTree( - mLocateInput->getText(), LOCATEBAR_MAX_RESULTS, [&]( auto res ) { - mUISceneNode->runOnMainThread( [&, res] { - mLocateTable->setModel( res ); - mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); - } ); - } ); -#else - mLocateTable->setModel( - mDirTree->fuzzyMatchTree( mLocateInput->getText(), LOCATEBAR_MAX_RESULTS ) ); - mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); -#endif - } else { - mLocateTable->setModel( mDirTree->asModel( LOCATEBAR_MAX_RESULTS ) ); - mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); - } +std::shared_ptr App::getThreadPool() const { + return mThreadPool; } bool App::trySendUnlockedCmd( const KeyEvent& keyEvent ) { @@ -440,677 +313,6 @@ bool App::trySendUnlockedCmd( const KeyEvent& keyEvent ) { return false; } -void App::goToLine() { - showLocateBar(); - mLocateInput->setText( "l " ); -} - -void App::initLocateBar() { - auto addClickListener = [&]( UIWidget* widget, std::string cmd ) { - widget->addEventListener( Event::MouseClick, [this, cmd]( const Event* event ) { - const MouseEvent* mouseEvent = static_cast( event ); - if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) - mLocateBarLayout->execute( cmd ); - } ); - }; - mLocateTable = UITableView::New(); - mLocateTable->setId( "locate_bar_table" ); - mLocateTable->setParent( mUISceneNode->getRoot() ); - mLocateTable->setHeadersVisible( false ); - mLocateTable->setVisible( false ); - mLocateInput->addEventListener( Event::OnTextChanged, [&]( const Event* ) { - if ( mEditorSplitter->getCurEditor() && - String::startsWith( mLocateInput->getText(), String( "l " ) ) ) { - String number( mLocateInput->getText().substr( 2 ) ); - Int64 val; - if ( String::fromString( val, number ) && val - 1 >= 0 ) { - mEditorSplitter->getCurEditor()->goToLine( { val - 1, 0 } ); - mLocateTable->setVisible( false ); - } - } else { - mLocateTable->setVisible( true ); - Vector2f pos( mLocateInput->convertToWorldSpace( { 0, 0 } ) ); - pos.y -= mLocateTable->getPixelsSize().getHeight(); - mLocateTable->setPixelsPosition( pos ); - if ( !mDirTreeReady ) - return; - updateLocateTable(); - } - } ); - mLocateInput->addEventListener( Event::OnPressEnter, [&]( const Event* ) { - KeyEvent keyEvent( mLocateTable, Event::KeyDown, KEY_RETURN, 0, 0 ); - mLocateTable->forceKeyDown( keyEvent ); - } ); - mLocateInput->addEventListener( Event::KeyDown, [&]( const Event* event ) { - const KeyEvent* keyEvent = static_cast( event ); - mLocateTable->forceKeyDown( *keyEvent ); - } ); - mLocateBarLayout->addCommand( "close-locatebar", [&] { - hideLocateBar(); - mEditorSplitter->getCurEditor()->setFocus(); - } ); - mLocateBarLayout->getKeyBindings().addKeybindsString( { - { "escape", "close-locatebar" }, - } ); - mLocateTable->addEventListener( Event::KeyDown, [&]( const Event* event ) { - const KeyEvent* keyEvent = static_cast( event ); - if ( keyEvent->getKeyCode() == KEY_ESCAPE ) - mLocateBarLayout->execute( "close-locatebar" ); - } ); - addClickListener( mLocateBarLayout->find( "locatebar_close" ), "close-locatebar" ); - mLocateTable->addEventListener( Event::OnModelEvent, [&]( const Event* event ) { - const ModelEvent* modelEvent = static_cast( event ); - if ( modelEvent->getModelEventType() == ModelEventType::Open ) { - Variant vPath( modelEvent->getModel()->data( - modelEvent->getModel()->index( modelEvent->getModelIndex().row(), 1 ), - Model::Role::Display ) ); - if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { - std::string path( vPath.asCStr() ); - UITab* tab = mEditorSplitter->isDocumentOpen( path ); - if ( !tab ) { - FileInfo fileInfo( path ); - if ( fileInfo.exists() && fileInfo.isRegularFile() ) - loadFileFromPath( path ); - } else { - tab->getTabWidget()->setTabSelected( tab ); - } - mLocateBarLayout->execute( "close-locatebar" ); - } - } - } ); -} - -void App::updateLocateBar() { - mLocateBarLayout->runOnMainThread( [&] { - Float width = eeceil( mLocateInput->getPixelsSize().getWidth() ); - mLocateTable->setPixelsSize( width, - mLocateTable->getRowHeight() * LOCATEBAR_MAX_VISIBLE_ITEMS ); - width -= mLocateTable->getVerticalScrollBar()->getPixelsSize().getWidth(); - mLocateTable->setColumnWidth( 0, eeceil( width * 0.5 ) ); - mLocateTable->setColumnWidth( 1, width - mLocateTable->getColumnWidth( 0 ) ); - Vector2f pos( mLocateInput->convertToWorldSpace( { 0, 0 } ) ); - pos.y -= mLocateTable->getPixelsSize().getHeight(); - mLocateTable->setPixelsPosition( pos ); - } ); -} - -void App::hideSearchBar() { - mSearchBarLayout->setEnabled( false )->setVisible( false ); -} - -void App::showLocateBar() { - hideGlobalSearchBar(); - hideSearchBar(); - - mLocateBarLayout->setVisible( true ); - mLocateInput->setFocus(); - mLocateTable->setVisible( true ); - mLocateInput->getDocument().selectAll(); - mLocateInput->addEventListener( Event::OnSizeChange, - [&]( const Event* ) { updateLocateBar(); } ); - if ( mDirTree && !mLocateTable->getModel() ) { - mLocateTable->setModel( mDirTree->asModel( LOCATEBAR_MAX_RESULTS ) ); - mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); - } - updateLocateBar(); -} - -void App::initSearchBar() { - auto addClickListener = [&]( UIWidget* widget, std::string cmd ) { - widget->addEventListener( Event::MouseClick, [this, cmd]( const Event* event ) { - const MouseEvent* mouseEvent = static_cast( event ); - if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) - mSearchBarLayout->execute( cmd ); - } ); - }; - auto addReturnListener = [&]( UIWidget* widget, std::string cmd ) { - widget->addEventListener( Event::OnPressEnter, [this, cmd]( const Event* ) { - mSearchBarLayout->execute( cmd ); - } ); - }; - UITextInput* findInput = mSearchBarLayout->find( "search_find" ); - UITextInput* replaceInput = mSearchBarLayout->find( "search_replace" ); - UICheckBox* caseSensitiveChk = mSearchBarLayout->find( "case_sensitive" ); - UICheckBox* wholeWordChk = mSearchBarLayout->find( "whole_word" ); - UICheckBox* luaPatternChk = mSearchBarLayout->find( "lua_pattern" ); - - caseSensitiveChk->addEventListener( - Event::OnValueChange, [&, caseSensitiveChk]( const Event* ) { - mSearchState.caseSensitive = caseSensitiveChk->isChecked(); - } ); - - wholeWordChk->addEventListener( Event::OnValueChange, [&, wholeWordChk]( const Event* ) { - mSearchState.wholeWord = wholeWordChk->isChecked(); - } ); - - luaPatternChk->addEventListener( Event::OnValueChange, [&, luaPatternChk]( const Event* ) { - mSearchState.type = luaPatternChk->isChecked() ? TextDocument::FindReplaceType::LuaPattern - : TextDocument::FindReplaceType::Normal; - } ); - - findInput->addEventListener( Event::OnTextChanged, [&, findInput]( const Event* ) { - if ( mSearchState.editor && mEditorSplitter->editorExists( mSearchState.editor ) ) { - mSearchState.text = findInput->getText(); - mSearchState.editor->setHighlightWord( mSearchState.text ); - if ( !mSearchState.text.empty() ) { - mSearchState.editor->getDocument().setSelection( { 0, 0 } ); - if ( !findNextText( mSearchState ) ) { - findInput->addClass( "error" ); - } else { - findInput->removeClass( "error" ); - } - } else { - findInput->removeClass( "error" ); - mSearchState.editor->getDocument().setSelection( - mSearchState.editor->getDocument().getSelection().start() ); - } - } - } ); - mSearchBarLayout->addCommand( "close-searchbar", [&] { - hideSearchBar(); - if ( mEditorSplitter->getCurEditor() ) - mEditorSplitter->getCurEditor()->setFocus(); - if ( mSearchState.editor ) { - if ( mEditorSplitter->editorExists( mSearchState.editor ) ) { - mSearchState.editor->setHighlightWord( "" ); - mSearchState.editor->setHighlightTextRange( TextRange() ); - } - } - } ); - mSearchBarLayout->addCommand( "repeat-find", [this] { findNextText( mSearchState ); } ); - mSearchBarLayout->addCommand( "replace-all", [this, replaceInput] { - replaceAll( mSearchState, replaceInput->getText() ); - replaceInput->setFocus(); - } ); - mSearchBarLayout->addCommand( "find-and-replace", [this, replaceInput] { - findAndReplace( mSearchState, replaceInput->getText() ); - } ); - mSearchBarLayout->addCommand( "find-prev", [this] { findPrevText( mSearchState ); } ); - mSearchBarLayout->addCommand( "replace-selection", [this, replaceInput] { - replaceSelection( mSearchState, replaceInput->getText() ); - } ); - mSearchBarLayout->addCommand( "change-case", [&, caseSensitiveChk] { - caseSensitiveChk->setChecked( !caseSensitiveChk->isChecked() ); - } ); - mSearchBarLayout->addCommand( "change-whole-word", [&, wholeWordChk] { - wholeWordChk->setChecked( !wholeWordChk->isChecked() ); - } ); - mSearchBarLayout->addCommand( "toggle-lua-pattern", [&, luaPatternChk] { - luaPatternChk->setChecked( !luaPatternChk->isChecked() ); - } ); - mSearchBarLayout->getKeyBindings().addKeybindsString( { { "f3", "repeat-find" }, - { "ctrl+g", "repeat-find" }, - { "escape", "close-searchbar" }, - { "ctrl+r", "replace-all" }, - { "ctrl+s", "change-case" }, - { "ctrl+w", "change-whole-word" }, - { "ctrl+l", "toggle-lua-pattern" } } ); - addReturnListener( findInput, "repeat-find" ); - addReturnListener( replaceInput, "find-and-replace" ); - addClickListener( mSearchBarLayout->find( "find_prev" ), "find-prev" ); - addClickListener( mSearchBarLayout->find( "find_next" ), "repeat-find" ); - addClickListener( mSearchBarLayout->find( "replace" ), "replace-selection" ); - addClickListener( mSearchBarLayout->find( "replace_find" ), "find-and-replace" ); - addClickListener( mSearchBarLayout->find( "replace_all" ), "replace-all" ); - addClickListener( mSearchBarLayout->find( "searchbar_close" ), "close-searchbar" ); - replaceInput->addEventListener( Event::OnTabNavigate, - [findInput]( const Event* ) { findInput->setFocus(); } ); -} - -void App::showGlobalSearch( bool searchReplace ) { - hideLocateBar(); - hideSearchBar(); - bool wasReplaceTree = mGlobalSearchTreeReplace == mGlobalSearchTree; - mGlobalSearchTree = searchReplace ? mGlobalSearchTreeReplace : mGlobalSearchTreeSearch; - mGlobalSearchTreeSearch->setVisible( !searchReplace ); - mGlobalSearchTreeReplace->setVisible( searchReplace ); - mGlobalSearchBarLayout->setVisible( true )->setEnabled( true ); - mGlobalSearchInput->setFocus(); - mGlobalSearchLayout->setVisible( true ); - if ( mEditorSplitter->getCurEditor() && - mEditorSplitter->getCurEditor()->getDocument().hasSelection() ) { - mGlobalSearchInput->setText( - mEditorSplitter->getCurEditor()->getDocument().getSelectedText() ); - } - mGlobalSearchInput->getDocument().selectAll(); - auto* loader = mGlobalSearchTree->getParent()->find( "loader" ); - if ( loader ) - loader->setVisible( true ); - if ( !searchReplace ) { - mGlobalSearchLayout->findByClass( "replace_box" )->setVisible( searchReplace ); - if ( wasReplaceTree ) { - updateGlobalSearchBarResults( mGlobalSearchTreeReplace->getSearchStr(), - std::static_pointer_cast( - mGlobalSearchTreeReplace->getModelShared() ), - searchReplace ); - } - } - updateGlobalSearchBar(); -} - -void App::updateGlobalSearchBar() { - mGlobalSearchBarLayout->runOnMainThread( [&] { - Float width = eeceil( mGlobalSearchInput->getPixelsSize().getWidth() ); - Float rowHeight = mGlobalSearchTree->getRowHeight() * LOCATEBAR_MAX_VISIBLE_ITEMS; - mGlobalSearchLayout->setPixelsSize( width, 0 ); - mGlobalSearchTree->setPixelsSize( width, rowHeight ); - width -= mGlobalSearchTree->getVerticalScrollBar()->getPixelsSize().getWidth(); - mGlobalSearchTree->setColumnWidth( 0, eeceil( width ) ); - Vector2f pos( mGlobalSearchInput->convertToWorldSpace( { 0, 0 } ) ); - pos = PixelDensity::pxToDp( pos ); - mGlobalSearchLayout->setLayoutMarginLeft( pos.x ); - mGlobalSearchLayout->setLayoutMarginTop( pos.y - - mGlobalSearchLayout->getSize().getHeight() ); - } ); -} - -void App::hideGlobalSearchBar() { - mGlobalSearchBarLayout->setEnabled( false )->setVisible( false ); - mGlobalSearchLayout->setVisible( false ); - auto* loader = mGlobalSearchTree->getParent()->find( "loader" ); - if ( loader ) - loader->setVisible( false ); -} - -void App::updateGlobalSearchBarResults( const std::string& search, - std::shared_ptr model, - bool searchReplace ) { - updateGlobalSearchBar(); - mGlobalSearchTree->setSearchStr( search ); - mGlobalSearchTree->setModel( model ); - if ( mGlobalSearchTree->getModel()->rowCount() < 50 ) - mGlobalSearchTree->expandAll(); - mGlobalSearchLayout->findByClass( "search_str" )->setText( search ); - mGlobalSearchLayout->findByClass( "search_total" ) - ->setText( String::format( "%zu matches found.", model->resultCount() ) ); - mGlobalSearchLayout->findByClass( "status_box" )->setVisible( true ); - mGlobalSearchLayout->findByClass( "replace_box" )->setVisible( searchReplace ); - if ( searchReplace && mGlobalSearchBarLayout->isVisible() ) { - auto* replaceInput = - mGlobalSearchLayout->find( "global_search_replace_input" ); - replaceInput->setText( search ); - replaceInput->setFocus(); - } -} - -void App::doGlobalSearch( const String& text, bool caseSensitive, bool wholeWord, bool luaPattern, - bool searchReplace, bool searchAgain ) { - if ( mDirTree && mDirTree->getFilesCount() > 0 && !text.empty() ) { - mGlobalSearchTree = searchReplace ? mGlobalSearchTreeReplace : mGlobalSearchTreeSearch; - mGlobalSearchTreeSearch->setVisible( !searchReplace ); - mGlobalSearchTreeReplace->setVisible( searchReplace ); - mGlobalSearchLayout->findByClass( "status_box" )->setVisible( true ); - mGlobalSearchLayout->findByClass( "replace_box" )->setVisible( false ); - mGlobalSearchLayout->findByClass( "search_str" )->setText( text ); - UILoader* loader = UILoader::New(); - loader->setId( "loader" ); - loader->setRadius( 48 ); - loader->setOutlineThickness( 6 ); - loader->setFillColor( Color::Red ); - loader->setParent( mGlobalSearchLayout->getParent() ); - loader->setPosition( mGlobalSearchLayout->getPosition() + - mGlobalSearchLayout->getSize() * 0.5f - loader->getSize() * 0.5f ); - Clock* clock = eeNew( Clock, () ); - std::string search( text.toUtf8() ); - ProjectSearch::find( - mDirTree->getFiles(), search, -#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) - mThreadPool, -#endif - [&, clock, search, loader, searchReplace, - searchAgain]( const ProjectSearch::Result& res ) { - Log::info( "Global search for \"%s\" took %.2fms", search.c_str(), - clock->getElapsedTime().asMilliseconds() ); - eeDelete( clock ); - mUISceneNode->runOnMainThread( [&, loader, res, search, searchReplace, - searchAgain] { - auto model = ProjectSearch::asModel( res ); - auto listBox = mGlobalSearchHistoryList->getListBox(); - - if ( !searchAgain ) { - mGlobalSearchHistory.push_back( std::make_pair( search, model ) ); - if ( mGlobalSearchHistory.size() > 10 ) - mGlobalSearchHistory.pop_front(); - - std::vector items; - for ( auto item = mGlobalSearchHistory.rbegin(); - item != mGlobalSearchHistory.rend(); item++ ) { - items.push_back( item->first ); - } - - listBox->clear(); - listBox->addListBoxItems( items ); - if ( mGlobalSearchHistoryOnItemSelectedCb ) - mGlobalSearchHistoryList->removeEventListener( - mGlobalSearchHistoryOnItemSelectedCb ); - listBox->setSelected( 0 ); - mGlobalSearchHistoryOnItemSelectedCb = - mGlobalSearchHistoryList->addEventListener( - Event::OnItemSelected, [&, searchReplace]( const Event* ) { - auto idx = mGlobalSearchHistoryList->getListBox() - ->getItemSelectedIndex(); - auto idxItem = mGlobalSearchHistory.at( - mGlobalSearchHistory.size() - 1 - idx ); - updateGlobalSearchBarResults( idxItem.first, idxItem.second, - searchReplace ); - } ); - } else if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) { - mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - - listBox->getItemSelectedIndex()] - .second = model; - } - - updateGlobalSearchBarResults( search, model, searchReplace ); - loader->setVisible( false ); - loader->close(); - } ); - }, - caseSensitive, wholeWord, - luaPattern ? TextDocument::FindReplaceType::LuaPattern - : TextDocument::FindReplaceType::Normal ); - } -} - -void App::initGlobalSearchTree( UITreeViewGlobalSearch* searchTree ) { - searchTree->addClass( "search_tree" ); - searchTree->setParent( mGlobalSearchLayout ); - searchTree->setVisible( false ); - searchTree->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::Fixed ); - searchTree->setExpanderIconSize( PixelDensity::dpToPx( 20 ) ); - searchTree->setHeadersVisible( false ); - searchTree->setColumnsHidden( - { ProjectSearch::ResultModel::Line, ProjectSearch::ResultModel::ColumnStart }, true ); - searchTree->addEventListener( Event::KeyDown, [&]( const Event* event ) { - const KeyEvent* keyEvent = static_cast( event ); - if ( keyEvent->getKeyCode() == KEY_ESCAPE ) - mGlobalSearchBarLayout->execute( "close-global-searchbar" ); - } ); - searchTree->addEventListener( Event::OnModelEvent, [&]( const Event* event ) { - const ModelEvent* modelEvent = static_cast( event ); - if ( modelEvent->getModelEventType() == ModelEventType::Open ) { - const Model* model = modelEvent->getModel(); - if ( !model ) - return; - if ( mGlobalSearchTreeReplace == mGlobalSearchTree ) { - if ( modelEvent->getTriggerEvent()->getType() == Event::KeyDown ) { - const KeyEvent* keyEvent = - static_cast( modelEvent->getTriggerEvent() ); - if ( keyEvent->getKeyCode() == KEY_SPACE && - keyEvent->getNode()->isType( UI_TYPE_TREEVIEW_CELL ) ) { - auto* cell = - static_cast( keyEvent->getNode() ); - cell->toggleSelected(); - return; - } - } - } - - Variant vPath( model->data( model->index( modelEvent->getModelIndex().internalId(), - ProjectSearch::ResultModel::FileOrPosition ), - Model::Role::Custom ) ); - if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { - std::string path( vPath.asCStr() ); - UITab* tab = mEditorSplitter->isDocumentOpen( path ); - if ( !tab ) { - FileInfo fileInfo( path ); - if ( fileInfo.exists() && fileInfo.isRegularFile() ) - loadFileFromPath( path ); - } else { - tab->getTabWidget()->setTabSelected( tab ); - } - Variant lineNum( - model->data( model->index( modelEvent->getModelIndex().row(), - ProjectSearch::ResultModel::FileOrPosition, - modelEvent->getModelIndex().parent() ), - Model::Role::Custom ) ); - Variant colNum( model->data( model->index( modelEvent->getModelIndex().row(), - ProjectSearch::ResultModel::ColumnStart, - modelEvent->getModelIndex().parent() ), - Model::Role::Custom ) ); - if ( mEditorSplitter->getCurEditor() && lineNum.isValid() && colNum.isValid() && - lineNum.is( Variant::Type::Int64 ) && colNum.is( Variant::Type::Int64 ) ) { - TextPosition pos{ lineNum.asInt64(), colNum.asInt64() }; - mEditorSplitter->getCurEditor()->getDocument().setSelection( pos ); - mEditorSplitter->getCurEditor()->goToLine( pos ); - hideGlobalSearchBar(); - } - } - } - } ); -} - -static String replaceInText( String text, const String& replaceText, - std::vector replacements ) { - Int64 diff = 0; - String oldText( text ); - for ( const auto& range : replacements ) { - Int64 len = range.end().column() - range.start().column(); - auto before = text.substr( 0, range.start().column() + diff ); - auto after = !text.empty() ? text.substr( range.end().column() + diff ) : ""; - text = before + replaceText + after; - diff += replaceText.size() - len; - } - return text; -} - -void App::replaceInFiles( const String& replaceText, - std::shared_ptr model ) { - const ProjectSearch::Result& res = model.get()->getResult(); - for ( const auto& fileResult : res ) { - std::map>> replaceRangeMap; - std::map replaceStringMap; - for ( const auto& result : fileResult.results ) { - if ( result.selected ) { - replaceRangeMap[result.position.start().line()].first = result.line; - replaceRangeMap[result.position.start().line()].second.push_back( result.position ); - } - } - - for ( const auto& replace : replaceRangeMap ) - replaceStringMap[replace.second.second.front().start().line()] = - replaceInText( replace.second.first, replaceText, replace.second.second ); - - std::shared_ptr doc = mEditorSplitter->findDocFromPath( fileResult.file ); - bool loaded = doc ? true : false; - bool save = false; - bool inMemoryAndNotDirty = doc ? !doc->isDirty() : false; - - if ( !doc ) { - doc = std::make_shared(); - loaded = doc->loadFromFile( fileResult.file ); - save = true; - } - - if ( doc && loaded ) { - for ( const auto& replaceString : replaceStringMap ) - doc->replaceLine( replaceString.first, replaceString.second ); - - if ( save || inMemoryAndNotDirty ) - doc->save(); - } - } -} - -void App::initGlobalSearchBar() { - auto addClickListener = [&]( UIWidget* widget, std::string cmd ) { - widget->addEventListener( Event::MouseClick, [this, cmd]( const Event* event ) { - const MouseEvent* mouseEvent = static_cast( event ); - if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) - mGlobalSearchBarLayout->execute( cmd ); - } ); - }; - UIPushButton* searchButton = mGlobalSearchBarLayout->find( "global_search" ); - UIPushButton* searchReplaceButton = - mGlobalSearchBarLayout->find( "global_search_replace" ); - UICheckBox* caseSensitiveChk = mGlobalSearchBarLayout->find( "case_sensitive" ); - UICheckBox* wholeWordChk = mGlobalSearchBarLayout->find( "whole_word" ); - UICheckBox* luaPatternChk = mGlobalSearchBarLayout->find( "lua_pattern" ); - UIWidget* searchBarClose = mGlobalSearchBarLayout->find( "global_searchbar_close" ); - mGlobalSearchInput = mGlobalSearchBarLayout->find( "global_search_find" ); - mGlobalSearchHistoryList = - mGlobalSearchBarLayout->find( "global_search_history" ); - mGlobalSearchBarLayout->addCommand( - "search-in-files", [&, caseSensitiveChk, wholeWordChk, luaPatternChk] { - doGlobalSearch( mGlobalSearchInput->getText(), caseSensitiveChk->isChecked(), - wholeWordChk->isChecked(), luaPatternChk->isChecked(), false ); - } ); - mGlobalSearchBarLayout->addCommand( - "search-replace-in-files", [&, caseSensitiveChk, wholeWordChk, luaPatternChk] { - doGlobalSearch( mGlobalSearchInput->getText(), caseSensitiveChk->isChecked(), - wholeWordChk->isChecked(), luaPatternChk->isChecked(), true ); - } ); - mGlobalSearchBarLayout->addCommand( - "search-again", [&, caseSensitiveChk, wholeWordChk, luaPatternChk] { - auto listBox = mGlobalSearchHistoryList->getListBox(); - if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) { - doGlobalSearch( mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - - listBox->getItemSelectedIndex()] - .first, - caseSensitiveChk->isChecked(), wholeWordChk->isChecked(), - luaPatternChk->isChecked(), - mGlobalSearchTreeReplace == mGlobalSearchTree, true ); - } - } ); - mGlobalSearchBarLayout->addCommand( "close-global-searchbar", [&] { - hideGlobalSearchBar(); - if ( mEditorSplitter->getCurEditor() ) - mEditorSplitter->getCurEditor()->setFocus(); - } ); - mGlobalSearchBarLayout->getKeyBindings().addKeybindsString( { - { "escape", "close-global-searchbar" }, - { "ctrl+s", "change-case" }, - { "ctrl+w", "change-whole-word" }, - { "ctrl+l", "toggle-lua-pattern" }, - { "ctrl+r", "search-replace-in-files" }, - { "ctrl+g", "search-again" }, - } ); - mGlobalSearchBarLayout->addCommand( "change-case", [&, caseSensitiveChk] { - caseSensitiveChk->setChecked( !caseSensitiveChk->isChecked() ); - } ); - mGlobalSearchBarLayout->addCommand( "change-whole-word", [&, wholeWordChk] { - wholeWordChk->setChecked( !wholeWordChk->isChecked() ); - } ); - mGlobalSearchBarLayout->addCommand( "toggle-lua-pattern", [&, luaPatternChk] { - luaPatternChk->setChecked( !luaPatternChk->isChecked() ); - } ); - mGlobalSearchInput->addEventListener( Event::OnPressEnter, [&]( const Event* ) { - if ( mGlobalSearchInput->hasFocus() ) { - mGlobalSearchBarLayout->execute( "search-in-files" ); - } else { - KeyEvent keyEvent( mGlobalSearchTree, Event::KeyDown, KEY_RETURN, 0, 0 ); - mGlobalSearchTree->forceKeyDown( keyEvent ); - } - } ); - mGlobalSearchInput->addEventListener( Event::KeyDown, [&]( const Event* event ) { - const KeyEvent* keyEvent = static_cast( event ); - Uint32 keyCode = keyEvent->getKeyCode(); - if ( ( keyCode == KEY_UP || keyCode == KEY_DOWN || keyCode == KEY_PAGEUP || - keyCode == KEY_PAGEDOWN || keyCode == KEY_HOME || keyCode == KEY_END ) && - mGlobalSearchTree->forceKeyDown( *keyEvent ) && !mGlobalSearchTree->hasFocus() ) { - mGlobalSearchTree->setFocus(); - } - } ); - mGlobalSearchInput->addEventListener( Event::OnSizeChange, [&]( const Event* ) { - if ( mGlobalSearchBarLayout->isVisible() ) - updateGlobalSearchBar(); - } ); - addClickListener( searchButton, "search-in-files" ); - addClickListener( searchReplaceButton, "search-replace-in-files" ); - addClickListener( searchBarClose, "close-global-searchbar" ); - mGlobalSearchLayout = mUISceneNode - ->loadLayoutFromString( R"xml( - - - - - - - - - - - - - - - )xml", - mUISceneNode->getRoot() ) - ->asType(); - UIPushButton* searchAgainBtn = mGlobalSearchLayout->find( "global_search_again" ); - UITextInput* replaceInput = - mGlobalSearchLayout->find( "global_search_replace_input" ); - UIPushButton* replaceButton = - mGlobalSearchLayout->find( "global_search_replace_button" ); - addClickListener( searchAgainBtn, "search-again" ); - addClickListener( replaceButton, "replace-in-files" ); - replaceInput->addEventListener( Event::OnPressEnter, [&, replaceInput]( const Event* ) { - if ( replaceInput->hasFocus() ) - mGlobalSearchBarLayout->execute( "replace-in-files" ); - } ); - replaceInput->addEventListener( Event::KeyDown, [&]( const Event* event ) { - const KeyEvent* keyEvent = static_cast( event ); - if ( keyEvent->getKeyCode() == KEY_ESCAPE ) - mGlobalSearchBarLayout->execute( "close-global-searchbar" ); - } ); - mGlobalSearchBarLayout->addCommand( "replace-in-files", [&, replaceInput] { - auto listBox = mGlobalSearchHistoryList->getListBox(); - if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) { - const auto& replaceData = mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - - listBox->getItemSelectedIndex()]; - replaceInFiles( replaceInput->getText(), replaceData.second ); - mGlobalSearchBarLayout->execute( "search-again" ); - mGlobalSearchBarLayout->execute( "close-global-searchbar" ); - } - } ); - mGlobalSearchTreeSearch = - UITreeViewGlobalSearch::New( mEditorSplitter->getCurrentColorScheme(), false ); - mGlobalSearchTreeReplace = - UITreeViewGlobalSearch::New( mEditorSplitter->getCurrentColorScheme(), true ); - initGlobalSearchTree( mGlobalSearchTreeSearch ); - initGlobalSearchTree( mGlobalSearchTreeReplace ); - mGlobalSearchTree = mGlobalSearchTreeSearch; -} - -void App::showFindView() { - hideLocateBar(); - hideGlobalSearchBar(); - - UICodeEditor* editor = mEditorSplitter->getCurEditor(); - if ( !editor ) - return; - - mSearchState.editor = editor; - mSearchState.range = TextRange(); - mSearchState.caseSensitive = - mSearchBarLayout->find( "case_sensitive" )->isChecked(); - mSearchState.wholeWord = mSearchBarLayout->find( "whole_word" )->isChecked(); - mSearchBarLayout->setEnabled( true )->setVisible( true ); - - UITextInput* findInput = mSearchBarLayout->find( "search_find" ); - findInput->getDocument().selectAll(); - findInput->setFocus(); - - const TextDocument& doc = editor->getDocument(); - - if ( doc.getSelection().hasSelection() && doc.getSelection().inSameLine() ) { - String text = doc.getSelectedText(); - if ( !text.empty() ) { - findInput->setText( text ); - findInput->getDocument().selectAll(); - } else if ( !findInput->getText().empty() ) { - findInput->getDocument().selectAll(); - } - } else if ( doc.getSelection().hasSelection() ) { - mSearchState.range = doc.getSelection( true ); - if ( !findInput->getText().empty() ) - findInput->getDocument().selectAll(); - } - mSearchState.text = findInput->getText(); - editor->setHighlightTextRange( mSearchState.range ); - editor->setHighlightWord( mSearchState.text ); - editor->getDocument().setActiveClient( editor ); -} - void App::closeApp() { if ( onCloseRequestCallback( mWindow ) ) mWindow->close(); @@ -1410,6 +612,10 @@ UIMenu* App::createViewMenu() { mViewMenu->addCheckBox( "Hide tabbar on single tab" ) ->setActive( mConfig.editor.hideTabBarOnSingleTab ) ->setTooltipText( "Hides the tabbar if there's only one element in the tab widget." ); + mViewMenu->addCheckBox( "Single Click Navigation in Tree View" ) + ->setActive( mConfig.editor.singleClickTreeNavigation ) + ->setTooltipText( + "Uses single click to open files and expand subfolders in\nthe directory tree." ); mViewMenu->add( "Line Breaking Column" ); mViewMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { @@ -1469,6 +675,9 @@ UIMenu* App::createViewMenu() { } else if ( item->getText() == "Hide tabbar on single tab" ) { mConfig.editor.hideTabBarOnSingleTab = item->asType()->isActive(); mEditorSplitter->setHideTabBarOnSingleTab( mConfig.editor.hideTabBarOnSingleTab ); + } else if ( item->getText() == "Single Click Navigation in Tree View" ) { + mConfig.editor.singleClickTreeNavigation = item->asType()->isActive(); + mProjectTreeView->setSingleClickNavigation( mConfig.editor.singleClickTreeNavigation ); } else if ( item->getText() == "Line Breaking Column" ) { UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::INPUT, "Set Line Breaking Column:\n" @@ -1825,25 +1034,12 @@ void App::updateDocInfo( TextDocument& doc ) { void App::onCodeEditorFocusChange( UICodeEditor* editor ) { updateDocInfo( editor->getDocument() ); updateDocumentMenu(); - - if ( mSearchState.editor && mSearchState.editor != editor ) { - String word = mSearchState.editor->getHighlightWord(); - mSearchState.editor->setHighlightWord( "" ); - mSearchState.editor->setHighlightTextRange( TextRange() ); - mSearchState.text = ""; - mSearchState.range = TextRange(); - if ( editor ) { - mSearchState.editor = editor; - mSearchState.editor->setHighlightWord( word ); - mSearchState.range = TextRange(); - } - } + mDocSearchController->onCodeEditorFocusChange( editor ); } void App::onColorSchemeChanged( const std::string& ) { updateColorSchemeMenu(); - mGlobalSearchTreeSearch->updateColorScheme( mEditorSplitter->getCurrentColorScheme() ); - mGlobalSearchTreeReplace->updateColorScheme( mEditorSplitter->getCurrentColorScheme() ); + mGlobalSearchController->updateColorScheme( mEditorSplitter->getCurrentColorScheme() ); } void App::onDocumentLoaded( UICodeEditor* editor, const std::string& path ) { @@ -2036,6 +1232,22 @@ void App::loadFileFromPath( const std::string& path, bool inNewTab, UICodeEditor } } +void App::hideGlobalSearchBar() { + mGlobalSearchController->hideGlobalSearchBar(); +} + +void App::hideSearchBar() { + mDocSearchController->hideSearchBar(); +} + +void App::hideLocateBar() { + mFileLocator->hideLocateBar(); +} + +bool App::isDirTreeReady() const { + return mDirTreeReady; +} + void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { const CodeEditorConfig& config = mConfig.editor; editor->setFontSize( config.fontSize.asDp( 0, Sizef(), mUISceneNode->getDPI() ) ); @@ -2069,11 +1281,15 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { doc.setCommand( "save-doc", [&] { saveDoc(); } ); doc.setCommand( "save-as-doc", [&] { saveFileDialog( mEditorSplitter->getCurEditor() ); } ); doc.setCommand( "save-all", [&] { saveAll(); } ); - doc.setCommand( "find-replace", [&] { showFindView(); } ); - doc.setCommand( "open-global-search", - [&] { showGlobalSearch( mGlobalSearchTreeReplace == mGlobalSearchTree ); } ); - doc.setCommand( "open-locatebar", [&] { showLocateBar(); } ); - doc.setCommand( "repeat-find", [&] { findNextText( mSearchState ); } ); + doc.setCommand( "find-replace", [&] { mDocSearchController->showFindView(); } ); + doc.setCommand( "open-global-search", [&] { + mGlobalSearchController->showGlobalSearch( + mGlobalSearchController->isUsingSearchReplaceTree() ); + } ); + doc.setCommand( "open-locatebar", [&] { mFileLocator->showLocateBar(); } ); + doc.setCommand( "repeat-find", [&] { + mDocSearchController->findNextText( mDocSearchController->getSearchState() ); + } ); doc.setCommand( "close-folder", [&] { closeFolder(); } ); doc.setCommand( "close-app", [&] { closeApp(); } ); doc.setCommand( "fullscreen-toggle", [&]() { @@ -2118,7 +1334,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { } ); doc.setCommand( "debug-draw-debug-data", [&] { mUISceneNode->setDrawDebugData( !mUISceneNode->getDrawDebugData() ); } ); - doc.setCommand( "go-to-line", [&] { goToLine(); } ); + doc.setCommand( "go-to-line", [&] { mFileLocator->goToLine(); } ); doc.setCommand( "load-current-dir", [&] { loadCurrentDirectory(); } ); doc.setCommand( "menu-toggle", [&] { toggleSettingsMenu(); } ); doc.setCommand( "switch-side-panel", [&] { switchSidePanel(); } ); @@ -2241,11 +1457,11 @@ UIPopUpMenu* App::createToolsMenu() { UIMenuItem* item = event->getNode()->asType(); std::string txt( item->getText() ); if ( txt == "Locate..." ) { - showLocateBar(); + mFileLocator->showLocateBar(); } else if ( txt == "Project Find..." ) { - showGlobalSearch(); + mGlobalSearchController->showGlobalSearch(); } else if ( txt == "Go to line..." ) { - goToLine(); + mFileLocator->goToLine(); } else if ( txt == "Load current document directory as folder" ) { loadCurrentDirectory(); } @@ -2408,7 +1624,7 @@ void App::loadDirTree( const std::string& path ) { clock->getElapsedTime().asMilliseconds(), dirTree.getFilesCount() ); eeDelete( clock ); mDirTreeReady = true; - mUISceneNode->runOnMainThread( [&] { updateLocateTable(); } ); + mUISceneNode->runOnMainThread( [&] { mFileLocator->updateLocateTable(); } ); if ( mFileWatcher ) { removeFolderWatches(); auto newDirs = dirTree.getDirectories(); @@ -2432,6 +1648,7 @@ void App::initProjectTreeView( const std::string& path ) { mProjectTreeView->setContractedIcon( "folder" ); mProjectTreeView->setHeadersVisible( false ); mProjectTreeView->setExpandersAsIcons( true ); + mProjectTreeView->setSingleClickNavigation( mConfig.editor.singleClickTreeNavigation ); mProjectTreeView->addEventListener( Event::OnModelEvent, [&]( const Event* event ) { const ModelEvent* modelEvent = static_cast( event ); if ( modelEvent->getModelEventType() == ModelEventType::Open ) { @@ -2842,20 +2059,15 @@ void App::init( const std::string& file, const Float& pidelDensity ) { mUISceneNode->bind( "main_layout", mMainLayout ); mUISceneNode->bind( "code_container", mBaseLayout ); mUISceneNode->bind( "image_container", mImageLayout ); - mUISceneNode->bind( "search_bar", mSearchBarLayout ); - mUISceneNode->bind( "global_search_bar", mGlobalSearchBarLayout ); - mUISceneNode->bind( "locate_bar", mLocateBarLayout ); mUISceneNode->bind( "doc_info", mDocInfo ); mUISceneNode->bind( "doc_info_text", mDocInfoText ); mUISceneNode->bind( "panel", mSidePanel ); mUISceneNode->bind( "project_splitter", mProjectSplitter ); - mUISceneNode->bind( "locate_find", mLocateInput ); mUISceneNode->addEventListener( Event::KeyDown, [&]( const Event* event ) { trySendUnlockedCmd( *static_cast( event ) ); } ); mDocInfo->setVisible( mConfig.editor.showDocInfo ); - mSearchBarLayout->setVisible( false )->setEnabled( false ); - mGlobalSearchBarLayout->setVisible( false )->setEnabled( false ); + mProjectSplitter->setSplitPartition( StyleSheetLength( mConfig.window.panelPartition ) ); if ( !mConfig.ui.showSidePanel ) @@ -2873,11 +2085,17 @@ void App::init( const std::string& file, const Float& pidelDensity ) { mFileWatcher->watch(); #endif - initSearchBar(); + mDocSearchController = std::make_unique( mEditorSplitter, this ); + mDocSearchController->initSearchBar( mUISceneNode->find( "search_bar" ) ); - initGlobalSearchBar(); + mGlobalSearchController = + std::make_unique( mEditorSplitter, mUISceneNode, this ); + mGlobalSearchController->initGlobalSearchBar( + mUISceneNode->find( "global_search_bar" ) ); - initLocateBar(); + mFileLocator = std::make_unique( mEditorSplitter, mUISceneNode, this ); + mFileLocator->initLocateBar( mUISceneNode->find( "locate_bar" ), + mUISceneNode->find( "locate_find" ) ); initImageView(); diff --git a/src/tools/codeeditor/codeeditor.hpp b/src/tools/codeeditor/codeeditor.hpp index f270f6d60..4369b4741 100644 --- a/src/tools/codeeditor/codeeditor.hpp +++ b/src/tools/codeeditor/codeeditor.hpp @@ -2,99 +2,17 @@ #define EE_TOOLS_CODEEDITOR_HPP #include "appconfig.hpp" +#include "docsearchcontroller.hpp" +#include "filelocator.hpp" #include "filesystemlistener.hpp" +#include "globalsearchcontroller.hpp" #include "projectdirectorytree.hpp" #include "projectsearch.hpp" #include "uitreeviewglobalsearch.hpp" +#include "widgetcommandexecuter.hpp" #include #include -class WidgetCommandExecuter { - public: - typedef std::function CommandCallback; - - WidgetCommandExecuter( const KeyBindings& keybindings ) : mKeyBindings( keybindings ) {} - - void addCommand( const std::string& name, const CommandCallback& cb ) { mCommands[name] = cb; } - - void execute( const std::string& command ) { - auto cmdIt = mCommands.find( command ); - if ( cmdIt != mCommands.end() ) - cmdIt->second(); - } - - KeyBindings& getKeyBindings() { return mKeyBindings; } - - protected: - KeyBindings mKeyBindings; - std::unordered_map> mCommands; - - Uint32 onKeyDown( const KeyEvent& event ) { - std::string cmd = - mKeyBindings.getCommandFromKeyBind( { event.getKeyCode(), event.getMod() } ); - if ( !cmd.empty() ) { - auto cmdIt = mCommands.find( cmd ); - if ( cmdIt != mCommands.end() ) { - cmdIt->second(); - return 1; - } - } - return 0; - } -}; - -class UISearchBar : public UILinearLayout, public WidgetCommandExecuter { - public: - static UISearchBar* New() { return eeNew( UISearchBar, () ); } - - UISearchBar() : - UILinearLayout( "searchbar", UIOrientation::Horizontal ), - WidgetCommandExecuter( getUISceneNode()->getWindow()->getInput() ) {} - - virtual Uint32 onKeyDown( const KeyEvent& event ) { - return WidgetCommandExecuter::onKeyDown( event ); - } -}; - -class UILocateBar : public UILinearLayout, public WidgetCommandExecuter { - public: - static UILocateBar* New() { return eeNew( UILocateBar, () ); } - UILocateBar() : - UILinearLayout( "locatebar", UIOrientation::Horizontal ), - WidgetCommandExecuter( getUISceneNode()->getWindow()->getInput() ) {} - - virtual Uint32 onKeyDown( const KeyEvent& event ) { - return WidgetCommandExecuter::onKeyDown( event ); - } -}; - -class UIGlobalSearchBar : public UILinearLayout, public WidgetCommandExecuter { - public: - static UIGlobalSearchBar* New() { return eeNew( UIGlobalSearchBar, () ); } - - UIGlobalSearchBar() : - UILinearLayout( "globalsearchbar", UIOrientation::Vertical ), - WidgetCommandExecuter( getUISceneNode()->getWindow()->getInput() ) {} - - virtual Uint32 onKeyDown( const KeyEvent& event ) { - return WidgetCommandExecuter::onKeyDown( event ); - } -}; - -struct SearchState { - UICodeEditor* editor{ nullptr }; - String text; - TextRange range = TextRange(); - bool caseSensitive{ false }; - bool wholeWord{ false }; - TextDocument::FindReplaceType type{ TextDocument::FindReplaceType::Normal }; - void reset() { - editor = nullptr; - range = TextRange(); - text = ""; - } -}; - class AutoCompleteModule; class LinterModule; @@ -116,26 +34,10 @@ class App : public UICodeEditorSplitter::Client { UIFileDialog* saveFileDialog( UICodeEditor* editor, bool focusOnClose = true ); - bool findPrevText( SearchState& search ); - - bool findNextText( SearchState& search ); - void closeApp(); void mainLoop(); - void showFindView(); - - void showGlobalSearch( bool searchAndReplace = false ); - - void showLocateBar(); - - bool replaceSelection( SearchState& search, const String& replacement ); - - int replaceAll( SearchState& search, const String& replace ); - - bool findAndReplace( SearchState& search, const String& replace ); - void runCommand( const std::string& command ); void loadConfig(); @@ -148,21 +50,29 @@ class App : public UICodeEditorSplitter::Client { void saveAll(); - void doGlobalSearch( const String& text, bool caseSensitive, bool wholeWord, bool luaPattern, - bool searchReplace, bool searchAgain = false ); + ProjectDirectoryTree* getDirTree() const; + + std::shared_ptr getThreadPool() const; + + void loadFileFromPath( const std::string& path, bool inNewTab = true, + UICodeEditor* codeEditor = nullptr ); + + void hideGlobalSearchBar(); + + void hideSearchBar(); + + void hideLocateBar(); + + bool isDirTreeReady() const; protected: EE::Window::Window* mWindow{ nullptr }; UISceneNode* mUISceneNode{ nullptr }; Console* mConsole{ nullptr }; std::string mWindowTitle{ "ecode" }; - String mLastSearch; UILayout* mMainLayout{ nullptr }; UILayout* mBaseLayout{ nullptr }; UILayout* mImageLayout{ nullptr }; - UISearchBar* mSearchBarLayout{ nullptr }; - UILocateBar* mLocateBarLayout{ nullptr }; - UILocateBar* mGlobalSearchBarLayout{ nullptr }; UIPopUpMenu* mSettingsMenu{ nullptr }; UITextView* mSettingsButton{ nullptr }; UIPopUpMenu* mColorSchemeMenu{ nullptr }; @@ -184,7 +94,6 @@ class App : public UICodeEditorSplitter::Client { std::map mKeybindingsInvert; std::string mConfigPath; std::string mKeybindingsPath; - SearchState mSearchState; Float mDisplayDPI; std::string mResPath; AutoCompleteModule* mAutoCompleteModule{ nullptr }; @@ -193,17 +102,6 @@ class App : public UICodeEditorSplitter::Client { std::unique_ptr mDirTree; UITreeView* mProjectTreeView{ nullptr }; std::shared_ptr mFileSystemModel; - UITableView* mLocateTable{ nullptr }; - UITextInput* mLocateInput{ nullptr }; - UILayout* mGlobalSearchLayout{ nullptr }; - UITreeViewGlobalSearch* mGlobalSearchTree{ nullptr }; - UITreeViewGlobalSearch* mGlobalSearchTreeSearch{ nullptr }; - UITreeViewGlobalSearch* mGlobalSearchTreeReplace{ nullptr }; - UITextInput* mGlobalSearchInput{ nullptr }; - UIDropDownList* mGlobalSearchHistoryList{ nullptr }; - Uint32 mGlobalSearchHistoryOnItemSelectedCb{ 0 }; - std::deque>> - mGlobalSearchHistory; size_t mMenuIconSize; bool mDirTreeReady{ false }; std::unordered_set mTmpDocs; @@ -214,6 +112,9 @@ class App : public UICodeEditorSplitter::Client { FileSystemListener* mFileSystemListener{ nullptr }; std::unordered_set mFolderWatches; std::unordered_map mFilesFolderWatches; + std::unique_ptr mGlobalSearchController; + std::unique_ptr mDocSearchController; + std::unique_ptr mFileLocator; void saveAllProcess(); @@ -241,10 +142,6 @@ class App : public UICodeEditorSplitter::Client { bool onCloseRequestCallback( EE::Window::Window* ); - void initSearchBar(); - - void initGlobalSearchBar(); - void addRemainingTabWidgets( Node* widget ); void createSettingsMenu(); @@ -311,24 +208,10 @@ class App : public UICodeEditorSplitter::Client { void setFocusEditorOnClose( UIMessageBox* msgBox ); - void updateLocateBar(); - - void updateLocateTable(); - - void updateGlobalSearchBar(); - UIPopUpMenu* createToolsMenu(); - void hideGlobalSearchBar(); - - void hideSearchBar(); - - void hideLocateBar(); - bool trySendUnlockedCmd( const KeyEvent& keyEvent ); - void goToLine(); - void loadCurrentDirectory(); void toggleSettingsMenu(); @@ -340,23 +223,11 @@ class App : public UICodeEditorSplitter::Client { void closeEditors(); - void updateGlobalSearchBarResults( const std::string& search, - std::shared_ptr model, - bool searchReplace ); - void switchSidePanel(); void removeFolderWatches(); void createDocAlert( UICodeEditor* editor ); - - void loadFileFromPath( const std::string& path, bool inNewTab = true, - UICodeEditor* codeEditor = nullptr ); - - void initGlobalSearchTree( UITreeViewGlobalSearch* searchTree ); - - void replaceInFiles( const String& replaceText, - std::shared_ptr model ); }; #endif // EE_TOOLS_CODEEDITOR_HPP diff --git a/src/tools/codeeditor/docsearchcontroller.cpp b/src/tools/codeeditor/docsearchcontroller.cpp new file mode 100644 index 000000000..037ce0158 --- /dev/null +++ b/src/tools/codeeditor/docsearchcontroller.cpp @@ -0,0 +1,279 @@ +#include "docsearchcontroller.hpp" +#include "codeeditor.hpp" + +DocSearchController::DocSearchController( UICodeEditorSplitter* editorSplitter, App* app ) : + mEditorSplitter( editorSplitter ), mApp( app ) {} + +void DocSearchController::initSearchBar( UISearchBar* searchBar ) { + mSearchBarLayout = searchBar; + mSearchBarLayout->setVisible( false )->setEnabled( false ); + auto addClickListener = [&]( UIWidget* widget, std::string cmd ) { + widget->addEventListener( Event::MouseClick, [this, cmd]( const Event* event ) { + const MouseEvent* mouseEvent = static_cast( event ); + if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) + mSearchBarLayout->execute( cmd ); + } ); + }; + auto addReturnListener = [&]( UIWidget* widget, std::string cmd ) { + widget->addEventListener( Event::OnPressEnter, [this, cmd]( const Event* ) { + mSearchBarLayout->execute( cmd ); + } ); + }; + UITextInput* findInput = mSearchBarLayout->find( "search_find" ); + UITextInput* replaceInput = mSearchBarLayout->find( "search_replace" ); + UICheckBox* caseSensitiveChk = mSearchBarLayout->find( "case_sensitive" ); + UICheckBox* wholeWordChk = mSearchBarLayout->find( "whole_word" ); + UICheckBox* luaPatternChk = mSearchBarLayout->find( "lua_pattern" ); + + caseSensitiveChk->addEventListener( + Event::OnValueChange, [&, caseSensitiveChk]( const Event* ) { + mSearchState.caseSensitive = caseSensitiveChk->isChecked(); + } ); + + wholeWordChk->addEventListener( Event::OnValueChange, [&, wholeWordChk]( const Event* ) { + mSearchState.wholeWord = wholeWordChk->isChecked(); + } ); + + luaPatternChk->addEventListener( Event::OnValueChange, [&, luaPatternChk]( const Event* ) { + mSearchState.type = luaPatternChk->isChecked() ? TextDocument::FindReplaceType::LuaPattern + : TextDocument::FindReplaceType::Normal; + } ); + + findInput->addEventListener( Event::OnTextChanged, [&, findInput]( const Event* ) { + if ( mSearchState.editor && mEditorSplitter->editorExists( mSearchState.editor ) ) { + mSearchState.text = findInput->getText(); + mSearchState.editor->setHighlightWord( mSearchState.text ); + if ( !mSearchState.text.empty() ) { + mSearchState.editor->getDocument().setSelection( { 0, 0 } ); + if ( !findNextText( mSearchState ) ) { + findInput->addClass( "error" ); + } else { + findInput->removeClass( "error" ); + } + } else { + findInput->removeClass( "error" ); + mSearchState.editor->getDocument().setSelection( + mSearchState.editor->getDocument().getSelection().start() ); + } + } + } ); + mSearchBarLayout->addCommand( "close-searchbar", [&] { + hideSearchBar(); + if ( mEditorSplitter->getCurEditor() ) + mEditorSplitter->getCurEditor()->setFocus(); + if ( mSearchState.editor ) { + if ( mEditorSplitter->editorExists( mSearchState.editor ) ) { + mSearchState.editor->setHighlightWord( "" ); + mSearchState.editor->setHighlightTextRange( TextRange() ); + } + } + } ); + mSearchBarLayout->addCommand( "repeat-find", [this] { findNextText( mSearchState ); } ); + mSearchBarLayout->addCommand( "replace-all", [this, replaceInput] { + replaceAll( mSearchState, replaceInput->getText() ); + replaceInput->setFocus(); + } ); + mSearchBarLayout->addCommand( "find-and-replace", [this, replaceInput] { + findAndReplace( mSearchState, replaceInput->getText() ); + } ); + mSearchBarLayout->addCommand( "find-prev", [this] { findPrevText( mSearchState ); } ); + mSearchBarLayout->addCommand( "replace-selection", [this, replaceInput] { + replaceSelection( mSearchState, replaceInput->getText() ); + } ); + mSearchBarLayout->addCommand( "change-case", [&, caseSensitiveChk] { + caseSensitiveChk->setChecked( !caseSensitiveChk->isChecked() ); + } ); + mSearchBarLayout->addCommand( "change-whole-word", [&, wholeWordChk] { + wholeWordChk->setChecked( !wholeWordChk->isChecked() ); + } ); + mSearchBarLayout->addCommand( "toggle-lua-pattern", [&, luaPatternChk] { + luaPatternChk->setChecked( !luaPatternChk->isChecked() ); + } ); + mSearchBarLayout->getKeyBindings().addKeybindsString( { { "f3", "repeat-find" }, + { "ctrl+g", "repeat-find" }, + { "escape", "close-searchbar" }, + { "ctrl+r", "replace-all" }, + { "ctrl+s", "change-case" }, + { "ctrl+w", "change-whole-word" }, + { "ctrl+l", "toggle-lua-pattern" } } ); + addReturnListener( findInput, "repeat-find" ); + addReturnListener( replaceInput, "find-and-replace" ); + addClickListener( mSearchBarLayout->find( "find_prev" ), "find-prev" ); + addClickListener( mSearchBarLayout->find( "find_next" ), "repeat-find" ); + addClickListener( mSearchBarLayout->find( "replace" ), "replace-selection" ); + addClickListener( mSearchBarLayout->find( "replace_find" ), "find-and-replace" ); + addClickListener( mSearchBarLayout->find( "replace_all" ), "replace-all" ); + addClickListener( mSearchBarLayout->find( "searchbar_close" ), "close-searchbar" ); + replaceInput->addEventListener( Event::OnTabNavigate, + [findInput]( const Event* ) { findInput->setFocus(); } ); +} + +void DocSearchController::showFindView() { + mApp->hideLocateBar(); + mApp->hideGlobalSearchBar(); + + UICodeEditor* editor = mEditorSplitter->getCurEditor(); + if ( !editor ) + return; + + mSearchState.editor = editor; + mSearchState.range = TextRange(); + mSearchState.caseSensitive = + mSearchBarLayout->find( "case_sensitive" )->isChecked(); + mSearchState.wholeWord = mSearchBarLayout->find( "whole_word" )->isChecked(); + mSearchBarLayout->setEnabled( true )->setVisible( true ); + + UITextInput* findInput = mSearchBarLayout->find( "search_find" ); + findInput->getDocument().selectAll(); + findInput->setFocus(); + + const TextDocument& doc = editor->getDocument(); + + if ( doc.getSelection().hasSelection() && doc.getSelection().inSameLine() ) { + String text = doc.getSelectedText(); + if ( !text.empty() ) { + findInput->setText( text ); + findInput->getDocument().selectAll(); + } else if ( !findInput->getText().empty() ) { + findInput->getDocument().selectAll(); + } + } else if ( doc.getSelection().hasSelection() ) { + mSearchState.range = doc.getSelection( true ); + if ( !findInput->getText().empty() ) + findInput->getDocument().selectAll(); + } + mSearchState.text = findInput->getText(); + editor->setHighlightTextRange( mSearchState.range ); + editor->setHighlightWord( mSearchState.text ); + editor->getDocument().setActiveClient( editor ); +} + +bool DocSearchController::findPrevText( SearchState& search ) { + if ( search.text.empty() ) + search.text = mLastSearch; + if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) || search.text.empty() ) + return false; + + search.editor->getDocument().setActiveClient( search.editor ); + mLastSearch = search.text; + TextDocument& doc = search.editor->getDocument(); + TextRange range = doc.getDocRange(); + TextPosition from = doc.getSelection( true ).start(); + if ( search.range.isValid() ) { + range = doc.sanitizeRange( search.range ).normalized(); + from = from < range.start() ? range.start() : from; + } + + TextPosition found = + doc.findLast( search.text, from, search.caseSensitive, search.wholeWord, search.range ); + if ( found.isValid() ) { + doc.setSelection( { doc.positionOffset( found, search.text.size() ), found } ); + return true; + } else { + found = doc.findLast( search.text, range.end() ); + if ( found.isValid() ) { + doc.setSelection( { doc.positionOffset( found, search.text.size() ), found } ); + return true; + } + } + return false; +} + +bool DocSearchController::findNextText( SearchState& search ) { + if ( search.text.empty() ) + search.text = mLastSearch; + if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) || search.text.empty() ) + return false; + + search.editor->getDocument().setActiveClient( search.editor ); + mLastSearch = search.text; + TextDocument& doc = search.editor->getDocument(); + TextRange range = doc.getDocRange(); + TextPosition from = doc.getSelection( true ).end(); + if ( search.range.isValid() ) { + range = doc.sanitizeRange( search.range ).normalized(); + from = from < range.start() ? range.start() : from; + } + + TextRange found = + doc.find( search.text, from, search.caseSensitive, search.wholeWord, search.type, range ); + if ( found.isValid() ) { + doc.setSelection( found.reversed() ); + return true; + } else { + found = doc.find( search.text, range.start(), search.caseSensitive, search.wholeWord, + search.type, range ); + if ( found.isValid() ) { + doc.setSelection( found.reversed() ); + return true; + } + } + return false; +} + +bool DocSearchController::replaceSelection( SearchState& search, const String& replacement ) { + if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) || + !search.editor->getDocument().hasSelection() ) + return false; + search.editor->getDocument().setActiveClient( search.editor ); + search.editor->getDocument().replaceSelection( replacement ); + return true; +} + +int DocSearchController::replaceAll( SearchState& search, const String& replace ) { + if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) ) + return 0; + if ( search.text.empty() ) + search.text = mLastSearch; + if ( search.text.empty() ) + return 0; + search.editor->getDocument().setActiveClient( search.editor ); + mLastSearch = search.text; + TextDocument& doc = search.editor->getDocument(); + TextPosition startedPosition = doc.getSelection().start(); + int count = doc.replaceAll( search.text, replace, search.caseSensitive, search.wholeWord, + search.type, search.range ); + doc.setSelection( startedPosition ); + return count; +} + +bool DocSearchController::findAndReplace( SearchState& search, const String& replace ) { + if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) ) + return false; + if ( search.text.empty() ) + search.text = mLastSearch; + if ( search.text.empty() ) + return false; + search.editor->getDocument().setActiveClient( search.editor ); + mLastSearch = search.text; + TextDocument& doc = search.editor->getDocument(); + if ( doc.hasSelection() && doc.getSelectedText() == search.text ) { + return replaceSelection( search, replace ); + } else { + return findNextText( search ); + } +} + +void DocSearchController::hideSearchBar() { + mSearchBarLayout->setEnabled( false )->setVisible( false ); +} + +void DocSearchController::onCodeEditorFocusChange( UICodeEditor* editor ) { + + if ( mSearchState.editor && mSearchState.editor != editor ) { + String word = mSearchState.editor->getHighlightWord(); + mSearchState.editor->setHighlightWord( "" ); + mSearchState.editor->setHighlightTextRange( TextRange() ); + mSearchState.text = ""; + mSearchState.range = TextRange(); + if ( editor ) { + mSearchState.editor = editor; + mSearchState.editor->setHighlightWord( word ); + mSearchState.range = TextRange(); + } + } +} + +SearchState& DocSearchController::getSearchState() { + return mSearchState; +} diff --git a/src/tools/codeeditor/docsearchcontroller.hpp b/src/tools/codeeditor/docsearchcontroller.hpp new file mode 100644 index 000000000..6e7a9bad7 --- /dev/null +++ b/src/tools/codeeditor/docsearchcontroller.hpp @@ -0,0 +1,55 @@ +#ifndef DOCSEARCHCONTROLLER_HPP +#define DOCSEARCHCONTROLLER_HPP + +#include + +struct SearchState { + UICodeEditor* editor{ nullptr }; + String text; + TextRange range = TextRange(); + bool caseSensitive{ false }; + bool wholeWord{ false }; + TextDocument::FindReplaceType type{ TextDocument::FindReplaceType::Normal }; + void reset() { + editor = nullptr; + range = TextRange(); + text = ""; + } +}; + +class App; +class UISearchBar; + +class DocSearchController { + public: + DocSearchController( UICodeEditorSplitter*, App* app ); + + void initSearchBar( UISearchBar* searchBar ); + + void showFindView(); + + bool findPrevText( SearchState& search ); + + bool findNextText( SearchState& search ); + + bool replaceSelection( SearchState& search, const String& replacement ); + + int replaceAll( SearchState& search, const String& replace ); + + bool findAndReplace( SearchState& search, const String& replace ); + + void hideSearchBar(); + + void onCodeEditorFocusChange( UICodeEditor* editor ); + + SearchState& getSearchState(); + + protected: + UICodeEditorSplitter* mEditorSplitter{ nullptr }; + UISearchBar* mSearchBarLayout{ nullptr }; + App* mApp{ nullptr }; + SearchState mSearchState; + String mLastSearch; +}; + +#endif // DOCSEARCHCONTROLLER_HPP diff --git a/src/tools/codeeditor/filelocator.cpp b/src/tools/codeeditor/filelocator.cpp new file mode 100644 index 000000000..908f65423 --- /dev/null +++ b/src/tools/codeeditor/filelocator.cpp @@ -0,0 +1,147 @@ +#include "filelocator.hpp" +#include "codeeditor.hpp" + +static int LOCATEBAR_MAX_VISIBLE_ITEMS = 18; +static int LOCATEBAR_MAX_RESULTS = 100; + +FileLocator::FileLocator( UICodeEditorSplitter* editorSplitter, UISceneNode* sceneNode, App* app ) : + mEditorSplitter( editorSplitter ), mUISceneNode( sceneNode ), mApp( app ) {} + +void FileLocator::hideLocateBar() { + mLocateBarLayout->setVisible( false ); + mLocateTable->setVisible( false ); +} + +void FileLocator::updateLocateTable() { + if ( !mLocateInput->getText().empty() ) { +#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) + mApp->getDirTree()->asyncFuzzyMatchTree( + mLocateInput->getText(), LOCATEBAR_MAX_RESULTS, [&]( auto res ) { + mUISceneNode->runOnMainThread( [&, res] { + mLocateTable->setModel( res ); + mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); + } ); + } ); +#else + mLocateTable->setModel( + mDirTree->fuzzyMatchTree( mLocateInput->getText(), LOCATEBAR_MAX_RESULTS ) ); + mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); +#endif + } else { + mLocateTable->setModel( mApp->getDirTree()->asModel( LOCATEBAR_MAX_RESULTS ) ); + mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); + } +} + +void FileLocator::goToLine() { + showLocateBar(); + mLocateInput->setText( "l " ); +} + +void FileLocator::initLocateBar( UILocateBar* locateBar, UITextInput* locateInput ) { + mLocateBarLayout = locateBar; + mLocateInput = locateInput; + auto addClickListener = [&]( UIWidget* widget, std::string cmd ) { + widget->addEventListener( Event::MouseClick, [this, cmd]( const Event* event ) { + const MouseEvent* mouseEvent = static_cast( event ); + if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) + mLocateBarLayout->execute( cmd ); + } ); + }; + mLocateTable = UITableView::New(); + mLocateTable->setId( "locate_bar_table" ); + mLocateTable->setParent( mUISceneNode->getRoot() ); + mLocateTable->setHeadersVisible( false ); + mLocateTable->setVisible( false ); + mLocateInput->addEventListener( Event::OnTextChanged, [&]( const Event* ) { + if ( mEditorSplitter->getCurEditor() && + String::startsWith( mLocateInput->getText(), String( "l " ) ) ) { + String number( mLocateInput->getText().substr( 2 ) ); + Int64 val; + if ( String::fromString( val, number ) && val - 1 >= 0 ) { + mEditorSplitter->getCurEditor()->goToLine( { val - 1, 0 } ); + mLocateTable->setVisible( false ); + } + } else { + mLocateTable->setVisible( true ); + Vector2f pos( mLocateInput->convertToWorldSpace( { 0, 0 } ) ); + pos.y -= mLocateTable->getPixelsSize().getHeight(); + mLocateTable->setPixelsPosition( pos ); + if ( !mApp->isDirTreeReady() ) + return; + updateLocateTable(); + } + } ); + mLocateInput->addEventListener( Event::OnPressEnter, [&]( const Event* ) { + KeyEvent keyEvent( mLocateTable, Event::KeyDown, KEY_RETURN, 0, 0 ); + mLocateTable->forceKeyDown( keyEvent ); + } ); + mLocateInput->addEventListener( Event::KeyDown, [&]( const Event* event ) { + const KeyEvent* keyEvent = static_cast( event ); + mLocateTable->forceKeyDown( *keyEvent ); + } ); + mLocateBarLayout->addCommand( "close-locatebar", [&] { + hideLocateBar(); + mEditorSplitter->getCurEditor()->setFocus(); + } ); + mLocateBarLayout->getKeyBindings().addKeybindsString( { + { "escape", "close-locatebar" }, + } ); + mLocateTable->addEventListener( Event::KeyDown, [&]( const Event* event ) { + const KeyEvent* keyEvent = static_cast( event ); + if ( keyEvent->getKeyCode() == KEY_ESCAPE ) + mLocateBarLayout->execute( "close-locatebar" ); + } ); + addClickListener( mLocateBarLayout->find( "locatebar_close" ), "close-locatebar" ); + mLocateTable->addEventListener( Event::OnModelEvent, [&]( const Event* event ) { + const ModelEvent* modelEvent = static_cast( event ); + if ( modelEvent->getModelEventType() == ModelEventType::Open ) { + Variant vPath( modelEvent->getModel()->data( + modelEvent->getModel()->index( modelEvent->getModelIndex().row(), 1 ), + Model::Role::Display ) ); + if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { + std::string path( vPath.asCStr() ); + UITab* tab = mEditorSplitter->isDocumentOpen( path ); + if ( !tab ) { + FileInfo fileInfo( path ); + if ( fileInfo.exists() && fileInfo.isRegularFile() ) + mApp->loadFileFromPath( path ); + } else { + tab->getTabWidget()->setTabSelected( tab ); + } + mLocateBarLayout->execute( "close-locatebar" ); + } + } + } ); +} + +void FileLocator::updateLocateBar() { + mLocateBarLayout->runOnMainThread( [&] { + Float width = eeceil( mLocateInput->getPixelsSize().getWidth() ); + mLocateTable->setPixelsSize( width, + mLocateTable->getRowHeight() * LOCATEBAR_MAX_VISIBLE_ITEMS ); + width -= mLocateTable->getVerticalScrollBar()->getPixelsSize().getWidth(); + mLocateTable->setColumnWidth( 0, eeceil( width * 0.5 ) ); + mLocateTable->setColumnWidth( 1, width - mLocateTable->getColumnWidth( 0 ) ); + Vector2f pos( mLocateInput->convertToWorldSpace( { 0, 0 } ) ); + pos.y -= mLocateTable->getPixelsSize().getHeight(); + mLocateTable->setPixelsPosition( pos ); + } ); +} + +void FileLocator::showLocateBar() { + mApp->hideGlobalSearchBar(); + mApp->hideSearchBar(); + + mLocateBarLayout->setVisible( true ); + mLocateInput->setFocus(); + mLocateTable->setVisible( true ); + mLocateInput->getDocument().selectAll(); + mLocateInput->addEventListener( Event::OnSizeChange, + [&]( const Event* ) { updateLocateBar(); } ); + if ( mApp->getDirTree() && !mLocateTable->getModel() ) { + mLocateTable->setModel( mApp->getDirTree()->asModel( LOCATEBAR_MAX_RESULTS ) ); + mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) ); + } + updateLocateBar(); +} diff --git a/src/tools/codeeditor/filelocator.hpp b/src/tools/codeeditor/filelocator.hpp new file mode 100644 index 000000000..dbed66905 --- /dev/null +++ b/src/tools/codeeditor/filelocator.hpp @@ -0,0 +1,34 @@ +#ifndef FILELOCATOR_HPP +#define FILELOCATOR_HPP + +#include "widgetcommandexecuter.hpp" +#include + +class App; + +class FileLocator { + public: + FileLocator( UICodeEditorSplitter* editorSplitter, UISceneNode* sceneNode, App* app ); + + void initLocateBar( UILocateBar* locateBar, UITextInput* locateInput ); + + void showLocateBar(); + + void hideLocateBar(); + + void goToLine(); + + void updateLocateTable(); + + protected: + UILocateBar* mLocateBarLayout{ nullptr }; + UITableView* mLocateTable{ nullptr }; + UITextInput* mLocateInput{ nullptr }; + UICodeEditorSplitter* mEditorSplitter{ nullptr }; + UISceneNode* mUISceneNode{ nullptr }; + App* mApp{ nullptr }; + + void updateLocateBar(); +}; + +#endif // FILELOCATOR_HPP diff --git a/src/tools/codeeditor/globalsearchcontroller.cpp b/src/tools/codeeditor/globalsearchcontroller.cpp new file mode 100644 index 000000000..d11709e99 --- /dev/null +++ b/src/tools/codeeditor/globalsearchcontroller.cpp @@ -0,0 +1,434 @@ +#include "globalsearchcontroller.hpp" +#include "codeeditor.hpp" +#include "uitreeviewglobalsearch.hpp" + +static int LOCATEBAR_MAX_VISIBLE_ITEMS = 18; + +GlobalSearchController::GlobalSearchController( UICodeEditorSplitter* editorSplitter, + UISceneNode* sceneNode, App* app ) : + mEditorSplitter( editorSplitter ), mUISceneNode( sceneNode ), mApp( app ) {} + +static String replaceInText( String text, const String& replaceText, + std::vector replacements ) { + Int64 diff = 0; + String oldText( text ); + for ( const auto& range : replacements ) { + Int64 len = range.end().column() - range.start().column(); + auto before = text.substr( 0, range.start().column() + diff ); + auto after = !text.empty() ? text.substr( range.end().column() + diff ) : ""; + text = before + replaceText + after; + diff += replaceText.size() - len; + } + return text; +} + +void GlobalSearchController::replaceInFiles( const String& replaceText, + std::shared_ptr model ) { + const ProjectSearch::Result& res = model.get()->getResult(); + for ( const auto& fileResult : res ) { + std::map>> replaceRangeMap; + std::map replaceStringMap; + for ( const auto& result : fileResult.results ) { + if ( result.selected ) { + replaceRangeMap[result.position.start().line()].first = result.line; + replaceRangeMap[result.position.start().line()].second.push_back( result.position ); + } + } + + for ( const auto& replace : replaceRangeMap ) + replaceStringMap[replace.second.second.front().start().line()] = + replaceInText( replace.second.first, replaceText, replace.second.second ); + + std::shared_ptr doc = mEditorSplitter->findDocFromPath( fileResult.file ); + bool loaded = doc ? true : false; + bool save = false; + bool inMemoryAndNotDirty = doc ? !doc->isDirty() : false; + + if ( !doc ) { + doc = std::make_shared(); + loaded = doc->loadFromFile( fileResult.file ); + save = true; + } + + if ( doc && loaded ) { + for ( const auto& replaceString : replaceStringMap ) + doc->replaceLine( replaceString.first, replaceString.second ); + + if ( save || inMemoryAndNotDirty ) + doc->save(); + } + } +} +void GlobalSearchController::initGlobalSearchBar( UIGlobalSearchBar* globalSearchBar ) { + mGlobalSearchBarLayout = globalSearchBar; + mGlobalSearchBarLayout->setVisible( false )->setEnabled( false ); + auto addClickListener = [&]( UIWidget* widget, std::string cmd ) { + widget->addEventListener( Event::MouseClick, [this, cmd]( const Event* event ) { + const MouseEvent* mouseEvent = static_cast( event ); + if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) + mGlobalSearchBarLayout->execute( cmd ); + } ); + }; + UIPushButton* searchButton = mGlobalSearchBarLayout->find( "global_search" ); + UIPushButton* searchReplaceButton = + mGlobalSearchBarLayout->find( "global_search_replace" ); + UICheckBox* caseSensitiveChk = mGlobalSearchBarLayout->find( "case_sensitive" ); + UICheckBox* wholeWordChk = mGlobalSearchBarLayout->find( "whole_word" ); + UICheckBox* luaPatternChk = mGlobalSearchBarLayout->find( "lua_pattern" ); + UIWidget* searchBarClose = mGlobalSearchBarLayout->find( "global_searchbar_close" ); + mGlobalSearchInput = mGlobalSearchBarLayout->find( "global_search_find" ); + mGlobalSearchHistoryList = + mGlobalSearchBarLayout->find( "global_search_history" ); + mGlobalSearchBarLayout->addCommand( + "search-in-files", [&, caseSensitiveChk, wholeWordChk, luaPatternChk] { + doGlobalSearch( mGlobalSearchInput->getText(), caseSensitiveChk->isChecked(), + wholeWordChk->isChecked(), luaPatternChk->isChecked(), false ); + } ); + mGlobalSearchBarLayout->addCommand( + "search-replace-in-files", [&, caseSensitiveChk, wholeWordChk, luaPatternChk] { + doGlobalSearch( mGlobalSearchInput->getText(), caseSensitiveChk->isChecked(), + wholeWordChk->isChecked(), luaPatternChk->isChecked(), true ); + } ); + mGlobalSearchBarLayout->addCommand( + "search-again", [&, caseSensitiveChk, wholeWordChk, luaPatternChk] { + auto listBox = mGlobalSearchHistoryList->getListBox(); + if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) { + doGlobalSearch( mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - + listBox->getItemSelectedIndex()] + .first, + caseSensitiveChk->isChecked(), wholeWordChk->isChecked(), + luaPatternChk->isChecked(), + mGlobalSearchTreeReplace == mGlobalSearchTree, true ); + } + } ); + mGlobalSearchBarLayout->addCommand( "close-global-searchbar", [&] { + hideGlobalSearchBar(); + if ( mEditorSplitter->getCurEditor() ) + mEditorSplitter->getCurEditor()->setFocus(); + } ); + mGlobalSearchBarLayout->getKeyBindings().addKeybindsString( { + { "escape", "close-global-searchbar" }, + { "ctrl+s", "change-case" }, + { "ctrl+w", "change-whole-word" }, + { "ctrl+l", "toggle-lua-pattern" }, + { "ctrl+r", "search-replace-in-files" }, + { "ctrl+g", "search-again" }, + } ); + mGlobalSearchBarLayout->addCommand( "change-case", [&, caseSensitiveChk] { + caseSensitiveChk->setChecked( !caseSensitiveChk->isChecked() ); + } ); + mGlobalSearchBarLayout->addCommand( "change-whole-word", [&, wholeWordChk] { + wholeWordChk->setChecked( !wholeWordChk->isChecked() ); + } ); + mGlobalSearchBarLayout->addCommand( "toggle-lua-pattern", [&, luaPatternChk] { + luaPatternChk->setChecked( !luaPatternChk->isChecked() ); + } ); + mGlobalSearchInput->addEventListener( Event::OnPressEnter, [&]( const Event* ) { + if ( mGlobalSearchInput->hasFocus() ) { + mGlobalSearchBarLayout->execute( "search-in-files" ); + } else { + KeyEvent keyEvent( mGlobalSearchTree, Event::KeyDown, KEY_RETURN, 0, 0 ); + mGlobalSearchTree->forceKeyDown( keyEvent ); + } + } ); + mGlobalSearchInput->addEventListener( Event::KeyDown, [&]( const Event* event ) { + const KeyEvent* keyEvent = static_cast( event ); + Uint32 keyCode = keyEvent->getKeyCode(); + if ( ( keyCode == KEY_UP || keyCode == KEY_DOWN || keyCode == KEY_PAGEUP || + keyCode == KEY_PAGEDOWN || keyCode == KEY_HOME || keyCode == KEY_END ) && + mGlobalSearchTree->forceKeyDown( *keyEvent ) && !mGlobalSearchTree->hasFocus() ) { + mGlobalSearchTree->setFocus(); + } + } ); + mGlobalSearchInput->addEventListener( Event::OnSizeChange, [&]( const Event* ) { + if ( mGlobalSearchBarLayout->isVisible() ) + updateGlobalSearchBar(); + } ); + addClickListener( searchButton, "search-in-files" ); + addClickListener( searchReplaceButton, "search-replace-in-files" ); + addClickListener( searchBarClose, "close-global-searchbar" ); + mGlobalSearchLayout = mUISceneNode + ->loadLayoutFromString( R"xml( + + + + + + + + + + + + + + + )xml", + mUISceneNode->getRoot() ) + ->asType(); + UIPushButton* searchAgainBtn = mGlobalSearchLayout->find( "global_search_again" ); + UITextInput* replaceInput = + mGlobalSearchLayout->find( "global_search_replace_input" ); + UIPushButton* replaceButton = + mGlobalSearchLayout->find( "global_search_replace_button" ); + addClickListener( searchAgainBtn, "search-again" ); + addClickListener( replaceButton, "replace-in-files" ); + replaceInput->addEventListener( Event::OnPressEnter, [&, replaceInput]( const Event* ) { + if ( replaceInput->hasFocus() ) + mGlobalSearchBarLayout->execute( "replace-in-files" ); + } ); + replaceInput->addEventListener( Event::KeyDown, [&]( const Event* event ) { + const KeyEvent* keyEvent = static_cast( event ); + if ( keyEvent->getKeyCode() == KEY_ESCAPE ) + mGlobalSearchBarLayout->execute( "close-global-searchbar" ); + } ); + mGlobalSearchBarLayout->addCommand( "replace-in-files", [&, replaceInput] { + auto listBox = mGlobalSearchHistoryList->getListBox(); + if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) { + const auto& replaceData = mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - + listBox->getItemSelectedIndex()]; + replaceInFiles( replaceInput->getText(), replaceData.second ); + mGlobalSearchBarLayout->execute( "search-again" ); + mGlobalSearchBarLayout->execute( "close-global-searchbar" ); + } + } ); + mGlobalSearchTreeSearch = + UITreeViewGlobalSearch::New( mEditorSplitter->getCurrentColorScheme(), false ); + mGlobalSearchTreeReplace = + UITreeViewGlobalSearch::New( mEditorSplitter->getCurrentColorScheme(), true ); + initGlobalSearchTree( mGlobalSearchTreeSearch ); + initGlobalSearchTree( mGlobalSearchTreeReplace ); + mGlobalSearchTree = mGlobalSearchTreeSearch; +} + +void GlobalSearchController::showGlobalSearch( bool searchReplace ) { + mApp->hideLocateBar(); + mApp->hideSearchBar(); + bool wasReplaceTree = mGlobalSearchTreeReplace == mGlobalSearchTree; + mGlobalSearchTree = searchReplace ? mGlobalSearchTreeReplace : mGlobalSearchTreeSearch; + mGlobalSearchTreeSearch->setVisible( !searchReplace ); + mGlobalSearchTreeReplace->setVisible( searchReplace ); + mGlobalSearchBarLayout->setVisible( true )->setEnabled( true ); + mGlobalSearchInput->setFocus(); + mGlobalSearchLayout->setVisible( true ); + if ( mEditorSplitter->getCurEditor() && + mEditorSplitter->getCurEditor()->getDocument().hasSelection() ) { + mGlobalSearchInput->setText( + mEditorSplitter->getCurEditor()->getDocument().getSelectedText() ); + } + mGlobalSearchInput->getDocument().selectAll(); + auto* loader = mGlobalSearchTree->getParent()->find( "loader" ); + if ( loader ) + loader->setVisible( true ); + if ( !searchReplace ) { + mGlobalSearchLayout->findByClass( "replace_box" )->setVisible( searchReplace ); + if ( wasReplaceTree ) { + updateGlobalSearchBarResults( mGlobalSearchTreeReplace->getSearchStr(), + std::static_pointer_cast( + mGlobalSearchTreeReplace->getModelShared() ), + searchReplace ); + } + } + updateGlobalSearchBar(); +} + +void GlobalSearchController::updateColorScheme( const SyntaxColorScheme& colorScheme ) { + mGlobalSearchTreeSearch->updateColorScheme( colorScheme ); + mGlobalSearchTreeReplace->updateColorScheme( colorScheme ); +} + +bool GlobalSearchController::isUsingSearchReplaceTree() { + return mGlobalSearchTreeReplace == mGlobalSearchTree; +} + +void GlobalSearchController::updateGlobalSearchBar() { + mGlobalSearchBarLayout->runOnMainThread( [&] { + Float width = eeceil( mGlobalSearchInput->getPixelsSize().getWidth() ); + Float rowHeight = mGlobalSearchTree->getRowHeight() * LOCATEBAR_MAX_VISIBLE_ITEMS; + mGlobalSearchLayout->setPixelsSize( width, 0 ); + mGlobalSearchTree->setPixelsSize( width, rowHeight ); + width -= mGlobalSearchTree->getVerticalScrollBar()->getPixelsSize().getWidth(); + mGlobalSearchTree->setColumnWidth( 0, eeceil( width ) ); + Vector2f pos( mGlobalSearchInput->convertToWorldSpace( { 0, 0 } ) ); + pos = PixelDensity::pxToDp( pos ); + mGlobalSearchLayout->setLayoutMarginLeft( pos.x ); + mGlobalSearchLayout->setLayoutMarginTop( pos.y - + mGlobalSearchLayout->getSize().getHeight() ); + } ); +} + +void GlobalSearchController::hideGlobalSearchBar() { + mGlobalSearchBarLayout->setEnabled( false )->setVisible( false ); + mGlobalSearchLayout->setVisible( false ); + auto* loader = mGlobalSearchTree->getParent()->find( "loader" ); + if ( loader ) + loader->setVisible( false ); +} + +void GlobalSearchController::updateGlobalSearchBarResults( + const std::string& search, std::shared_ptr model, + bool searchReplace ) { + updateGlobalSearchBar(); + mGlobalSearchTree->setSearchStr( search ); + mGlobalSearchTree->setModel( model ); + if ( mGlobalSearchTree->getModel()->rowCount() < 50 ) + mGlobalSearchTree->expandAll(); + mGlobalSearchLayout->findByClass( "search_str" )->setText( search ); + mGlobalSearchLayout->findByClass( "search_total" ) + ->setText( String::format( "%zu matches found.", model->resultCount() ) ); + mGlobalSearchLayout->findByClass( "status_box" )->setVisible( true ); + mGlobalSearchLayout->findByClass( "replace_box" )->setVisible( searchReplace ); + if ( searchReplace && mGlobalSearchBarLayout->isVisible() ) { + auto* replaceInput = + mGlobalSearchLayout->find( "global_search_replace_input" ); + replaceInput->setText( search ); + replaceInput->setFocus(); + } +} + +void GlobalSearchController::doGlobalSearch( const String& text, bool caseSensitive, bool wholeWord, + bool luaPattern, bool searchReplace, + bool searchAgain ) { + if ( mApp->getDirTree() && mApp->getDirTree()->getFilesCount() > 0 && !text.empty() ) { + mGlobalSearchTree = searchReplace ? mGlobalSearchTreeReplace : mGlobalSearchTreeSearch; + mGlobalSearchTreeSearch->setVisible( !searchReplace ); + mGlobalSearchTreeReplace->setVisible( searchReplace ); + mGlobalSearchLayout->findByClass( "status_box" )->setVisible( true ); + mGlobalSearchLayout->findByClass( "replace_box" )->setVisible( false ); + mGlobalSearchLayout->findByClass( "search_str" )->setText( text ); + UILoader* loader = UILoader::New(); + loader->setId( "loader" ); + loader->setRadius( 48 ); + loader->setOutlineThickness( 6 ); + loader->setFillColor( Color::Red ); + loader->setParent( mGlobalSearchLayout->getParent() ); + loader->setPosition( mGlobalSearchLayout->getPosition() + + mGlobalSearchLayout->getSize() * 0.5f - loader->getSize() * 0.5f ); + Clock* clock = eeNew( Clock, () ); + std::string search( text.toUtf8() ); + ProjectSearch::find( + mApp->getDirTree()->getFiles(), search, +#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) + mApp->getThreadPool(), +#endif + [&, clock, search, loader, searchReplace, + searchAgain]( const ProjectSearch::Result& res ) { + Log::info( "Global search for \"%s\" took %.2fms", search.c_str(), + clock->getElapsedTime().asMilliseconds() ); + eeDelete( clock ); + mUISceneNode->runOnMainThread( [&, loader, res, search, searchReplace, + searchAgain] { + auto model = ProjectSearch::asModel( res ); + auto listBox = mGlobalSearchHistoryList->getListBox(); + + if ( !searchAgain ) { + mGlobalSearchHistory.push_back( std::make_pair( search, model ) ); + if ( mGlobalSearchHistory.size() > 10 ) + mGlobalSearchHistory.pop_front(); + + std::vector items; + for ( auto item = mGlobalSearchHistory.rbegin(); + item != mGlobalSearchHistory.rend(); item++ ) { + items.push_back( item->first ); + } + + listBox->clear(); + listBox->addListBoxItems( items ); + if ( mGlobalSearchHistoryOnItemSelectedCb ) + mGlobalSearchHistoryList->removeEventListener( + mGlobalSearchHistoryOnItemSelectedCb ); + listBox->setSelected( 0 ); + mGlobalSearchHistoryOnItemSelectedCb = + mGlobalSearchHistoryList->addEventListener( + Event::OnItemSelected, [&, searchReplace]( const Event* ) { + auto idx = mGlobalSearchHistoryList->getListBox() + ->getItemSelectedIndex(); + auto idxItem = mGlobalSearchHistory.at( + mGlobalSearchHistory.size() - 1 - idx ); + updateGlobalSearchBarResults( idxItem.first, idxItem.second, + searchReplace ); + } ); + } else if ( listBox->getItemSelectedIndex() < mGlobalSearchHistory.size() ) { + mGlobalSearchHistory[mGlobalSearchHistory.size() - 1 - + listBox->getItemSelectedIndex()] + .second = model; + } + + updateGlobalSearchBarResults( search, model, searchReplace ); + loader->setVisible( false ); + loader->close(); + } ); + }, + caseSensitive, wholeWord, + luaPattern ? TextDocument::FindReplaceType::LuaPattern + : TextDocument::FindReplaceType::Normal ); + } +} + +void GlobalSearchController::initGlobalSearchTree( UITreeViewGlobalSearch* searchTree ) { + searchTree->addClass( "search_tree" ); + searchTree->setParent( mGlobalSearchLayout ); + searchTree->setVisible( false ); + searchTree->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::Fixed ); + searchTree->setExpanderIconSize( PixelDensity::dpToPx( 20 ) ); + searchTree->setHeadersVisible( false ); + searchTree->setColumnsHidden( + { ProjectSearch::ResultModel::Line, ProjectSearch::ResultModel::ColumnStart }, true ); + searchTree->addEventListener( Event::KeyDown, [&]( const Event* event ) { + const KeyEvent* keyEvent = static_cast( event ); + if ( keyEvent->getKeyCode() == KEY_ESCAPE ) + mGlobalSearchBarLayout->execute( "close-global-searchbar" ); + } ); + searchTree->addEventListener( Event::OnModelEvent, [&]( const Event* event ) { + const ModelEvent* modelEvent = static_cast( event ); + if ( modelEvent->getModelEventType() == ModelEventType::Open ) { + const Model* model = modelEvent->getModel(); + if ( !model ) + return; + if ( mGlobalSearchTreeReplace == mGlobalSearchTree ) { + if ( modelEvent->getTriggerEvent()->getType() == Event::KeyDown ) { + const KeyEvent* keyEvent = + static_cast( modelEvent->getTriggerEvent() ); + if ( keyEvent->getKeyCode() == KEY_SPACE && + keyEvent->getNode()->isType( UI_TYPE_TREEVIEW_CELL ) ) { + auto* cell = + static_cast( keyEvent->getNode() ); + cell->toggleSelected(); + return; + } + } + } + + Variant vPath( model->data( model->index( modelEvent->getModelIndex().internalId(), + ProjectSearch::ResultModel::FileOrPosition ), + Model::Role::Custom ) ); + if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { + std::string path( vPath.asCStr() ); + UITab* tab = mEditorSplitter->isDocumentOpen( path ); + if ( !tab ) { + FileInfo fileInfo( path ); + if ( fileInfo.exists() && fileInfo.isRegularFile() ) + mApp->loadFileFromPath( path ); + } else { + tab->getTabWidget()->setTabSelected( tab ); + } + Variant lineNum( + model->data( model->index( modelEvent->getModelIndex().row(), + ProjectSearch::ResultModel::FileOrPosition, + modelEvent->getModelIndex().parent() ), + Model::Role::Custom ) ); + Variant colNum( model->data( model->index( modelEvent->getModelIndex().row(), + ProjectSearch::ResultModel::ColumnStart, + modelEvent->getModelIndex().parent() ), + Model::Role::Custom ) ); + if ( mEditorSplitter->getCurEditor() && lineNum.isValid() && colNum.isValid() && + lineNum.is( Variant::Type::Int64 ) && colNum.is( Variant::Type::Int64 ) ) { + TextPosition pos{ lineNum.asInt64(), colNum.asInt64() }; + mEditorSplitter->getCurEditor()->getDocument().setSelection( pos ); + mEditorSplitter->getCurEditor()->goToLine( pos ); + hideGlobalSearchBar(); + } + } + } + } ); +} diff --git a/src/tools/codeeditor/globalsearchcontroller.hpp b/src/tools/codeeditor/globalsearchcontroller.hpp new file mode 100644 index 000000000..cb12c3aa9 --- /dev/null +++ b/src/tools/codeeditor/globalsearchcontroller.hpp @@ -0,0 +1,56 @@ +#ifndef GLOBALSEARCHCONTROLLER_HPP +#define GLOBALSEARCHCONTROLLER_HPP + +#include "projectsearch.hpp" +#include + +class App; +class UIGlobalSearchBar; +class UITreeViewGlobalSearch; + +class GlobalSearchController { + public: + GlobalSearchController( UICodeEditorSplitter*, UISceneNode*, App* ); + + void updateGlobalSearchBar(); + + void initGlobalSearchBar( UIGlobalSearchBar* globalSearchBar ); + + void hideGlobalSearchBar(); + + void updateGlobalSearchBarResults( const std::string& search, + std::shared_ptr model, + bool searchReplace ); + + void initGlobalSearchTree( UITreeViewGlobalSearch* searchTree ); + + void doGlobalSearch( const String& text, bool caseSensitive, bool wholeWord, bool luaPattern, + bool searchReplace, bool searchAgain = false ); + + void replaceInFiles( const String& replaceText, + std::shared_ptr model ); + + void showGlobalSearch( bool searchAndReplace = false ); + + void updateColorScheme( const SyntaxColorScheme& colorScheme ); + + bool isUsingSearchReplaceTree(); + + protected: + UICodeEditorSplitter* mEditorSplitter{ nullptr }; + UISceneNode* mUISceneNode{ nullptr }; + App* mApp{ nullptr }; + + UIGlobalSearchBar* mGlobalSearchBarLayout{ nullptr }; + UILayout* mGlobalSearchLayout{ nullptr }; + UITreeViewGlobalSearch* mGlobalSearchTree{ nullptr }; + UITreeViewGlobalSearch* mGlobalSearchTreeSearch{ nullptr }; + UITreeViewGlobalSearch* mGlobalSearchTreeReplace{ nullptr }; + UITextInput* mGlobalSearchInput{ nullptr }; + UIDropDownList* mGlobalSearchHistoryList{ nullptr }; + Uint32 mGlobalSearchHistoryOnItemSelectedCb{ 0 }; + std::deque>> + mGlobalSearchHistory; +}; + +#endif // GLOBALSEARCHCONTROLLER_HPP diff --git a/src/tools/codeeditor/widgetcommandexecuter.hpp b/src/tools/codeeditor/widgetcommandexecuter.hpp new file mode 100644 index 000000000..22a1b163a --- /dev/null +++ b/src/tools/codeeditor/widgetcommandexecuter.hpp @@ -0,0 +1,78 @@ +#ifndef WIDGETCOMMANDEXECUTER_HPP +#define WIDGETCOMMANDEXECUTER_HPP + +#include + +class WidgetCommandExecuter { + public: + typedef std::function CommandCallback; + + WidgetCommandExecuter( const KeyBindings& keybindings ) : mKeyBindings( keybindings ) {} + + void addCommand( const std::string& name, const CommandCallback& cb ) { mCommands[name] = cb; } + + void execute( const std::string& command ) { + auto cmdIt = mCommands.find( command ); + if ( cmdIt != mCommands.end() ) + cmdIt->second(); + } + + KeyBindings& getKeyBindings() { return mKeyBindings; } + + protected: + KeyBindings mKeyBindings; + std::unordered_map> mCommands; + + Uint32 onKeyDown( const KeyEvent& event ) { + std::string cmd = + mKeyBindings.getCommandFromKeyBind( { event.getKeyCode(), event.getMod() } ); + if ( !cmd.empty() ) { + auto cmdIt = mCommands.find( cmd ); + if ( cmdIt != mCommands.end() ) { + cmdIt->second(); + return 1; + } + } + return 0; + } +}; + +class UISearchBar : public UILinearLayout, public WidgetCommandExecuter { + public: + static UISearchBar* New() { return eeNew( UISearchBar, () ); } + + UISearchBar() : + UILinearLayout( "searchbar", UIOrientation::Horizontal ), + WidgetCommandExecuter( getUISceneNode()->getWindow()->getInput() ) {} + + virtual Uint32 onKeyDown( const KeyEvent& event ) { + return WidgetCommandExecuter::onKeyDown( event ); + } +}; + +class UILocateBar : public UILinearLayout, public WidgetCommandExecuter { + public: + static UILocateBar* New() { return eeNew( UILocateBar, () ); } + UILocateBar() : + UILinearLayout( "locatebar", UIOrientation::Horizontal ), + WidgetCommandExecuter( getUISceneNode()->getWindow()->getInput() ) {} + + virtual Uint32 onKeyDown( const KeyEvent& event ) { + return WidgetCommandExecuter::onKeyDown( event ); + } +}; + +class UIGlobalSearchBar : public UILinearLayout, public WidgetCommandExecuter { + public: + static UIGlobalSearchBar* New() { return eeNew( UIGlobalSearchBar, () ); } + + UIGlobalSearchBar() : + UILinearLayout( "globalsearchbar", UIOrientation::Vertical ), + WidgetCommandExecuter( getUISceneNode()->getWindow()->getInput() ) {} + + virtual Uint32 onKeyDown( const KeyEvent& event ) { + return WidgetCommandExecuter::onKeyDown( event ); + } +}; + +#endif // WIDGETCOMMANDEXECUTER_HPP