diff --git a/TODO.md b/TODO.md index 2aed4a029..3b83aa7c6 100644 --- a/TODO.md +++ b/TODO.md @@ -33,10 +33,6 @@ Detect errors and log them. * Display number of results in search and number of replacements. -* Add support for global/project search. - -* Add support for find and open file. - ## UI Editor * Integrate the `UICodeEditor` into the editor in order to be able to edit the layouts and CSS in app. diff --git a/include/eepp/graphics/console.hpp b/include/eepp/graphics/console.hpp index 24bceaaee..cbdc0d8f5 100644 --- a/include/eepp/graphics/console.hpp +++ b/include/eepp/graphics/console.hpp @@ -141,6 +141,7 @@ class EE_API Console : protected LogReaderInterface { const bool& isFading() const; protected: + Mutex mMutex; std::map mCallbacks; std::deque mCmdLog; std::deque mLastCommands; diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index cbf6cd83a..fcb42ac63 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -44,7 +44,7 @@ class EE_API TextDocument { virtual void onDocumentClosed( TextDocument* ) {} }; - TextDocument(); + TextDocument( bool verbose = true ); ~TextDocument(); @@ -337,6 +337,7 @@ class EE_API TextDocument { bool mAutoDetectIndentType{true}; bool mForceNewLineAtEndOfFile{false}; bool mTrimTrailingWhitespaces{false}; + bool mVerbose{false}; Uint32 mIndentWidth{4}; IndentType mIndentType{IndentType::IndentTabs}; Clock mTimer; diff --git a/include/eepp/ui/models/model.hpp b/include/eepp/ui/models/model.hpp index 4e4741447..b2c721f96 100644 --- a/include/eepp/ui/models/model.hpp +++ b/include/eepp/ui/models/model.hpp @@ -27,12 +27,7 @@ class EE_API Model { InvalidateAllIndexes = 1 << 0, }; - enum class Role { - Display, - Icon, - Sort, - Custom - }; + enum class Role { Display, Icon, Sort, Custom }; virtual ~Model(){}; @@ -91,7 +86,8 @@ class EE_API Model { void onModelUpdate( unsigned flags = UpdateFlag::InvalidateAllIndexes ); - ModelIndex createIndex( int row, int column, const void* data = nullptr ) const; + ModelIndex createIndex( int row, int column, const void* data = nullptr, + const Int64& internalId = 0 ) const; private: std::unordered_set mViews; diff --git a/include/eepp/ui/models/modelindex.hpp b/include/eepp/ui/models/modelindex.hpp index b5bb32fea..3083ad10f 100644 --- a/include/eepp/ui/models/modelindex.hpp +++ b/include/eepp/ui/models/modelindex.hpp @@ -2,6 +2,7 @@ #define EE_UI_MODEL_MODELINDEX_HPP #include +#include namespace EE { namespace UI { namespace Models { @@ -19,6 +20,8 @@ class EE_API ModelIndex { void* data() const { return mData; } + Int64 internalId() const { return mInternalId; } + ModelIndex parent() const; bool hasParent() const { return parent().isValid(); } @@ -40,11 +43,17 @@ class EE_API ModelIndex { Int64 mRow{-1}; Int64 mColumn{-1}; void* mData{nullptr}; + Int64 mInternalId{0}; - ModelIndex( const Model& model, int row, int column, void* internalData ) : - mModel( &model ), mRow( row ), mColumn( column ), mData( internalData ) {} + ModelIndex( const Model& model, int row, int column, void* internalData, + const Int64& internalId = 0 ) : + mModel( &model ), + mRow( row ), + mColumn( column ), + mData( internalData ), + mInternalId( internalId ) {} }; -}}} // namespace EE::UI::Model +}}} // namespace EE::UI::Models #endif // EE_UI_MODEL_MODELINDEX_HPP diff --git a/include/eepp/ui/uitreeview.hpp b/include/eepp/ui/uitreeview.hpp index a569191b7..ef857ccca 100644 --- a/include/eepp/ui/uitreeview.hpp +++ b/include/eepp/ui/uitreeview.hpp @@ -32,14 +32,22 @@ class EE_API UITreeView : public UIAbstractTableView { bool isExpanded( const ModelIndex& index ) const; + void expandAll( const ModelIndex& index = {} ); + + void contractAll( const ModelIndex& index = {} ); + Drawable* getExpandIcon() const; void setExpandedIcon( Drawable* expandIcon ); + void setExpandedIcon( const std::string& expandIcon ); + Drawable* getContractIcon() const; void setContractedIcon( Drawable* contractIcon ); + void setContractedIcon( const std::string& contractIcon ); + bool getExpandersAsIcons() const; void setExpandersAsIcons( bool expandersAsIcons ); @@ -87,6 +95,8 @@ class EE_API UITreeView : public UIAbstractTableView { virtual void onOpenTreeModelIndex( const ModelIndex& index, bool open ); void updateContentSize(); + + void setAllExpanded( const ModelIndex& index = {}, bool expanded = true ); }; }} // namespace EE::UI diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 00f68e504..e6ff11968 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -1013,6 +1013,8 @@ ../../src/tools/codeeditor/ignorematcher.hpp ../../src/tools/codeeditor/projectdirectorytree.cpp ../../src/tools/codeeditor/projectdirectorytree.hpp +../../src/tools/codeeditor/projectsearch.cpp +../../src/tools/codeeditor/projectsearch.hpp ../../src/tools/codeeditor/uicodeeditorsplitter.cpp ../../src/tools/codeeditor/uicodeeditorsplitter.hpp ../../src/tools/mapeditor/mapeditor.cpp diff --git a/src/eepp/graphics/console.cpp b/src/eepp/graphics/console.cpp index c47ead9fa..fbf6aa306 100755 --- a/src/eepp/graphics/console.cpp +++ b/src/eepp/graphics/console.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -217,6 +218,7 @@ void Console::addCommand( const String& Command, ConsoleCallback CB ) { } void Console::draw( const Time& elapsedTime ) { + Lock l( mMutex ); if ( mEnabled && NULL != mFontStyleConfig.Font ) { fade( elapsedTime == Time::Zero ? mWindow->getElapsed() : elapsedTime ); @@ -418,6 +420,7 @@ void Console::processLine() { } void Console::privPushText( const String& str ) { + Lock l( mMutex ); mCmdLog.push_back( str ); if ( mCmdLog.size() >= mMaxLogLines ) @@ -726,7 +729,12 @@ void Console::privInputCallback( InputEvent* Event ) { } if ( KeyCode == KEY_HOME ) { - if ( static_cast( mCmdLog.size() ) > linesOnScreen() ) + size_t size; + { + Lock l( mMutex ); + size = mCmdLog.size(); + } + if ( static_cast( size ) > linesOnScreen() ) mCon.ConModif = mCon.ConMin; } diff --git a/src/eepp/system/iostreamfile.cpp b/src/eepp/system/iostreamfile.cpp index 2744aa40d..864878a05 100644 --- a/src/eepp/system/iostreamfile.cpp +++ b/src/eepp/system/iostreamfile.cpp @@ -9,7 +9,6 @@ IOStreamFile* IOStreamFile::New( const std::string& path, const char* modes ) { IOStreamFile::IOStreamFile( const std::string& path, const char* modes ) : mFS( NULL ), mSize( 0 ) { mFS = std::fopen( path.c_str(), modes ); - ; } IOStreamFile::~IOStreamFile() { diff --git a/src/eepp/system/log.cpp b/src/eepp/system/log.cpp index 3aa97374b..e0301c7da 100644 --- a/src/eepp/system/log.cpp +++ b/src/eepp/system/log.cpp @@ -53,7 +53,9 @@ void Log::write( std::string Text, const bool& newLine ) { Text += '\n'; } + lock(); mData += Text; + unlock(); writeToReaders( Text ); @@ -115,7 +117,9 @@ void Log::writef( const char* format, ... ) { tstr.resize( n ); tstr += '\n'; + lock(); mData += tstr; + unlock(); writeToReaders( tstr ); @@ -169,8 +173,10 @@ void Log::setConsoleOutput( const bool& output ) { mConsoleOutput = output; if ( !OldOutput && output ) { + lock(); std::string data( mData ); mData = ""; + unlock(); write( data, false ); } } diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index d18d85623..d397c039a 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -22,7 +22,7 @@ bool TextDocument::isNonWord( String::StringBaseType ch ) const { return mNonWordChars.find_first_of( ch ) != String::InvalidPos; } -TextDocument::TextDocument() : +TextDocument::TextDocument( bool verbose ) : mUndoStack( this ), mDefaultFileName( "untitled" ), mCleanChangeId( 0 ), @@ -149,8 +149,9 @@ bool TextDocument::loadFromStream( IOStream& file, std::string path ) { notifyTextChanged(); - eePRINTL( "Document \"%s\" loaded in %.2fms.", path.c_str(), - clock.getElapsedTime().asMilliseconds() ); + if ( mVerbose ) + eePRINTL( "Document \"%s\" loaded in %.2fms.", path.c_str(), + clock.getElapsedTime().asMilliseconds() ); return true; } diff --git a/src/eepp/ui/models/model.cpp b/src/eepp/ui/models/model.cpp index f91c3eb53..c51a4aae1 100644 --- a/src/eepp/ui/models/model.cpp +++ b/src/eepp/ui/models/model.cpp @@ -26,8 +26,9 @@ void Model::registerView( UIAbstractView* view ) { mViews.insert( view ); } -ModelIndex Model::createIndex( int row, int column, const void* data ) const { - return ModelIndex( *this, row, column, const_cast( data ) ); +ModelIndex Model::createIndex( int row, int column, const void* data, + const Int64& internalId ) const { + return ModelIndex( *this, row, column, const_cast( data ), internalId ); } void Model::setOnUpdate( const std::function& onUpdate ) { diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index cac85d119..bf5403e70 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -639,6 +639,7 @@ Uint32 UICodeEditor::onFocusLoss() { mMouseDown = false; mCursorVisible = false; getSceneNode()->getWindow()->stopTextInput(); + getUISceneNode()->setCursor( Cursor::Arrow ); if ( mDoc->getActiveClient() == this ) mDoc->setActiveClient( nullptr ); for ( auto& module : mModules ) diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index 9ad483bfd..085d5751f 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -383,6 +383,33 @@ bool UITreeView::isExpanded( const ModelIndex& index ) const { return getIndexMetadata( index ).open; } +void UITreeView::setAllExpanded( const ModelIndex& index, bool expanded ) { + Model& model = *getModel(); + size_t count = model.rowCount( index ); + for ( size_t i = 0; i < count; i++ ) { + auto curIndex = model.index( i, model.treeColumn(), index ); + getIndexMetadata( curIndex ).open = expanded; + if ( model.rowCount( curIndex ) > 0 ) + setAllExpanded( curIndex, expanded ); + } + + createOrUpdateColumns(); +} + +void UITreeView::expandAll( const ModelIndex& index ) { + if ( !getModel() ) + return; + setAllExpanded( index, true ); + createOrUpdateColumns(); +} + +void UITreeView::contractAll( const ModelIndex& index ) { + if ( !getModel() ) + return; + setAllExpanded( index, false ); + createOrUpdateColumns(); +} + Drawable* UITreeView::getExpandIcon() const { return mExpandIcon; } @@ -394,6 +421,10 @@ void UITreeView::setExpandedIcon( Drawable* expandIcon ) { } } +void UITreeView::setExpandedIcon( const std::string& expandIcon ) { + setExpandedIcon( mUISceneNode->findIcon( expandIcon ) ); +} + Drawable* UITreeView::getContractIcon() const { return mContractIcon; } @@ -405,6 +436,10 @@ void UITreeView::setContractedIcon( Drawable* contractIcon ) { } } +void UITreeView::setContractedIcon( const std::string& contractIcon ) { + setContractedIcon( mUISceneNode->findIcon( contractIcon ) ); +} + bool UITreeView::getExpandersAsIcons() const { return mExpandersAsIcons; } diff --git a/src/tests/ui_perf_test/ui_perf_test.cpp b/src/tests/ui_perf_test/ui_perf_test.cpp index 683ca8015..b2521195b 100644 --- a/src/tests/ui_perf_test/ui_perf_test.cpp +++ b/src/tests/ui_perf_test/ui_perf_test.cpp @@ -192,8 +192,8 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { Clock clock; auto model = FileSystemModel::New( "." ); // std::make_shared(); - //UITreeView* view = UITreeView::New(); - UITableView* view = UITableView::New(); + UITreeView* view = UITreeView::New(); + //UITableView* view = UITableView::New(); view->setId( "treeview" ); /*view->setExpandedIcon( open ); view->setContractedIcon( closed );*/ diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index 086590a66..d10484440 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -1,5 +1,6 @@ #include "codeeditor.hpp" #include "autocompletemodule.hpp" +#include "projectsearch.hpp" #include #include @@ -390,6 +391,11 @@ std::string App::getKeybind( const std::string& command ) { static int LOCATEBAR_MAX_VISIBLE_ITEMS = 18; static int LOCATEBAR_MAX_RESULTS = 100; +void App::hideLocateBar() { + mLocateBarLayout->setVisible( false ); + mLocateTable->setVisible( false ); +} + void App::updateLocateTable() { if ( !mLocateInput->getText().empty() ) { mLocateTable->setModel( @@ -430,13 +436,17 @@ void App::initLocateBar() { mLocateTable->forceKeyDown( *keyEvent ); } ); mLocateBarLayout->addCommand( "close-locatebar", [&] { - mLocateBarLayout->setVisible( false ); - mLocateTable->setVisible( false ); + hideLocateBar(); mEditorSplitter->getCurEditor()->setFocus(); } ); mLocateBarLayout->getKeyBindings().addKeybindsString( { {"escape", "close-locatebar"}, } ); + mLocateTable->addEventListener( Event::KeyDown, [&]( const Event* event ) { + const KeyEvent* keyEvent = reinterpret_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 ); @@ -474,7 +484,14 @@ void App::updateLocateBar() { } ); } +void App::hideSearchBar() { + mSearchBarLayout->setEnabled( false )->setVisible( false ); +} + void App::showLocateBar() { + hideGlobalSearchBar(); + hideSearchBar(); + mLocateBarLayout->setVisible( true ); mLocateInput->setFocus(); mLocateTable->setVisible( true ); @@ -522,8 +539,9 @@ void App::initSearchBar() { } } ); mSearchBarLayout->addCommand( "close-searchbar", [&] { - mSearchBarLayout->setEnabled( false )->setVisible( false ); - mEditorSplitter->getCurEditor()->setFocus(); + hideSearchBar(); + if ( mEditorSplitter->getCurEditor() ) + mEditorSplitter->getCurEditor()->setFocus(); if ( mSearchState.editor ) { if ( mEditorSplitter->editorExists( mSearchState.editor ) ) { mSearchState.editor->setHighlightWord( "" ); @@ -566,7 +584,154 @@ void App::initSearchBar() { [findInput]( const Event* ) { findInput->setFocus(); } ); } +void App::showGlobalSearch() { + hideLocateBar(); + hideSearchBar(); + mGlobalSearchBarLayout->setVisible( true )->setEnabled( true ); + mGlobalSearchInput->setFocus(); + mGlobalSearchTree->setVisible( true ); + updateGlobalSearchBar(); +} + +void App::updateGlobalSearchBar() { + mGlobalSearchBarLayout->runOnMainThread( [&] { + Float width = eeceil( mGlobalSearchInput->getPixelsSize().getWidth() ); + Float rowHeight = mGlobalSearchTree->getRowHeight() * LOCATEBAR_MAX_VISIBLE_ITEMS; + mGlobalSearchTree->setPixelsSize( width, rowHeight ); + width -= mGlobalSearchTree->getVerticalScrollBar()->getPixelsSize().getWidth(); + mGlobalSearchTree->setColumnWidth( 0, eeceil( width ) ); + Vector2f pos( mGlobalSearchInput->convertToWorldSpace( {0, 0} ) ); + pos.y -= mGlobalSearchTree->getPixelsSize().getHeight(); + mGlobalSearchTree->setPixelsPosition( pos ); + } ); +} + +void App::hideGlobalSearchBar() { + mGlobalSearchBarLayout->setEnabled( false )->setVisible( false ); + mGlobalSearchTree->setVisible( false ); +} + +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( "search" ); + UICheckBox* caseSensitiveBox = mGlobalSearchBarLayout->find( "case_sensitive" ); + UIWidget* searchBarClose = mGlobalSearchBarLayout->find( "global_searchbar_close" ); + mGlobalSearchInput = mGlobalSearchBarLayout->find( "global_search_find" ); + mGlobalSearchInput->addEventListener( Event::OnPressEnter, [this]( const Event* ) { + mGlobalSearchBarLayout->execute( "search-in-files" ); + } ); + mGlobalSearchBarLayout->addCommand( "search-in-files", [&, caseSensitiveBox] { + if ( mDirTree && mDirTree->getFilesCount() > 0 && !mGlobalSearchInput->getText().empty() ) { + UILoader* loader = UILoader::New(); + loader->setId( "loader " ); + loader->setRadius( 48 ); + loader->setOutlineThickness( 6 ); + loader->setFillColor( Color::Red ); + loader->setParent( mGlobalSearchTree->getParent() ); + loader->setPosition( mGlobalSearchTree->getPosition() + + mGlobalSearchTree->getSize() * 0.5f - loader->getSize() * 0.5f ); + Clock* clock = eeNew( Clock, () ); + std::string search( mGlobalSearchInput->getText().toUtf8() ); + ProjectSearch::find( + mDirTree->getFiles(), search, +#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN + mThreadPool, +#endif + [&, clock, search, loader]( const ProjectSearch::Result& res ) { + eePRINTL( "Global search for \"%s\" took %.2fms", search.c_str(), + clock->getElapsedTime().asMilliseconds() ); + eeDelete( clock ); + mUISceneNode->runOnMainThread( [&, loader, res] { + updateGlobalSearchBar(); + mGlobalSearchTree->setModel( ProjectSearch::asModel( res ) ); + if ( mGlobalSearchTree->getModel()->rowCount() < 50 ) + mGlobalSearchTree->expandAll(); + loader->setVisible( false ); + loader->close(); + } ); + }, + caseSensitiveBox->isChecked() ); + } + } ); + mGlobalSearchBarLayout->addCommand( "close-global-searchbar", [&] { + hideGlobalSearchBar(); + if ( mEditorSplitter->getCurEditor() ) + mEditorSplitter->getCurEditor()->setFocus(); + } ); + mGlobalSearchBarLayout->getKeyBindings().addKeybindsString( { + {"escape", "close-global-searchbar"}, + } ); + mGlobalSearchInput->addEventListener( Event::OnPressEnter, [&]( const Event* ) { + KeyEvent keyEvent( mGlobalSearchTree, Event::KeyDown, KEY_RETURN, 0, 0 ); + mGlobalSearchTree->forceKeyDown( keyEvent ); + } ); + mGlobalSearchInput->addEventListener( Event::KeyDown, [&]( const Event* event ) { + const KeyEvent* keyEvent = reinterpret_cast( event ); + mGlobalSearchTree->forceKeyDown( *keyEvent ); + } ); + addClickListener( searchButton, "search-in-files" ); + addClickListener( searchBarClose, "close-global-searchbar" ); + mGlobalSearchTree = UITreeView::New(); + mGlobalSearchTree->setId( "search_tree" ); + mGlobalSearchTree->setParent( mUISceneNode->getRoot() ); + mGlobalSearchTree->setHeadersVisible( false ); + mGlobalSearchTree->setVisible( false ); + mGlobalSearchTree->setColumnsHidden( + {ProjectSearch::ResultModel::Line, ProjectSearch::ResultModel::ColumnPosition}, true ); + mGlobalSearchTree->addEventListener( Event::KeyDown, [&]( const Event* event ) { + const KeyEvent* keyEvent = reinterpret_cast( event ); + if ( keyEvent->getKeyCode() == KEY_ESCAPE ) + mGlobalSearchBarLayout->execute( "close-global-searchbar" ); + } ); + mGlobalSearchTree->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; + Variant vPath( + model->data( model->index( modelEvent->getModelIndex().internalId(), + ProjectSearch::ResultModel::FileOrPosition ) ) ); + 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() ) + mEditorSplitter->loadFileFromPathInNewTab( 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::ColumnPosition, + modelEvent->getModelIndex().parent() ), + Model::Role::Custom ) ); + if ( mEditorSplitter->getCurEditor() && lineNum.isValid() && colNum.isValid() && + lineNum.is( Variant::Type::Int64 ) && colNum.is( Variant::Type::Int64 ) ) { + mEditorSplitter->getCurEditor()->getDocument().setSelection( + {lineNum.asInt64(), colNum.asInt64()} ); + } + } + } + } ); +} + void App::showFindView() { + hideLocateBar(); + hideGlobalSearchBar(); + UICodeEditor* editor = mEditorSplitter->getCurEditor(); if ( !editor ) return; @@ -653,7 +818,7 @@ void App::onTextDropped( String text ) { } } -App::App() : mThreadPool( ThreadPool::createShared( eemin( 4, Sys::getCPUCount() ) ) ) {} +App::App() : mThreadPool( ThreadPool::createShared( eemax( 2, Sys::getCPUCount() ) ) ) {} App::~App() { saveConfig(); @@ -1286,6 +1451,7 @@ std::map App::getLocalKeybindings() { {{KEY_F7, KEYMOD_NONE}, "debug-draw-boxes-toggle"}, {{KEY_F8, KEYMOD_NONE}, "debug-draw-debug-data"}, {{KEY_K, KEYMOD_CTRL}, "open-locatebar"}, + {{KEY_F, KEYMOD_CTRL | KEYMOD_SHIFT}, "global-find"}, }; } @@ -1321,6 +1487,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { doc.setCommand( "save-doc", [&] { saveDoc(); } ); doc.setCommand( "save-as-doc", [&] { saveFileDialog(); } ); doc.setCommand( "find-replace", [&] { showFindView(); } ); + doc.setCommand( "global-find", [&] { showGlobalSearch(); } ); doc.setCommand( "open-locatebar", [&] { showLocateBar(); } ); doc.setCommand( "repeat-find", [&] { findNextText( mSearchState ); } ); doc.setCommand( "close-app", [&] { closeApp(); } ); @@ -1409,12 +1576,15 @@ bool App::setAutoComplete( bool enable ) { UIPopUpMenu* App::createToolsMenu() { mToolsMenu = UIPopUpMenu::New(); mToolsMenu->add( "Locate...", findIcon( "search" ), getKeybind( "open-locatebar" ) ); + mToolsMenu->add( "Project Find...", findIcon( "search" ), getKeybind( "global-find" ) ); mToolsMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) return; UIMenuItem* item = event->getNode()->asType(); if ( item->getText() == "Locate..." ) { showLocateBar(); + } else if ( item->getText() == "Project Find..." ) { + showGlobalSearch(); } } ); return mToolsMenu; @@ -1565,6 +1735,8 @@ void App::initProjectTreeView( const std::string& path ) { FileSystemModel::Inode, FileSystemModel::Owner, FileSystemModel::SymlinkTarget, FileSystemModel::Permissions, FileSystemModel::ModificationTime, FileSystemModel::Path}, true ); + mProjectTreeView->setExpandedIcon( "folder-open" ); + mProjectTreeView->setContractedIcon( "folder" ); mProjectTreeView->setHeadersVisible( false ); mProjectTreeView->setExpandersAsIcons( true ); mProjectTreeView->addEventListener( Event::OnModelEvent, [&]( const Event* event ) { @@ -1717,11 +1889,13 @@ void App::init( const std::string& file, const Float& pidelDensity ) { @@ -1816,6 +1993,20 @@ void App::init( const std::string& file, const Float& pidelDensity ) { + + + + + + + + + + + + + + @@ -1862,17 +2053,20 @@ void App::init( const std::string& file, const Float& pidelDensity ) { addIcon( "cancel", 0xeb98, buttonIconSize ); addIcon( "color-picker", 0xf13d, buttonIconSize ); addIcon( "pixel-density", 0xed8c, buttonIconSize ); - addIcon( "tree-expanded", 0xed70, menuIconSize ); - addIcon( "tree-contracted", 0xed54, menuIconSize ); + addIcon( "tree-expanded", 0xea50, PixelDensity::dpToPx( 24 ) ); + addIcon( "tree-contracted", 0xea54, PixelDensity::dpToPx( 24 ) ); addIcon( "search", 0xf0d1, menuIconSize ); mUISceneNode->getUIIconThemeManager()->setCurrentTheme( iconTheme ); UIWidgetCreator::registerWidget( "searchbar", [] { return UISearchBar::New(); } ); UIWidgetCreator::registerWidget( "locatebar", [] { return UILocateBar::New(); } ); + UIWidgetCreator::registerWidget( "globalsearchbar", + [] { return UIGlobalSearchBar::New(); } ); mUISceneNode->loadLayoutFromString( baseUI ); mUISceneNode->bind( "main_layout", mMainLayout ); mUISceneNode->bind( "code_container", mBaseLayout ); 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 ); @@ -1881,6 +2075,7 @@ void App::init( const std::string& file, const Float& pidelDensity ) { mUISceneNode->bind( "locate_find", mLocateInput ); 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 ) @@ -1893,6 +2088,8 @@ void App::init( const std::string& file, const Float& pidelDensity ) { initSearchBar(); + initGlobalSearchBar(); + initLocateBar(); createSettingsMenu(); diff --git a/src/tools/codeeditor/codeeditor.hpp b/src/tools/codeeditor/codeeditor.hpp index 6abb74368..ace7faf03 100644 --- a/src/tools/codeeditor/codeeditor.hpp +++ b/src/tools/codeeditor/codeeditor.hpp @@ -68,6 +68,38 @@ class UILocateBar : public UILinearLayout { } }; +class UIGlobalSearchBar : public UILinearLayout { + public: + typedef std::function CommandCallback; + static UIGlobalSearchBar* New() { return eeNew( UIGlobalSearchBar, () ); } + UIGlobalSearchBar() : + UILinearLayout( "globalsearchbar", UIOrientation::Vertical ), + mKeyBindings( getUISceneNode()->getWindow()->getInput() ) {} + 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 0; + } + } + return 1; + } +}; + struct UIConfig { StyleSheetLength fontSize{12, StyleSheetLength::Dp}; bool showSidePanel{true}; @@ -151,6 +183,8 @@ class App : public UICodeEditorSplitter::Client { void showFindView(); + void showGlobalSearch(); + void showLocateBar(); bool replaceSelection( SearchState& search, const String& replacement ); @@ -177,6 +211,7 @@ class App : public UICodeEditorSplitter::Client { UILayout* mBaseLayout{nullptr}; UISearchBar* mSearchBarLayout{nullptr}; UILocateBar* mLocateBarLayout{nullptr}; + UILocateBar* mGlobalSearchBarLayout{nullptr}; UIPopUpMenu* mSettingsMenu{nullptr}; UITextView* mSettingsButton{nullptr}; UIPopUpMenu* mColorSchemeMenu{nullptr}; @@ -209,6 +244,8 @@ class App : public UICodeEditorSplitter::Client { UITreeView* mProjectTreeView{nullptr}; UITableView* mLocateTable{nullptr}; UITextInput* mLocateInput{nullptr}; + UITreeView* mGlobalSearchTree{nullptr}; + UITextInput* mGlobalSearchInput; bool mDirTreeReady{false}; void initLocateBar(); @@ -233,6 +270,8 @@ class App : public UICodeEditorSplitter::Client { void initSearchBar(); + void initGlobalSearchBar(); + void addRemainingTabWidgets( Node* widget ); void createSettingsMenu(); @@ -301,7 +340,15 @@ class App : public UICodeEditorSplitter::Client { void updateLocateTable(); + void updateGlobalSearchBar(); + UIPopUpMenu* createToolsMenu(); + + void hideGlobalSearchBar(); + + void hideSearchBar(); + + void hideLocateBar(); }; #endif // EE_TOOLS_CODEEDITOR_HPP diff --git a/src/tools/codeeditor/projectdirectorytree.cpp b/src/tools/codeeditor/projectdirectorytree.cpp index b2c7a1438..751f997f8 100644 --- a/src/tools/codeeditor/projectdirectorytree.cpp +++ b/src/tools/codeeditor/projectdirectorytree.cpp @@ -14,7 +14,7 @@ void ProjectDirectoryTree::scan( const ProjectDirectoryTree::ScanCompleteEvent& const bool& ignoreHidden ) { #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN mPool->run( - [&, acceptedPattern] { + [&, acceptedPattern, ignoreHidden] { #endif Lock l( mFilesMutex ); if ( !acceptedPattern.empty() ) { @@ -112,6 +112,10 @@ size_t ProjectDirectoryTree::getFilesCount() const { return mFiles.size(); } +const std::vector& ProjectDirectoryTree::getFiles() const { + return mFiles; +} + void ProjectDirectoryTree::getDirectoryFiles( std::vector& files, std::vector& names, std::string directory, diff --git a/src/tools/codeeditor/projectdirectorytree.hpp b/src/tools/codeeditor/projectdirectorytree.hpp index 181686a33..614c89a23 100644 --- a/src/tools/codeeditor/projectdirectorytree.hpp +++ b/src/tools/codeeditor/projectdirectorytree.hpp @@ -29,7 +29,7 @@ class FileListModel : public Model { } virtual Variant data( const ModelIndex& index, Role role = Role::Display ) const { - if ( role == Role::Display ) { + if ( role == Role::Display && index.row() < (Int64)mFiles.size() ) { return Variant( index.column() == 0 ? mNames[index.row()].c_str() : mFiles[index.row()].c_str() ); } @@ -62,6 +62,8 @@ class ProjectDirectoryTree { size_t getFilesCount() const; + const std::vector& getFiles() const; + protected: std::string mPath; std::shared_ptr mPool; diff --git a/src/tools/codeeditor/projectsearch.cpp b/src/tools/codeeditor/projectsearch.cpp new file mode 100644 index 000000000..a4c3ee6f4 --- /dev/null +++ b/src/tools/codeeditor/projectsearch.cpp @@ -0,0 +1,68 @@ +#include "projectsearch.hpp" + +static std::vector +searchInFile( const std::string& file, const std::string& text, const bool& caseSensitive ) { + std::vector res; + TextDocument doc( false ); + TextPosition pos{0, 0}; + String searchText( text ); + if ( doc.loadFromFile( file ) ) { + do { + pos = doc.find( searchText, pos, caseSensitive ); + if ( pos.isValid() ) { + const auto& l = doc.line( pos.line() ).getText(); + res.push_back( {l.substr( 0, l.size() - 1 ).toUtf8(), pos} ); + pos = doc.positionOffset( pos, searchText.size() ); + } + } while ( pos.isValid() ); + } + return res; +} + +void ProjectSearch::find( const std::vector files, const std::string& string, + ResultCb result, bool caseSensitive ) { + Result res; + for ( auto& file : files ) { + auto fileRes = searchInFile( file, string, caseSensitive ); + if ( !fileRes.empty() ) + res.push_back( {file, fileRes} ); + } + result( res ); +} + +struct FindData { + Mutex resMutex; + Mutex countMutex; + int resCount{0}; + ProjectSearch::Result res; +}; + +void ProjectSearch::find( const std::vector files, const std::string& string, + std::shared_ptr pool, ResultCb result, bool caseSensitive ) { + if ( files.empty() ) + result( {} ); + FindData* findData = eeNew( FindData, () ); + findData->resCount = files.size(); + for ( auto& file : files ) { + pool->run( + [findData, file, string, caseSensitive] { + auto fileRes = searchInFile( file, string, caseSensitive ); + if ( !fileRes.empty() ) { + Lock l( findData->resMutex ); + findData->res.push_back( {file, fileRes} ); + } + }, + [result, findData] { + int count; + { + Lock l( findData->countMutex ); + findData->resCount--; + count = findData->resCount; + } + if ( count == 0 ) { + result( findData->res ); + eeDelete( findData ); + } + } ); + } +} diff --git a/src/tools/codeeditor/projectsearch.hpp b/src/tools/codeeditor/projectsearch.hpp new file mode 100644 index 000000000..9eeab87cd --- /dev/null +++ b/src/tools/codeeditor/projectsearch.hpp @@ -0,0 +1,126 @@ +#ifndef PROJECTSEARCH_HPP +#define PROJECTSEARCH_HPP + +#include +#include +#include +#include +#include +#include +#include + +using namespace EE; +using namespace EE::System; +using namespace EE::UI::Doc; +using namespace EE::UI::Models; + +class ProjectSearch { + public: + struct ResultData { + struct Result { + Result( const String& line, const TextPosition& pos ) : line( line ), position( pos ) {} + std::string line; + TextPosition position; + }; + std::string file; + std::vector results; + }; + + typedef std::vector Result; + typedef std::function ResultCb; + + class ResultModel : public Model { + public: + enum Column { FileOrPosition, Line, ColumnPosition }; + + ResultModel( const Result& result ) : mResult( result ) {} + + virtual size_t treeColumn() const { return Column::FileOrPosition; } + + ModelIndex parentIndex( const ModelIndex& index ) const { + if ( !index.isValid() || index.internalId() == -1 ) + return {}; + return createIndex( index.internalId(), index.column(), &mResult[index.internalId()], + -1 ); + } + + ModelIndex index( int row, int column, const ModelIndex& parent ) const { + if ( row < 0 || column < 0 ) + return {}; + if ( !parent.isValid() ) + return createIndex( row, column, &mResult[row], -1 ); + if ( parent.data() ) + return createIndex( row, column, &mResult[parent.row()].results[row], + parent.row() ); + return {}; + } + + size_t rowCount( const ModelIndex& index ) const { + if ( !index.isValid() ) + return mResult.size(); + if ( index.internalId() == -1 ) + return mResult[index.row()].results.size(); + return 0; + } + + size_t columnCount( const ModelIndex& ) const { return 2; } + + std::string columnName( const size_t& colIndex ) const { + return colIndex == 0 ? "File" : "Line"; + } + + Variant data( const ModelIndex& index, Role role = Role::Display ) const { + static const char* EMPTY = ""; + if ( role == Role::Display ) { + if ( index.internalId() == -1 ) { + if ( index.column() == FileOrPosition ) { + return Variant( String::format( "%s (%zu)", + mResult[index.row()].file.c_str(), + mResult[index.row()].results.size() ) ); + } + } else { + switch ( index.column() ) { + case FileOrPosition: + return Variant( String::format( + "%6lld %s", + mResult[index.internalId()].results[index.row()].position.line(), + mResult[index.internalId()].results[index.row()].line.c_str() ) ); + } + } + } else if ( role == Role::Custom ) { + if ( index.internalId() != -1 ) { + switch ( index.column() ) { + case FileOrPosition: + return Variant( + mResult[index.internalId()].results[index.row()].position.line() ); + case Line: + return Variant( + mResult[index.internalId()].results[index.row()].line.c_str() ); + case ColumnPosition: + return Variant( mResult[index.internalId()] + .results[index.row()] + .position.column() ); + } + } + } + return Variant( EMPTY ); + } + + virtual void update() { onModelUpdate(); } + + protected: + Result mResult; + }; + + static std::shared_ptr asModel( const Result& result ) { + return std::make_shared( result ); + } + + static void find( const std::vector files, const std::string& string, + ResultCb result, bool caseSensitive ); + + static void find( const std::vector files, const std::string& string, + std::shared_ptr pool, ResultCb result, bool caseSensitive ); +}; + +#endif // PROJECTSEARCH_HPP