diff --git a/bin/assets/linters/linters.json b/bin/assets/linters/linters.json index 91f513c96..127dc84fa 100644 --- a/bin/assets/linters/linters.json +++ b/bin/assets/linters/linters.json @@ -15,5 +15,17 @@ "file_patterns": ["%.lua$"], "warning_pattern": "[^:]:(%d+):(%d+):[%s]?([^\n]+)", "command": "luacheck $FILENAME --formatter=plain -g --no-max-line-length" + }, + { + "file_patterns": ["%.py$"], + "warning_pattern": "[^:]:(%d+):(%d+):%s([%w]+:%s[^\n]*)", + "column_starts_at_zero": true, + "command": "pylint --score=n $FILENAME" + }, + { + "file_patterns": ["%.sh$"], + "warning_pattern": "[^:]:(%d+):(%d+):%s?([^%s]*)([^\n]*)", + "warning_pattern_order": { "line": 1, "col": 2, "message": 4, "type": 3 }, + "command": "shellcheck -f gcc $FILENAME" } ] diff --git a/include/eepp/system/sys.hpp b/include/eepp/system/sys.hpp index f093f74c2..76aa7259a 100644 --- a/include/eepp/system/sys.hpp +++ b/include/eepp/system/sys.hpp @@ -11,8 +11,12 @@ class EE_API Sys { /** Returns the current date time */ static std::string getDateTimeStr(); + /** Converts any epoch timestmap to a formatted string. */ + static std::string epochToString( const Uint64& epochTimestamp, + const std::string& format = "%Y-%m-%d %H:%M" ); + /** @return A storage path for config files for every platform */ - static std::string getConfigPath( std::string appname ); + static std::string getConfigPath( const std::string& appname ); /** @return The path of the directory designated for temporary files. */ static std::string getTempPath(); diff --git a/include/eepp/ui/abstract/uiabstractview.hpp b/include/eepp/ui/abstract/uiabstractview.hpp index b04f67929..a144f1cef 100644 --- a/include/eepp/ui/abstract/uiabstractview.hpp +++ b/include/eepp/ui/abstract/uiabstractview.hpp @@ -51,6 +51,8 @@ class EE_API UIAbstractView : public UIScrollableWidget { const Model* getModel() const { return mModel.get(); } + std::shared_ptr getModelShared() const { return mModel; } + ModelSelection& getSelection() { return mSelection; } const ModelSelection& getSelection() const { return mSelection; } diff --git a/include/eepp/ui/models/filesystemmodel.hpp b/include/eepp/ui/models/filesystemmodel.hpp index d63b496b2..da6192797 100644 --- a/include/eepp/ui/models/filesystemmodel.hpp +++ b/include/eepp/ui/models/filesystemmodel.hpp @@ -20,9 +20,9 @@ class EE_API FileSystemModel : public Model { foldersFirst( foldersFirst ), ignoreHidden( ignoreHidden ), acceptedExtensions( acceptedExtensions ) {} - bool sortByName{true}; - bool foldersFirst{true}; - bool ignoreHidden{false}; + bool sortByName{ true }; + bool foldersFirst{ true }; + bool ignoreHidden{ false }; std::vector acceptedExtensions; bool operator==( const DisplayConfig& other ) { return sortByName == other.sortByName && foldersFirst == other.foldersFirst && @@ -49,33 +49,49 @@ class EE_API FileSystemModel : public Model { struct Node { public: Node( const std::string& rootPath, const FileSystemModel& model ); + Node( FileInfo&& info, Node* parent ); + const std::string& getName() const { return mName; } + Node* getParent() const { return mParent; } + const FileInfo& info() const { return mInfo; } + bool isSelected() const { return mSelected; } + void setSelected( bool selected ) { mSelected = selected; }; + const std::string& fullPath() const; + const std::string& getMimeType() const { return mMimeType; } + size_t childCount() const { return mChildren.size(); } - const Node& getChild( const size_t& index ) { - eeASSERT( index < mChildren.size() ); - return mChildren[index]; - } + + const Node& getChild( const size_t& index ); + + void invalidate(); + + Node* findChildName( const std::string& name, const FileSystemModel& model, + bool forceRefresh = false ); private: friend class FileSystemModel; std::string mName; std::string mMimeType; - Node* mParent{nullptr}; + Node* mParent{ nullptr }; FileInfo mInfo; std::vector mChildren; - bool mHasTraversed{false}; - bool mInfoDirty{true}; - bool mSelected{false}; + bool mHasTraversed{ false }; + bool mInfoDirty{ true }; + bool mSelected{ false }; + ModelIndex index( const FileSystemModel& model, int column ) const; + void traverseIfNeeded( const FileSystemModel& ); + void refreshIfNeeded( const FileSystemModel& ); + bool fetchData( const String& fullPath ); }; @@ -85,10 +101,12 @@ class EE_API FileSystemModel : public Model { const Mode& getMode() const { return mMode; } - std::string getRootPath() const; + const std::string& getRootPath() const; void setRootPath( const std::string& rootPath ); + Node* getNodeFromPath( std::string path, bool folderNode = false, bool invalidateTree = true ); + void reload(); void update(); @@ -111,10 +129,15 @@ class EE_API FileSystemModel : public Model { void setDisplayConfig( const DisplayConfig& displayConfig ); + const ModelIndex& getPreviouslySelectedIndex() const; + + void setPreviouslySelectedIndex( const ModelIndex& previouslySelectedIndex ); + protected: std::string mRootPath; - std::unique_ptr mRoot{nullptr}; - Mode mMode{Mode::FilesAndDirectories}; + std::string mRealRootPath; + std::unique_ptr mRoot{ nullptr }; + Mode mMode{ Mode::FilesAndDirectories }; DisplayConfig mDisplayConfig; ModelIndex mPreviouslySelectedIndex{}; diff --git a/include/eepp/ui/models/model.hpp b/include/eepp/ui/models/model.hpp index d3e3e536c..da2a68172 100644 --- a/include/eepp/ui/models/model.hpp +++ b/include/eepp/ui/models/model.hpp @@ -92,6 +92,8 @@ class EE_API Model { void setOnUpdate( const std::function& onUpdate ); + void invalidate(); + protected: Model(){}; diff --git a/include/eepp/ui/uicheckbox.hpp b/include/eepp/ui/uicheckbox.hpp index cda0c1401..a775c6bd9 100644 --- a/include/eepp/ui/uicheckbox.hpp +++ b/include/eepp/ui/uicheckbox.hpp @@ -10,7 +10,7 @@ class EE_API UICheckBox : public UITextView { public: static UICheckBox* New(); - UICheckBox(); + static UICheckBox* NewWithTag( const std::string& tag ); virtual ~UICheckBox(); @@ -28,6 +28,8 @@ class EE_API UICheckBox : public UITextView { UIWidget* getInactiveButton() const; + UIWidget* getCurrentButton() const; + Int32 getTextSeparation() const; void setTextSeparation( const Int32& textSeparation ); @@ -44,6 +46,10 @@ class EE_API UICheckBox : public UITextView { Uint32 mLastTick; Int32 mTextSeparation; + UICheckBox(); + + UICheckBox( const std::string& tag ); + virtual void onSizeChange(); void switchState(); diff --git a/include/eepp/ui/uinode.hpp b/include/eepp/ui/uinode.hpp index 1ceb35db9..287f4fdda 100644 --- a/include/eepp/ui/uinode.hpp +++ b/include/eepp/ui/uinode.hpp @@ -78,6 +78,8 @@ class EE_API UINode : public Node { Rect getRect() const; + Rectf getRectBox() const; + virtual void draw(); Uint32 getHorizontalAlign() const; diff --git a/include/eepp/ui/uipushbutton.hpp b/include/eepp/ui/uipushbutton.hpp index 7bc7ad31f..6e87e4279 100644 --- a/include/eepp/ui/uipushbutton.hpp +++ b/include/eepp/ui/uipushbutton.hpp @@ -15,6 +15,9 @@ class EE_API UIPushButton : public UIWidget { static UIPushButton* NewWithTag( const std::string& tag ); + static UIPushButton* NewWithOpt( const std::string& tag, + const std::function& newTextViewCb ); + UIPushButton(); virtual ~UIPushButton(); @@ -62,10 +65,12 @@ class EE_API UIPushButton : public UIWidget { UIImage* mIcon; UITextView* mTextBox; Sizei mIconMinSize; - InnerWidgetOrientation mInnerWidgetOrientation{InnerWidgetOrientation::Right}; + InnerWidgetOrientation mInnerWidgetOrientation{ InnerWidgetOrientation::Right }; explicit UIPushButton( const std::string& tag ); + explicit UIPushButton( const std::string& tag, const std::function& cb ); + virtual Rectf calculatePadding() const; virtual void onSizeChange(); diff --git a/include/eepp/ui/uitablecell.hpp b/include/eepp/ui/uitablecell.hpp index 84a6b0ba2..39e7e0eea 100644 --- a/include/eepp/ui/uitablecell.hpp +++ b/include/eepp/ui/uitablecell.hpp @@ -30,12 +30,18 @@ class EE_API UITableCell : public UIPushButton { onThemeLoaded(); } + virtual void updateCell( Model* ){}; + protected: ModelIndex mCurIndex; UITableCell() : UITableCell( "table::cell" ) {} - UITableCell( const std::string& tag ) : UIPushButton( tag ) { applyDefaultTheme(); } + UITableCell( const std::string& tag, + const std::function& newTextViewCb = nullptr ) : + UIPushButton( tag, newTextViewCb ) { + applyDefaultTheme(); + } }; }} // namespace EE::UI diff --git a/include/eepp/ui/uitextview.hpp b/include/eepp/ui/uitextview.hpp index e4f8b0acb..b11c1cd85 100644 --- a/include/eepp/ui/uitextview.hpp +++ b/include/eepp/ui/uitextview.hpp @@ -94,7 +94,9 @@ class EE_API UITextView : public UIWidget { const Text* getTextCache() const; - protected: + const Vector2f& getRealAlignOffset() const; + + protected: Text* mTextCache; String mString; UIFontStyleConfig mFontStyleConfig; diff --git a/include/eepp/ui/uitreeview.hpp b/include/eepp/ui/uitreeview.hpp index 43df9a6ee..f389e64a2 100644 --- a/include/eepp/ui/uitreeview.hpp +++ b/include/eepp/ui/uitreeview.hpp @@ -55,10 +55,11 @@ class EE_API UITreeViewCell : public UITableCell { const Float& getIndentation() const { return mIndent; } protected: - mutable UIImage* mImage{nullptr}; - Float mIndent{0}; + mutable UIImage* mImage{ nullptr }; + Float mIndent{ 0 }; - UITreeViewCell() : UITableCell( "treeview::cell" ) { + UITreeViewCell( const std::function& newTextViewCb = nullptr ) : + UITableCell( "treeview::cell", newTextViewCb ) { mTextBox->setElementTag( mTag + "::text" ); mIcon->setElementTag( mTag + "::icon" ); mInnerWidgetOrientation = InnerWidgetOrientation::Left; @@ -137,17 +138,17 @@ class EE_API UITreeView : public UIAbstractTableView { Float mIndentWidth; Sizef mContentSize; - UIIcon* mExpandIcon{nullptr}; - UIIcon* mContractIcon{nullptr}; - size_t mExpanderIconSize{16}; - bool mExpandersAsIcons{false}; + UIIcon* mExpandIcon{ nullptr }; + UIIcon* mContractIcon{ nullptr }; + size_t mExpanderIconSize{ 16 }; + bool mExpandersAsIcons{ false }; UITreeView(); virtual void createOrUpdateColumns(); struct MetadataForIndex { - bool open{false}; + bool open{ false }; }; template void traverseTree( Callback ) const; diff --git a/src/eepp/system/sys.cpp b/src/eepp/system/sys.cpp index c6d60f6b4..a35275cc7 100644 --- a/src/eepp/system/sys.cpp +++ b/src/eepp/system/sys.cpp @@ -6,6 +6,7 @@ #include #include #include +#include // This taints the System module! #if EE_PLATFORM == EE_PLATFORM_ANDROID @@ -578,7 +579,7 @@ double Sys::getSystemTime() { // High performance counter not available : use GetTickCount (less accurate) return GetTickCount() * 0.001; #else - timeval Time = {0, 0}; + timeval Time = { 0, 0 }; gettimeofday( &Time, NULL ); return Time.tv_sec + Time.tv_usec / 1000000.; @@ -599,8 +600,16 @@ std::string Sys::getDateTimeStr() { return std::string( buf ); } +std::string Sys::epochToString( const Uint64& epochTimestamp, const std::string& format ) { + std::time_t t = epochTimestamp; + auto tm = *std::localtime( &t ); + std::ostringstream oss; + oss << std::put_time( &tm, format.c_str() ); + return oss.str(); +} + #define EE_MAX_CFG_PATH_LEN 1024 -std::string Sys::getConfigPath( std::string appname ) { +std::string Sys::getConfigPath( const std::string& appname ) { char path[EE_MAX_CFG_PATH_LEN]; #if EE_PLATFORM == EE_PLATFORM_WIN diff --git a/src/eepp/ui/abstract/uiabstracttableview.cpp b/src/eepp/ui/abstract/uiabstracttableview.cpp index 671c7924e..96bfe5350 100644 --- a/src/eepp/ui/abstract/uiabstracttableview.cpp +++ b/src/eepp/ui/abstract/uiabstracttableview.cpp @@ -427,6 +427,8 @@ UIWidget* UIAbstractTableView::updateCell( const int& rowIndex, const ModelIndex cell->setIcon( icon.asIcon()->getSize( mIconSize ) ); } cell->getIcon()->setVisible( isVisible ); + + cell->updateCell( getModel() ); } return widget; diff --git a/src/eepp/ui/models/filesystemmodel.cpp b/src/eepp/ui/models/filesystemmodel.cpp index fa644e912..42a08786c 100644 --- a/src/eepp/ui/models/filesystemmodel.cpp +++ b/src/eepp/ui/models/filesystemmodel.cpp @@ -34,6 +34,29 @@ const std::string& FileSystemModel::Node::fullPath() const { return mInfo.getFilepath(); } +const FileSystemModel::Node& FileSystemModel::Node::getChild( const size_t& index ) { + eeASSERT( index < mChildren.size() ); + return mChildren[index]; +} + +void FileSystemModel::Node::invalidate() { + mHasTraversed = false; + mInfoDirty = true; + mChildren.clear(); +} + +FileSystemModel::Node* FileSystemModel::Node::findChildName( const std::string& name, + const FileSystemModel& model, + bool forceRefresh ) { + if ( forceRefresh ) + refreshIfNeeded( model ); + for ( auto& child : mChildren ) { + if ( child.getName() == name ) + return &child; + } + return nullptr; +} + ModelIndex FileSystemModel::Node::index( const FileSystemModel& model, int column ) const { if ( !mParent ) return {}; @@ -103,19 +126,56 @@ std::shared_ptr FileSystemModel::New( const std::string& rootPa FileSystemModel::FileSystemModel( const std::string& rootPath, const FileSystemModel::Mode& mode, const DisplayConfig displayConfig ) : - mRootPath( rootPath ), mMode( mode ), mDisplayConfig( displayConfig ) { + mRootPath( rootPath ), + mRealRootPath( FileSystem::getRealPath( rootPath ) ), + mMode( mode ), + mDisplayConfig( displayConfig ) { update(); } -std::string FileSystemModel::getRootPath() const { +const std::string& FileSystemModel::getRootPath() const { return mRootPath; } void FileSystemModel::setRootPath( const std::string& rootPath ) { mRootPath = rootPath; + mRealRootPath = FileSystem::getRealPath( mRootPath ); update(); } +FileSystemModel::Node* FileSystemModel::getNodeFromPath( std::string path, bool folderNode, + bool invalidateTree ) { + path = FileSystem::getRealPath( path ); + if ( folderNode && !FileSystem::isDirectory( path ) ) + path = FileSystem::fileRemoveFileName( path ); + if ( String::startsWith( path, mRealRootPath ) ) + path = path.substr( mRealRootPath.size() ); + else if ( path.empty() || + !( path[0] == '/' || + ( path.size() >= 2 && String::isLetter( path[0] ) && path[1] == ':' ) ) ) { + return nullptr; + } + if ( String::contains( path, "\\" ) ) + String::replaceAll( path, "\\", "/" ); + auto folders = String::split( path, '/' ); + Node* curNode = mRoot.get(); + Node* foundNode = nullptr; + + if ( !folders.empty() ) { + for ( size_t i = 0; i < folders.size(); i++ ) { + auto& part = folders[i]; + if ( ( foundNode = curNode->findChildName( + part, *this, invalidateTree || i == folders.size() - 1 ) ) ) { + curNode = foundNode; + } else { + return nullptr; + } + } + } + + return curNode; +} + void FileSystemModel::reload() { setRootPath( mRootPath ); } @@ -187,14 +247,6 @@ static std::string permissionString( const FileInfo& info ) { return builder; } -static std::string timestampString( const Uint64& time ) { - std::time_t t = time; - auto tm = *std::localtime( &t ); - std::ostringstream oss; - oss << std::put_time( &tm, "%Y-%m-%d %H:%M" ); - return oss.str(); -} - Variant FileSystemModel::data( const ModelIndex& index, Model::Role role ) const { eeASSERT( index.isValid() ); @@ -245,7 +297,7 @@ Variant FileSystemModel::data( const ModelIndex& index, Model::Role role ) const case Column::Permissions: return Variant( permissionString( node.info() ) ); case Column::ModificationTime: - return Variant( timestampString( node.info().getModificationTime() ) ); + return Variant( Sys::epochToString( node.info().getModificationTime() ) ); case Column::Inode: return Variant( String::toString( node.info().getInode() ) ); case Column::Path: @@ -311,4 +363,12 @@ void FileSystemModel::setDisplayConfig( const DisplayConfig& displayConfig ) { } } +const ModelIndex& FileSystemModel::getPreviouslySelectedIndex() const { + return mPreviouslySelectedIndex; +} + +void FileSystemModel::setPreviouslySelectedIndex( const ModelIndex& previouslySelectedIndex ) { + mPreviouslySelectedIndex = previouslySelectedIndex; +} + }}} // namespace EE::UI::Models diff --git a/src/eepp/ui/models/model.cpp b/src/eepp/ui/models/model.cpp index d0674f546..222677c7d 100644 --- a/src/eepp/ui/models/model.cpp +++ b/src/eepp/ui/models/model.cpp @@ -45,6 +45,10 @@ void Model::setOnUpdate( const std::function& onUpdate ) { mOnUpdate = onUpdate; } +void Model::invalidate() { + onModelUpdate(); +} + ModelIndex Model::sibling( int row, int column, const ModelIndex& parent ) const { if ( !parent.isValid() ) return index( row, column, {} ); diff --git a/src/eepp/ui/uicheckbox.cpp b/src/eepp/ui/uicheckbox.cpp index 6f9670ddf..6a4327f77 100644 --- a/src/eepp/ui/uicheckbox.cpp +++ b/src/eepp/ui/uicheckbox.cpp @@ -9,10 +9,17 @@ UICheckBox* UICheckBox::New() { return eeNew( UICheckBox, () ); } -UICheckBox::UICheckBox() : UITextView( "checkbox" ), mChecked( false ), mTextSeparation( 4 ) { +UICheckBox* UICheckBox::NewWithTag( const std::string& tag ) { + return eeNew( UICheckBox, ( tag ) ); +} + +UICheckBox::UICheckBox() : UICheckBox( "checkbox" ) {} + +UICheckBox::UICheckBox( const std::string& tag ) : + UITextView( tag ), mChecked( false ), mTextSeparation( 4 ) { auto cb = [&]( const Event* ) { onAutoSize(); }; - mActiveButton = UIWidget::NewWithTag( "checkbox::active" ); + mActiveButton = UIWidget::NewWithTag( tag + "::active" ); mActiveButton->setVisible( false ); mActiveButton->setEnabled( true ); mActiveButton->setParent( this ); @@ -20,7 +27,7 @@ UICheckBox::UICheckBox() : UITextView( "checkbox" ), mChecked( false ), mTextSep mActiveButton->setSize( 8, 8 ); mActiveButton->addEventListener( Event::OnSizeChange, cb ); - mInactiveButton = UIWidget::NewWithTag( "checkbox::inactive" ); + mInactiveButton = UIWidget::NewWithTag( tag + "::inactive" ); mInactiveButton->setVisible( true ); mInactiveButton->setEnabled( true ); mInactiveButton->setParent( this ); @@ -197,6 +204,10 @@ UIWidget* UICheckBox::getInactiveButton() const { return mInactiveButton; } +UIWidget* UICheckBox::getCurrentButton() const { + return mChecked ? mActiveButton : mInactiveButton; +} + Int32 UICheckBox::getTextSeparation() const { return mTextSeparation; } diff --git a/src/eepp/ui/uinode.cpp b/src/eepp/ui/uinode.cpp index 5e917287e..303abaa56 100644 --- a/src/eepp/ui/uinode.cpp +++ b/src/eepp/ui/uinode.cpp @@ -300,6 +300,10 @@ Rect UINode::getRect() const { return Rect( Vector2i( mDpPos.x, mDpPos.y ), Sizei( mDpSize.x, mDpSize.y ) ); } +Rectf UINode::getRectBox() const { + return Rectf( mPosition, mSize ); +} + const Sizef& UINode::getSize() const { return mDpSize; } diff --git a/src/eepp/ui/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index 6188ae633..f9b19923b 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -16,7 +16,15 @@ UIPushButton* UIPushButton::NewWithTag( const std::string& tag ) { return eeNew( UIPushButton, ( tag ) ); } -UIPushButton::UIPushButton( const std::string& tag ) : +UIPushButton* UIPushButton::NewWithOpt( const std::string& tag, + const std::function& newTextViewCb ) { + return eeNew( UIPushButton, ( tag, newTextViewCb ) ); +} + +UIPushButton::UIPushButton( const std::string& tag ) : UIPushButton( tag, nullptr ) {} + +UIPushButton::UIPushButton( const std::string& tag, + const std::function& newTextViewCb ) : UIWidget( tag ), mIcon( NULL ), mTextBox( NULL ) { mFlags |= ( UI_AUTO_SIZE | UI_VALIGN_CENTER | UI_HALIGN_CENTER ); @@ -35,7 +43,7 @@ UIPushButton::UIPushButton( const std::string& tag ) : mIcon->addEventListener( Event::OnSizeChange, cb ); mIcon->addEventListener( Event::OnVisibleChange, cb ); - mTextBox = UITextView::NewWithTag( "pushbutton::text" ); + mTextBox = newTextViewCb ? newTextViewCb() : UITextView::NewWithTag( "pushbutton::text" ); mTextBox->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent ) ->setFlags( UI_VALIGN_CENTER | UI_HALIGN_CENTER ) ->setParent( this ) diff --git a/src/eepp/ui/uitextview.cpp b/src/eepp/ui/uitextview.cpp index 99f36876d..3a362d5ca 100644 --- a/src/eepp/ui/uitextview.cpp +++ b/src/eepp/ui/uitextview.cpp @@ -231,6 +231,10 @@ const Text* UITextView::getTextCache() const { return mTextCache; } +const Vector2f& UITextView::getRealAlignOffset() const { + return mRealAlignOffset; +} + const Color& UITextView::getFontShadowColor() const { return mFontStyleConfig.ShadowColor; } diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index d3178e40e..bec797ff5 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -237,6 +237,8 @@ UIWidget* UITreeView::updateCell( const int& rowIndex, const ModelIndex& index, cell->setIcon( icon.asIcon()->getSize( mIconSize ) ); } cell->getIcon()->setVisible( isVisible ); + + cell->updateCell( getModel() ); } return widget; diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index 078fd3cfa..ab1008bf2 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -823,7 +823,8 @@ void App::initGlobalSearchBar() { } ); addClickListener( searchButton, "search-in-files" ); addClickListener( searchBarClose, "close-global-searchbar" ); - mGlobalSearchTree = UITreeViewGlobalSearch::New( mEditorSplitter->getCurrentColorScheme() ); + mGlobalSearchTree = + UITreeViewGlobalSearch::New( mEditorSplitter->getCurrentColorScheme(), false ); mGlobalSearchTree->setId( "search_tree" ); mGlobalSearchTree->setParent( mUISceneNode->getRoot() ); mGlobalSearchTree->setExpanderIconSize( PixelDensity::dpToPx( 20 ) ); @@ -1971,8 +1972,10 @@ bool App::setAutoComplete( bool enable ) { bool App::setLinter( bool enable ) { mConfig.editor.linter = enable; if ( enable && !mLinterModule ) { - mLinterModule = - eeNew( LinterModule, ( mResPath + "assets/linters/linters.json", mThreadPool ) ); + std::string path( mResPath + "assets/linters/linters.json" ); + if ( FileSystem::fileExists( mConfigPath + "linters.json" ) ) + path = mConfigPath + "linters.json"; + mLinterModule = eeNew( LinterModule, ( path, mThreadPool ) ); mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { editor->registerModule( mLinterModule ); } ); return true; @@ -2234,9 +2237,14 @@ void App::initProjectTreeView( const std::string& path ) { } else { std::string rpath( FileSystem::getRealPath( path ) ); - mProjectTreeView->setModel( FileSystemModel::New( - FileSystem::fileRemoveFileName( rpath ), FileSystemModel::Mode::FilesAndDirectories, - { true, true, true } ) ); + mFileSystemModel = FileSystemModel::New( FileSystem::fileRemoveFileName( rpath ), + FileSystemModel::Mode::FilesAndDirectories, + { true, true, true } ); + + mProjectTreeView->setModel( mFileSystemModel ); + + if ( mFileSystemListener ) + mFileSystemListener->setFileSystemModel( mFileSystemModel ); mEditorSplitter->loadFileFromPath( rpath ); } @@ -2257,8 +2265,13 @@ void App::loadFolder( const std::string& path ) { mConfig.loadProject( rpath, mEditorSplitter, mConfigPath ); - mProjectTreeView->setModel( FileSystemModel::New( - rpath, FileSystemModel::Mode::FilesAndDirectories, { true, true, true } ) ); + mFileSystemModel = FileSystemModel::New( rpath, FileSystemModel::Mode::FilesAndDirectories, + { true, true, true } ); + + mProjectTreeView->setModel( mFileSystemModel ); + + if ( mFileSystemListener ) + mFileSystemListener->setFileSystemModel( mFileSystemModel ); auto found = std::find( mRecentFolders.begin(), mRecentFolders.end(), rpath ); if ( found != mRecentFolders.end() ) @@ -2592,7 +2605,7 @@ void App::init( const std::string& file, const Float& pidelDensity ) { #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN mFileWatcher = new efsw::FileWatcher(); - mFileSystemListener = new FileSystemListener( mEditorSplitter ); + mFileSystemListener = new FileSystemListener( mEditorSplitter, mFileSystemModel ); mFileWatcher->watch(); #endif diff --git a/src/tools/codeeditor/codeeditor.hpp b/src/tools/codeeditor/codeeditor.hpp index 2ed4284e3..ca5f49b3e 100644 --- a/src/tools/codeeditor/codeeditor.hpp +++ b/src/tools/codeeditor/codeeditor.hpp @@ -188,6 +188,7 @@ class App : public UICodeEditorSplitter::Client { std::shared_ptr mThreadPool; std::unique_ptr mDirTree; UITreeView* mProjectTreeView{ nullptr }; + std::shared_ptr mFileSystemModel; UITableView* mLocateTable{ nullptr }; UITextInput* mLocateInput{ nullptr }; UITreeViewGlobalSearch* mGlobalSearchTree{ nullptr }; diff --git a/src/tools/codeeditor/filesystemlistener.cpp b/src/tools/codeeditor/filesystemlistener.cpp index e693df122..6915d364c 100644 --- a/src/tools/codeeditor/filesystemlistener.cpp +++ b/src/tools/codeeditor/filesystemlistener.cpp @@ -1,16 +1,30 @@ #include "filesystemlistener.hpp" -FileSystemListener::FileSystemListener( UICodeEditorSplitter* splitter ) : mSplitter( splitter ) {} +FileSystemListener::FileSystemListener( UICodeEditorSplitter* splitter, + std::shared_ptr fileSystemModel ) : + mSplitter( splitter ), mFileSystemModel( fileSystemModel ) {} void FileSystemListener::handleFileAction( efsw::WatchID, const std::string& dir, const std::string& filename, efsw::Action action, std::string ) { - if ( action == efsw::Actions::Modified ) { - FileInfo file( dir + filename ); - if ( file.isLink() ) - file = FileInfo( file.linksTo() ); - if ( isFileOpen( file ) ) - notifyChange( file ); + FileInfo file( dir + filename ); + + switch ( action ) { + case efsw::Actions::Add: + case efsw::Actions::Delete: + case efsw::Actions::Moved: { + auto* node = mFileSystemModel.get()->getNodeFromPath( file.getFilepath(), true, false ); + if ( node ) { + node->invalidate(); + mFileSystemModel.get()->invalidate(); + } + } + case efsw::Actions::Modified: { + if ( file.isLink() ) + file = FileInfo( file.linksTo() ); + if ( isFileOpen( file ) ) + notifyChange( file ); + } } } diff --git a/src/tools/codeeditor/filesystemlistener.hpp b/src/tools/codeeditor/filesystemlistener.hpp index 7f64c5996..0e78b7a65 100644 --- a/src/tools/codeeditor/filesystemlistener.hpp +++ b/src/tools/codeeditor/filesystemlistener.hpp @@ -2,25 +2,31 @@ #define FILESYSTEMLISTENER_HPP #include +#include #include #include #include using namespace EE::System; using namespace EE::UI; +using namespace EE::UI::Models; using namespace EE::UI::Tools; class FileSystemListener : public efsw::FileWatchListener { public: - FileSystemListener( UICodeEditorSplitter* codeSplitter ); + FileSystemListener( UICodeEditorSplitter* codeSplitter, + std::shared_ptr fileSystemModel ); virtual ~FileSystemListener() {} void handleFileAction( efsw::WatchID, const std::string& dir, const std::string& filename, efsw::Action action, std::string ); + void setFileSystemModel( std::shared_ptr model ) { mFileSystemModel = model; } + protected: UICodeEditorSplitter* mSplitter; + std::shared_ptr mFileSystemModel; bool isFileOpen( const FileInfo& file ); diff --git a/src/tools/codeeditor/lintermodule.cpp b/src/tools/codeeditor/lintermodule.cpp index 7539ae2be..7d3633ae4 100644 --- a/src/tools/codeeditor/lintermodule.cpp +++ b/src/tools/codeeditor/lintermodule.cpp @@ -50,7 +50,15 @@ void LinterModule::load( const std::string& lintersPath ) { for ( auto& pattern : fp ) linter.files.push_back( pattern.get() ); - linter.warningPattern = obj["warning_pattern"].get(); + auto wp = obj["warning_pattern"]; + + if ( wp.is_array() ) { + for ( auto& warningPattern : wp ) + linter.warningPattern.push_back( warningPattern.get() ); + } else { + linter.warningPattern = { wp.get() }; + } + linter.command = obj["command"].get(); if ( obj.contains( "warning_pattern_order" ) ) { @@ -65,6 +73,9 @@ void LinterModule::load( const std::string& lintersPath ) { linter.warningPatternOrder.type = wpo["type"].get(); } + if ( obj.contains( "column_starts_at_zero" ) ) + linter.columnsStartAtZero = obj["column_starts_at_zero"].get(); + mLinters.emplace_back( std::move( linter ) ); } @@ -220,35 +231,41 @@ void LinterModule::runLinter( TextDocument* doc, const Linter& linter, const std // Log::info( "Linter result:\n%s", data.c_str() ); - LuaPattern pattern( linter.warningPattern ); std::map matches; - for ( auto& match : pattern.gmatch( data ) ) { - LinterMatch linterMatch; - std::string lineStr = match.group( linter.warningPatternOrder.line ); - std::string colStr = linter.warningPatternOrder.col >= 0 - ? match.group( linter.warningPatternOrder.col ) - : ""; - linterMatch.text = match.group( linter.warningPatternOrder.message ); - if ( linter.warningPatternOrder.type >= 0 ) { - std::string type( match.group( linter.warningPatternOrder.type ) ); - String::toLowerInPlace( type ); - if ( String::startsWith( type, "warn" ) ) { - linterMatch.type = LinterType::Warning; - } else if ( String::startsWith( type, "notice" ) ) { - linterMatch.type = LinterType::Notice; + for ( auto& warningPatterm : linter.warningPattern ) { + LuaPattern pattern( warningPatterm ); + for ( auto& match : pattern.gmatch( data ) ) { + LinterMatch linterMatch; + std::string lineStr = match.group( linter.warningPatternOrder.line ); + std::string colStr = linter.warningPatternOrder.col >= 0 + ? match.group( linter.warningPatternOrder.col ) + : ""; + linterMatch.text = match.group( linter.warningPatternOrder.message ); + + if ( linter.warningPatternOrder.type >= 0 ) { + std::string type( match.group( linter.warningPatternOrder.type ) ); + String::toLowerInPlace( type ); + if ( String::startsWith( type, "warn" ) ) { + linterMatch.type = LinterType::Warning; + } else if ( String::startsWith( type, "notice" ) ) { + linterMatch.type = LinterType::Notice; + } } - } - Int64 line; - Int64 col = 1; - if ( !linterMatch.text.empty() && !lineStr.empty() && - String::fromString( line, lineStr ) ) { - if ( !colStr.empty() ) - String::fromString( col, colStr ); - linterMatch.pos = { line - 1, col > 0 ? col - 1 : 0 }; - linterMatch.lineCache = doc->line( line - 1 ).getHash(); - matches.insert( { line - 1, std::move( linterMatch ) } ); + Int64 line; + Int64 col = 1; + if ( !linterMatch.text.empty() && !lineStr.empty() && + String::fromString( line, lineStr ) ) { + if ( !colStr.empty() ) { + String::fromString( col, colStr ); + if ( linter.columnsStartAtZero ) + col++; + } + linterMatch.pos = { line - 1, col > 0 ? col - 1 : 0 }; + linterMatch.lineCache = doc->line( line - 1 ).getHash(); + matches.insert( { line - 1, std::move( linterMatch ) } ); + } } } diff --git a/src/tools/codeeditor/lintermodule.hpp b/src/tools/codeeditor/lintermodule.hpp index 6e4a9dfb2..162bb8d3f 100644 --- a/src/tools/codeeditor/lintermodule.hpp +++ b/src/tools/codeeditor/lintermodule.hpp @@ -10,15 +10,12 @@ using namespace EE; using namespace EE::System; using namespace EE::UI; -enum class LinterType { - Notice, - Warning, - Error -}; +enum class LinterType { Notice, Warning, Error }; struct Linter { std::vector files; - std::string warningPattern; + std::vector warningPattern; + bool columnsStartAtZero{ false }; struct { int line{ 1 }; int col{ 2 }; @@ -87,7 +84,6 @@ class LinterModule : public UICodeEditorModule { void setDocDirty( UICodeEditor* editor ); void invalidateEditors( TextDocument* doc ); - }; #endif // EE_TOOLS_LINTER_HPP diff --git a/src/tools/codeeditor/projectsearch.hpp b/src/tools/codeeditor/projectsearch.hpp index da70eb27d..7d4149578 100644 --- a/src/tools/codeeditor/projectsearch.hpp +++ b/src/tools/codeeditor/projectsearch.hpp @@ -21,9 +21,33 @@ class ProjectSearch { Result( const String& line, const TextRange& pos ) : line( line ), position( pos ) {} String line; TextRange position; + bool selected{ false }; }; std::string file; std::vector results; + bool selected{ false }; + + void setResultsSelected( bool selected ) { + this->selected = selected; + for ( auto& res : results ) + res.selected = selected; + } + + bool allResultsSelected() { + for ( const auto& res : results ) { + if ( !res.selected ) + return false; + } + return true; + } + + bool noneResultsSelected() { + for ( const auto& res : results ) { + if ( res.selected ) + return false; + } + return true; + } }; typedef std::vector Result; @@ -31,7 +55,7 @@ class ProjectSearch { class ResultModel : public Model { public: - enum Column { FileOrPosition, Line, LineEnd, ColumnStart, ColumnEnd }; + enum Column { FileOrPosition, Line, LineEnd, ColumnStart, ColumnEnd, Selected, Data }; ResultModel( const Result& result ) : mResult( result ) {} @@ -114,11 +138,19 @@ class ProjectSearch { .results[index.row()] .position.end() .column() ); + case Selected: + return Variant( + mResult[index.internalId()].results[index.row()].selected ); + case Data: + return Variant( + (void*)&mResult[index.internalId()].results[index.row()] ); } } else { switch ( index.column() ) { case FileOrPosition: return Variant( mResult[index.row()].file.c_str() ); + case Data: + return Variant( (void*)&mResult[index.row()] ); } } } diff --git a/src/tools/codeeditor/uitreeviewglobalsearch.cpp b/src/tools/codeeditor/uitreeviewglobalsearch.cpp index ba59334d9..44de686a7 100644 --- a/src/tools/codeeditor/uitreeviewglobalsearch.cpp +++ b/src/tools/codeeditor/uitreeviewglobalsearch.cpp @@ -1,23 +1,94 @@ #include "uitreeviewglobalsearch.hpp" +#include "projectsearch.hpp" #include #include #include +#include #include #include -UITreeViewGlobalSearch::UITreeViewGlobalSearch( const SyntaxColorScheme& colorScheme ) : - UITreeView(), mColorScheme( colorScheme ) { +UITreeViewGlobalSearch::UITreeViewGlobalSearch( const SyntaxColorScheme& colorScheme, + bool searchReplace ) : + UITreeView(), mColorScheme( colorScheme ), mSearchReplace( searchReplace ) { mLineNumColor = Color::fromString( mUISceneNode->getRoot()->getUIStyle()->getVariable( "--font-hint" ).getValue() ); } UIWidget* UITreeViewGlobalSearch::createCell( UIWidget* rowWidget, const ModelIndex& index ) { UITableCell* widget = index.column() == (Int64)getModel()->treeColumn() - ? UITreeViewCellGlobalSearch::New() + ? UITreeViewCellGlobalSearch::New( mSearchReplace ) : UITableCell::New(); return setupCell( widget, rowWidget, index ); } +std::function UITreeViewCellGlobalSearch::getCheckBoxFn() { + return [&]() -> UITextView* { + UICheckBox* chk = UICheckBox::New(); + + addEventListener( Event::MouseClick, [&, chk]( const Event* event ) { + const MouseEvent* mouseEvent = static_cast( event ); + + Vector2f pos = convertToNodeSpace( mouseEvent->getPosition().asFloat() ); + Rectf box( { convertToNodeSpace( chk->getCurrentButton()->convertToWorldSpace( + chk->getCurrentButton()->getPixelsPosition() ) ), + chk->getCurrentButton()->getPixelsSize() } ); + + if ( box.contains( pos ) ) { + if ( getCurIndex().internalId() != -1 ) { + ProjectSearch::ResultData::Result* result = getResultPtr(); + if ( result ) { + result->selected = !result->selected; + chk->setChecked( result->selected ); + + ProjectSearch::ResultData* parentData = + (ProjectSearch::ResultData*)getDataPtr( getCurIndex().parent() ); + if ( parentData ) + parentData->selected = parentData->allResultsSelected(); + } + } else { + auto* result = getResultDataPtr(); + result->setResultsSelected( !result->selected ); + chk->setChecked( result->selected ); + } + } + return 1; + } ); + return chk; + }; +} + +void* UITreeViewCellGlobalSearch::getDataPtr( const ModelIndex& modelIndex ) { + const ProjectSearch::ResultModel* model = + static_cast( modelIndex.model() ); + ModelIndex index = + model->index( modelIndex.row(), ProjectSearch::ResultModel::Data, modelIndex.parent() ); + Variant var( model->data( index, Model::Role::Custom ) ); + if ( var.is( Variant::Type::DataPtr ) ) + return var.asDataPtr(); + return nullptr; +} + +ProjectSearch::ResultData::Result* UITreeViewCellGlobalSearch::getResultPtr() { + if ( getCurIndex().internalId() == -1 ) + return nullptr; + void* ptr = getDataPtr( getCurIndex() ); + if ( ptr ) + return (ProjectSearch::ResultData::Result*)ptr; + return nullptr; +} + +ProjectSearch::ResultData* UITreeViewCellGlobalSearch::getResultDataPtr() { + if ( getCurIndex().internalId() != -1 ) + return nullptr; + void* ptr = getDataPtr( getCurIndex() ); + if ( ptr ) + return (ProjectSearch::ResultData*)ptr; + return nullptr; +} + +UITreeViewCellGlobalSearch::UITreeViewCellGlobalSearch( bool selectionEnabled ) : + UITreeViewCell( selectionEnabled ? getCheckBoxFn() : nullptr ) {} + UIPushButton* UITreeViewCellGlobalSearch::setText( const String& text ) { if ( text != mTextBox->getText() ) { mTextBox->setVisible( !text.empty() ); @@ -107,13 +178,30 @@ void UITreeViewCellGlobalSearch::draw() { mTextBox->getFont()->getGlyph( L' ', mTextBox->getPixelsFontSize(), false ).advance; Primitives p; p.setColor( pp->getColorScheme().getEditorSyntaxStyle( "selection" ).color ); + Vector2f screenPos( mScreenPos ); + if ( mTextBox->isType( UI_TYPE_CHECKBOX ) ) { + UICheckBox* chk = mTextBox->asType(); + screenPos.x += chk->getRealAlignOffset().x; + } p.drawRectangle( Rectf( - { mScreenPos.x + mTextBox->getPixelsPosition().x + + { screenPos.x + mTextBox->getPixelsPosition().x + getXOffsetCol( hspace, mTextBox->getTextCache()->getTabWidth(), mTextBox->getText(), mSearchStrPos.first ), - mScreenPos.y + mTextBox->getPixelsPosition().y }, + screenPos.y + mTextBox->getPixelsPosition().y }, Sizef( getTextWidth( hspace, mTextBox->getTextCache()->getTabWidth(), mResultStr ), mTextBox->getPixelsSize().getHeight() ) ) ); } } } + +void UITreeViewCellGlobalSearch::updateCell( Model* ) { + if ( mTextBox->isType( UI_TYPE_CHECKBOX ) ) { + UICheckBox* chk = mTextBox->asType(); + auto* result = getResultPtr(); + if ( result ) { + chk->setChecked( result->selected ); + } else if ( auto* dataResult = getResultDataPtr() ) { + chk->setChecked( dataResult->selected ); + } + } +} diff --git a/src/tools/codeeditor/uitreeviewglobalsearch.hpp b/src/tools/codeeditor/uitreeviewglobalsearch.hpp index 26ce8d4b4..a70ee527b 100644 --- a/src/tools/codeeditor/uitreeviewglobalsearch.hpp +++ b/src/tools/codeeditor/uitreeviewglobalsearch.hpp @@ -10,9 +10,11 @@ using namespace EE::UI::Doc; class UITreeViewCellGlobalSearch : public UITreeViewCell { public: - static UITreeViewCellGlobalSearch* New() { return eeNew( UITreeViewCellGlobalSearch, () ); } + static UITreeViewCellGlobalSearch* New( bool selectionEnabled ) { + return eeNew( UITreeViewCellGlobalSearch, ( selectionEnabled ) ); + } - UITreeViewCellGlobalSearch() : UITreeViewCell() {} + UITreeViewCellGlobalSearch( bool selectionEnabled ); UIPushButton* setText( const String& text ); @@ -20,18 +22,28 @@ class UITreeViewCellGlobalSearch : public UITreeViewCell { virtual void draw(); + virtual void updateCell( Model* model ); + protected: std::pair mSearchStrPos; String mResultStr; + + std::function getCheckBoxFn(); + + void* getDataPtr( const ModelIndex& modelIndex ); + + ProjectSearch::ResultData::Result* getResultPtr(); + + ProjectSearch::ResultData* getResultDataPtr(); }; class UITreeViewGlobalSearch : public UITreeView { public: - static UITreeViewGlobalSearch* New( const SyntaxColorScheme& colorScheme ) { - return eeNew( UITreeViewGlobalSearch, ( colorScheme ) ); + static UITreeViewGlobalSearch* New( const SyntaxColorScheme& colorScheme, bool searchReplace ) { + return eeNew( UITreeViewGlobalSearch, ( colorScheme, searchReplace ) ); } - UITreeViewGlobalSearch( const SyntaxColorScheme& colorScheme ); + UITreeViewGlobalSearch( const SyntaxColorScheme& colorScheme, bool searchReplace ); UIWidget* createCell( UIWidget* rowWidget, const ModelIndex& index ); @@ -49,6 +61,7 @@ class UITreeViewGlobalSearch : public UITreeView { Color mLineNumColor; SyntaxColorScheme mColorScheme; String mSearchStr; + bool mSearchReplace{ false }; }; #endif // UITREEVIEWGLOBALSEARCH_HPP