diff --git a/projects/macos/ee.files b/projects/macos/ee.files index 2f43db939..b95b669f6 100644 --- a/projects/macos/ee.files +++ b/projects/macos/ee.files @@ -1292,8 +1292,12 @@ ../../src/tools/ecode/pathhelper.hpp ../../src/tools/ecode/plugins/git/git.cpp ../../src/tools/ecode/plugins/git/git.hpp +../../src/tools/ecode/plugins/git/gitbranchmodel.cpp +../../src/tools/ecode/plugins/git/gitbranchmodel.hpp ../../src/tools/ecode/plugins/git/gitplugin.cpp ../../src/tools/ecode/plugins/git/gitplugin.hpp +../../src/tools/ecode/plugins/git/gitstatusmodel.cpp +../../src/tools/ecode/plugins/git/gitstatusmodel.hpp ../../src/tools/ecode/plugins/linter/linterplugin.cpp ../../src/tools/ecode/plugins/linter/linterplugin.hpp ../../src/tools/ecode/plugins/lsp/lspclientplugin.cpp diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp index f93510584..63f7358dd 100644 --- a/src/tools/ecode/plugins/git/git.hpp +++ b/src/tools/ecode/plugins/git/git.hpp @@ -1,3 +1,6 @@ +#ifndef ECODE_GIT_HPP +#define ECODE_GIT_HPP + #include #include #include @@ -300,3 +303,5 @@ class Git { }; } // namespace ecode + +#endif diff --git a/src/tools/ecode/plugins/git/gitbranchmodel.cpp b/src/tools/ecode/plugins/git/gitbranchmodel.cpp new file mode 100644 index 000000000..4afde7a81 --- /dev/null +++ b/src/tools/ecode/plugins/git/gitbranchmodel.cpp @@ -0,0 +1,132 @@ +#include "gitbranchmodel.hpp" +#include "gitplugin.hpp" + +namespace ecode { + +size_t GitBranchModel::hashBranches( const std::vector& branches ) { + size_t hash = 0; + for ( const auto& branch : branches ) + hash = hashCombine( hash, String::hash( branch.name ), String::hash( branch.remote ), + String::hash( branch.lastCommit ), branch.type, branch.ahead, + branch.behind ); + return hash; +} + +std::string GitBranchModel::refTypeToString( Git::RefType type ) { + switch ( type ) { + case Git::RefType::Head: + return mPlugin->i18n( "git_local_branches", "Local Branches" ).toUtf8(); + case Git::RefType::Remote: + return mPlugin->i18n( "git_remote_branches", "Remote Branches" ).toUtf8(); + case Git::RefType::Tag: + return mPlugin->i18n( "git_tags", "Tags" ).toUtf8(); + default: + break; + } + return ""; +} + +GitBranchModel::GitBranchModel( std::vector&& branches, size_t hash, + GitPlugin* gitPlugin ) : + mPlugin( gitPlugin ), mHash( hash ) { + std::map> branchTypes; + for ( auto& branch : branches ) { + auto& type = branchTypes[refTypeToString( branch.type )]; + type.emplace_back( std::move( branch ) ); + } + for ( auto& branch : branchTypes ) { + mBranches.emplace_back( + BranchData{ std::move( branch.first ), std::move( branch.second ) } ); + } +} + +size_t GitBranchModel::rowCount( const ModelIndex& index ) const { + if ( !index.isValid() ) + return mBranches.size(); + if ( index.internalId() == -1 ) + return mBranches[index.row()].data.size(); + return 0; +} + +ModelIndex GitBranchModel::parentIndex( const ModelIndex& index ) const { + if ( !index.isValid() || index.internalId() == -1 ) + return {}; + return createIndex( index.internalId(), index.column(), &mBranches[index.internalId()], -1 ); +} + +ModelIndex GitBranchModel::index( int row, int column, const ModelIndex& parent ) const { + if ( row < 0 || column < 0 ) + return {}; + if ( !parent.isValid() ) + return createIndex( row, column, &mBranches[row], -1 ); + if ( parent.internalData() ) + return createIndex( row, column, &mBranches[parent.row()].data[row], parent.row() ); + return {}; +} + +UIIcon* GitBranchModel::iconFor( const ModelIndex& index ) const { + if ( index.column() == (Int64)treeColumn() ) { + if ( index.hasParent() ) { + Git::Branch* branch = static_cast( index.internalData() ); + return mPlugin->findIcon( branch->type == Git::RefType::Tag ? GIT_TAG : GIT_REPO ); + } + } + return nullptr; +} + +Variant GitBranchModel::data( const ModelIndex& index, ModelRole role ) const { + switch ( role ) { + case ModelRole::Display: { + if ( index.internalId() == -1 ) { + if ( index.column() == Column::Name ) + return Variant( String::format( "%s (%zu)", mBranches[index.row()].branch, + mBranches[index.row()].data.size() ) ); + return Variant( GIT_EMPTY ); + } + const Git::Branch& branch = mBranches[index.internalId()].data[index.row()]; + switch ( index.column() ) { + case Column::Name: { + if ( branch.type == Git::Remote && + String::startsWith( branch.name, "origin/" ) ) { + return Variant( std::string_view{ branch.name }.substr( 7 ).data() ); + } else if ( branch.type == Git::Head && ( branch.ahead || branch.behind ) ) { + if ( branch.ahead && branch.behind ) { + return Variant( String::format( "%s (+%ld/-%ld)", branch.name, + branch.ahead, branch.behind ) ); + } else if ( branch.ahead ) { + return Variant( + String::format( "%s (+%ld)", branch.name, branch.ahead ) ); + } else { + return Variant( + String::format( "%s (-%ld)", branch.name, branch.behind ) ); + } + } + return Variant( branch.name.c_str() ); + } + case Column::Remote: + return Variant( branch.remote.c_str() ); + case Column::Type: + return Variant( branch.typeStr() ); + case Column::LastCommit: + return Variant( branch.lastCommit.c_str() ); + } + return Variant( GIT_EMPTY ); + } + case ModelRole::Class: { + if ( index.internalId() == -1 ) + return Variant( GIT_BOLD ); + const Git::Branch& branch = mBranches[index.internalId()].data[index.row()]; + if ( branch.name == mPlugin->gitBranch() ) + return Variant( GIT_BOLD ); + return Variant( GIT_NOT_BOLD ); + } + case ModelRole::Icon: { + return iconFor( index ); + } + default: + break; + } + return {}; +} + +} // namespace ecode diff --git a/src/tools/ecode/plugins/git/gitbranchmodel.hpp b/src/tools/ecode/plugins/git/gitbranchmodel.hpp new file mode 100644 index 000000000..8a57922ca --- /dev/null +++ b/src/tools/ecode/plugins/git/gitbranchmodel.hpp @@ -0,0 +1,64 @@ +#ifndef ECODE_GITBRANCHMODEL_HPP +#define ECODE_GITBRANCHMODEL_HPP + +#include "git.hpp" + +#include + +#include + +using namespace EE; +using namespace EE::UI; +using namespace EE::UI::Models; + +namespace ecode { + +class GitPlugin; + +class GitBranchModel : public Model { + public: + static std::shared_ptr asModel( std::vector&& branches, + size_t hash, GitPlugin* gitPlugin ) { + return std::make_shared( std::move( branches ), hash, gitPlugin ); + } + + static size_t hashBranches( const std::vector& branches ); + + enum Column { Name, Remote, Type, LastCommit }; + + struct BranchData { + std::string branch; + std::vector data; + }; + + std::string refTypeToString( Git::RefType type ); + + GitBranchModel( std::vector&& branches, size_t hash, GitPlugin* gitPlugin ); + + size_t treeColumn() const { return Column::Name; } + + size_t rowCount( const ModelIndex& index ) const; + + size_t columnCount( const ModelIndex& ) const { return 4; } + + ModelIndex parentIndex( const ModelIndex& index ) const; + + ModelIndex index( int row, int column, const ModelIndex& parent ) const; + + UIIcon* iconFor( const ModelIndex& index ) const; + + Variant data( const ModelIndex& index, ModelRole role ) const; + + virtual bool classModelRoleEnabled() { return true; } + + size_t getHash() const { return mHash; } + + protected: + std::vector mBranches; + GitPlugin* mPlugin{ nullptr }; + size_t mHash{ 0 }; +}; + +} // namespace ecode + +#endif // ECODE_GITBRANCHMODEL_HPP diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index 257766b8b..93c219e57 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -1,4 +1,6 @@ #include "gitplugin.hpp" +#include "gitbranchmodel.hpp" +#include "gitstatusmodel.hpp" #include #include #include @@ -27,14 +29,6 @@ using json = nlohmann::json; namespace ecode { -static const char* GIT_EMPTY = ""; -static const char* GIT_SUCCESS = "success"; -static const char* GIT_ERROR = "error"; -static const char* GIT_BOLD = "bold"; -static const char* GIT_NOT_BOLD = "notbold"; -static const char* GIT_TAG = "tag"; -static const char* GIT_REPO = "repo"; - std::string GitPlugin::statusTypeToString( Git::GitStatusType type ) { switch ( type ) { case Git::GitStatusType::Untracked: @@ -58,420 +52,6 @@ std::vector GitPlugin::repos() { return ret; } -static size_t hashBranches( const std::vector& branches ) { - size_t hash = 0; - for ( const auto& branch : branches ) - hash = hashCombine( hash, String::hash( branch.name ), String::hash( branch.remote ), - String::hash( branch.lastCommit ), branch.type, branch.ahead, - branch.behind ); - return hash; -} - -class GitBranchModel : public Model { - public: - static std::shared_ptr asModel( std::vector&& branches, - size_t hash, GitPlugin* gitPlugin ) { - return std::make_shared( std::move( branches ), hash, gitPlugin ); - } - - enum Column { Name, Remote, Type, LastCommit }; - - struct BranchData { - std::string branch; - std::vector data; - }; - - std::string refTypeToString( Git::RefType type ) { - switch ( type ) { - case Git::RefType::Head: - return mPlugin->i18n( "git_local_branches", "Local Branches" ).toUtf8(); - case Git::RefType::Remote: - return mPlugin->i18n( "git_remote_branches", "Remote Branches" ).toUtf8(); - case Git::RefType::Tag: - return mPlugin->i18n( "git_tags", "Tags" ).toUtf8(); - default: - break; - } - return ""; - } - - GitBranchModel( std::vector&& branches, size_t hash, GitPlugin* gitPlugin ) : - mPlugin( gitPlugin ), mHash( hash ) { - std::map> branchTypes; - for ( auto& branch : branches ) { - auto& type = branchTypes[refTypeToString( branch.type )]; - type.emplace_back( std::move( branch ) ); - } - for ( auto& branch : branchTypes ) { - mBranches.emplace_back( - BranchData{ std::move( branch.first ), std::move( branch.second ) } ); - } - } - - size_t treeColumn() const { return Column::Name; } - - size_t rowCount( const ModelIndex& index ) const { - if ( !index.isValid() ) - return mBranches.size(); - if ( index.internalId() == -1 ) - return mBranches[index.row()].data.size(); - return 0; - } - - size_t columnCount( const ModelIndex& ) const { return 4; } - - ModelIndex parentIndex( const ModelIndex& index ) const { - if ( !index.isValid() || index.internalId() == -1 ) - return {}; - return createIndex( index.internalId(), index.column(), &mBranches[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, &mBranches[row], -1 ); - if ( parent.internalData() ) - return createIndex( row, column, &mBranches[parent.row()].data[row], parent.row() ); - return {}; - } - - UIIcon* iconFor( const ModelIndex& index ) const { - if ( index.column() == (Int64)treeColumn() ) { - if ( index.hasParent() ) { - Git::Branch* branch = static_cast( index.internalData() ); - return mPlugin->findIcon( branch->type == Git::RefType::Tag ? GIT_TAG : GIT_REPO ); - } - } - return nullptr; - } - - Variant data( const ModelIndex& index, ModelRole role ) const { - switch ( role ) { - case ModelRole::Display: { - if ( index.internalId() == -1 ) { - if ( index.column() == Column::Name ) - return Variant( String::format( "%s (%zu)", mBranches[index.row()].branch, - mBranches[index.row()].data.size() ) ); - return Variant( GIT_EMPTY ); - } - const Git::Branch& branch = mBranches[index.internalId()].data[index.row()]; - switch ( index.column() ) { - case Column::Name: { - if ( branch.type == Git::Remote && - String::startsWith( branch.name, "origin/" ) ) { - return Variant( std::string_view{ branch.name }.substr( 7 ).data() ); - } else if ( branch.type == Git::Head && - ( branch.ahead || branch.behind ) ) { - if ( branch.ahead && branch.behind ) { - return Variant( String::format( "%s (+%ld/-%ld)", branch.name, - branch.ahead, branch.behind ) ); - } else if ( branch.ahead ) { - return Variant( - String::format( "%s (+%ld)", branch.name, branch.ahead ) ); - } else { - return Variant( - String::format( "%s (-%ld)", branch.name, branch.behind ) ); - } - } - return Variant( branch.name.c_str() ); - } - case Column::Remote: - return Variant( branch.remote.c_str() ); - case Column::Type: - return Variant( branch.typeStr() ); - case Column::LastCommit: - return Variant( branch.lastCommit.c_str() ); - } - return Variant( GIT_EMPTY ); - } - case ModelRole::Class: { - if ( index.internalId() == -1 ) - return Variant( GIT_BOLD ); - const Git::Branch& branch = mBranches[index.internalId()].data[index.row()]; - if ( branch.name == mPlugin->gitBranch() ) - return Variant( GIT_BOLD ); - return Variant( GIT_NOT_BOLD ); - } - case ModelRole::Icon: { - return iconFor( index ); - } - default: - break; - } - return {}; - } - - virtual bool classModelRoleEnabled() { return true; } - - size_t getHash() const { return mHash; } - - protected: - std::vector mBranches; - GitPlugin* mPlugin{ nullptr }; - size_t mHash{ 0 }; -}; - -class GitStatusModel : public Model { - public: - static std::shared_ptr asModel( Git::FilesStatus status, - GitPlugin* gitPlugin ) { - return std::make_shared( std::move( status ), gitPlugin ); - } - - struct RepoStatusType; - struct RepoStatus; - - struct DiffFile : Git::DiffFile { - DiffFile( Git::DiffFile&& df, RepoStatusType* parent ) : - Git::DiffFile( df ), parent( parent ){}; - RepoStatusType* parent; - }; - - struct RepoStatusType { - std::string typeStr; - Git::GitStatusType type; - std::vector files; - RepoStatus* parent{ nullptr }; - }; - - struct RepoStatus { - std::string repo; - std::vector type; - }; - - enum Column { File, State, Inserted, Removed, RelativeDirectory }; - - GitStatusModel( Git::FilesStatus&& status, GitPlugin* gitPlugin ) : mPlugin( gitPlugin ) { - std::map> typesFound; - std::unordered_map repoPos; - std::unordered_map> repoTypePos; - - for ( auto& s : status ) - for ( auto& f : s.second ) - typesFound[s.first].insert( f.report.type ); - - for ( const auto& tf : typesFound ) { - RepoStatus rs; - rs.repo = tf.first; - size_t pos = mStatus.size(); - repoPos[rs.repo] = pos; - for ( const auto& s : tf.second ) { - RepoStatusType rt; - rt.typeStr = mPlugin->statusTypeToString( s ); - rt.type = s; - repoTypePos[pos][s] = rs.type.size(); - rs.type.emplace_back( std::move( rt ) ); - } - mStatus.emplace_back( std::move( rs ) ); - } - - for ( auto& s : status ) { - for ( auto& fv : s.second ) { - auto pos = repoPos[s.first]; - auto typePos = repoTypePos[pos][fv.report.type]; - DiffFile df( std::move( fv ), &mStatus[pos].type[typePos] ); - mStatus[pos].type[typePos].files.emplace_back( std::move( df ) ); - } - } - - // Set the parents after the addreses are stable - for ( auto& status : mStatus ) { - for ( auto& type : status.type ) { - for ( auto& f : type.files ) - f.parent = &type; - type.parent = &status; - } - } - } - - size_t treeColumn() const { return Column::File; } - - size_t rowCount( const ModelIndex& index ) const { - if ( !index.isValid() ) - return mStatus.size(); - - if ( index.internalId() == Repo ) - return mStatus[index.row()].type.size(); - - if ( index.internalId() == Status ) - return mStatus[index.parent().row()].type[index.row()].files.size(); - - return 0; - } - - size_t columnCount( const ModelIndex& ) const { return 5; } - - ModelIndex parentIndex( const ModelIndex& index ) const { - if ( !index.isValid() || index.internalId() == Repo ) - return {}; - - if ( index.internalId() == Status ) { - RepoStatusType* status = reinterpret_cast( index.internalData() ); - size_t f = 0; - size_t statusSize = mStatus.size(); - for ( size_t i = 0; i < statusSize; i++ ) { - if ( &mStatus[i] == status->parent ) { - f = i; - break; - } - } - return createIndex( f, index.column(), status->parent, Repo ); - } - - if ( index.internalId() == GitFile ) { - DiffFile* file = reinterpret_cast( index.internalData() ); - RepoStatusType* status = file->parent; - RepoStatus* repoStatus = status->parent; - size_t typeSize = repoStatus->type.size(); - size_t f = 0; - for ( size_t i = 0; i < typeSize; i++ ) { - if ( &status->parent->type[i] == status ) { - f = i; - break; - } - } - return createIndex( f, index.column(), status, Status ); - } - - return {}; - } - - enum ModelCategory { Repo, Status, GitFile }; - - ModelIndex index( int row, int column, const ModelIndex& parent ) const { - if ( row < 0 || column < 0 ) - return {}; - - if ( !parent.isValid() ) - return createIndex( row, column, &mStatus[row], Repo ); - - if ( parent.internalId() == Repo ) - return createIndex( row, column, &mStatus[parent.row()].type[row], Status ); - - if ( parent.internalId() == Status ) { - size_t pprow = parent.parent().row(); - size_t prow = parent.row(); - return createIndex( row, column, &mStatus[pprow].type[prow].files[row], GitFile ); - } - - return {}; - } - - Variant data( const ModelIndex& index, ModelRole role ) const { - switch ( role ) { - case ModelRole::Display: { - if ( index.internalId() == Repo ) { - if ( index.column() == Column::File ) - return Variant( mStatus[index.row()].repo.c_str() ); - return Variant( GIT_EMPTY ); - } else if ( index.internalId() == Status ) { - if ( index.column() == Column::File ) { - return Variant( - mStatus[index.parent().row()].type[index.row()].typeStr.c_str() ); - } - return Variant( GIT_EMPTY ); - } - const Git::DiffFile& s = mStatus[index.parent().parent().row()] - .type[index.parent().row()] - .files[index.row()]; - switch ( index.column() ) { - case Column::File: - return Variant( FileSystem::fileNameFromPath( s.file ) ); - case Column::Inserted: - return Variant( String::format( "+%d ", s.inserts ) ); - case Column::Removed: - return Variant( String::format( "-%d ", s.deletes ) ); - case Column::State: - return Variant( String::format( "%c", s.report.symbol ) ); - case Column::RelativeDirectory: - return Variant( FileSystem::fileRemoveFileName( s.file ) ); - } - break; - } - case ModelRole::Class: { - if ( index.internalId() == GitFile ) { - switch ( index.column() ) { - case Column::Inserted: - return Variant( GIT_SUCCESS ); - case Column::Removed: - return Variant( GIT_ERROR ); - default: - break; - } - } - break; - } - case ModelRole::Icon: { - if ( (Int64)treeColumn() == index.column() ) { - if ( index.internalId() == Repo ) { - return Variant( mPlugin->findIcon( "repo" ) ); - } else if ( index.internalId() == GitFile ) { - const Git::DiffFile& s = mStatus[index.parent().parent().row()] - .type[index.parent().row()] - .files[index.row()]; - std::string iconName = - UIIconThemeManager::getIconNameFromFileName( s.file ); - auto* scene = mPlugin->getUISceneNode(); - auto* d = scene->findIcon( iconName ); - if ( !d ) - return scene->findIcon( "file" ); - return d; - } - } - break; - } - default: - break; - } - return {}; - } - - virtual bool classModelRoleEnabled() { return true; } - - const RepoStatus* repo( const ModelIndex& index ) const { - if ( index.internalId() != Repo ) - return nullptr; - return &mStatus[index.row()]; - } - - const RepoStatusType* statusType( const ModelIndex& index ) const { - if ( index.internalId() != Status ) - return nullptr; - return &mStatus[index.parent().row()].type[index.row()]; - } - - const DiffFile* file( const ModelIndex& index ) const { - if ( index.internalId() != GitFile ) - return nullptr; - return &mStatus[index.parent().parent().row()] - .type[index.parent().row()] - .files[index.row()]; - } - - std::vector getFiles( const std::string& repo, uint32_t statusType ) const { - std::vector files; - for ( const auto& status : mStatus ) { - if ( status.repo == repo ) { - for ( const auto& type : status.type ) { - if ( static_cast( type.type ) & statusType ) { - for ( const auto& file : type.files ) - files.push_back( file.file ); - break; - } - } - break; - } - } - return files; - } - - protected: - std::vector mStatus; - GitPlugin* mPlugin{ nullptr }; -}; - Plugin* GitPlugin::New( PluginManager* pluginManager ) { return eeNew( GitPlugin, ( pluginManager, false ) ); } @@ -1238,7 +818,7 @@ void GitPlugin::updateBranches( bool force ) { } auto branches = mGit->getAllBranchesAndTags( Git::RefType::All, {}, repoSelected() ); - auto hash = hashBranches( branches ); + auto hash = GitBranchModel::hashBranches( branches ); auto model = GitBranchModel::asModel( std::move( branches ), hash, this ); if ( mBranchesTree && diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp index a2af700cc..c7fdcbda6 100644 --- a/src/tools/ecode/plugins/git/gitplugin.hpp +++ b/src/tools/ecode/plugins/git/gitplugin.hpp @@ -24,6 +24,14 @@ namespace ecode { class Git; class GitBranchModel; +static constexpr const char* GIT_EMPTY = ""; +static constexpr const char* GIT_SUCCESS = "success"; +static constexpr const char* GIT_ERROR = "error"; +static constexpr const char* GIT_BOLD = "bold"; +static constexpr const char* GIT_NOT_BOLD = "notbold"; +static constexpr const char* GIT_TAG = "tag"; +static constexpr const char* GIT_REPO = "repo"; + class GitPlugin : public PluginBase { public: static PluginDefinition Definition() { diff --git a/src/tools/ecode/plugins/git/gitstatusmodel.cpp b/src/tools/ecode/plugins/git/gitstatusmodel.cpp new file mode 100644 index 000000000..dd05f0075 --- /dev/null +++ b/src/tools/ecode/plugins/git/gitstatusmodel.cpp @@ -0,0 +1,227 @@ +#include "gitstatusmodel.hpp" +#include "gitplugin.hpp" + +#include +#include + +#include +#include + +namespace ecode { + +GitStatusModel::GitStatusModel( Git::FilesStatus&& status, GitPlugin* gitPlugin ) : + mPlugin( gitPlugin ) { + std::map> typesFound; + std::unordered_map repoPos; + std::unordered_map> repoTypePos; + + for ( auto& s : status ) + for ( auto& f : s.second ) + typesFound[s.first].insert( f.report.type ); + + for ( const auto& tf : typesFound ) { + RepoStatus rs; + rs.repo = tf.first; + size_t pos = mStatus.size(); + repoPos[rs.repo] = pos; + for ( const auto& s : tf.second ) { + RepoStatusType rt; + rt.typeStr = mPlugin->statusTypeToString( s ); + rt.type = s; + repoTypePos[pos][s] = rs.type.size(); + rs.type.emplace_back( std::move( rt ) ); + } + mStatus.emplace_back( std::move( rs ) ); + } + + for ( auto& s : status ) { + for ( auto& fv : s.second ) { + auto pos = repoPos[s.first]; + auto typePos = repoTypePos[pos][fv.report.type]; + DiffFile df( std::move( fv ), &mStatus[pos].type[typePos] ); + mStatus[pos].type[typePos].files.emplace_back( std::move( df ) ); + } + } + + // Set the parents after the addreses are stable + for ( auto& status : mStatus ) { + for ( auto& type : status.type ) { + for ( auto& f : type.files ) + f.parent = &type; + type.parent = &status; + } + } +} + +size_t GitStatusModel::rowCount( const ModelIndex& index ) const { + if ( !index.isValid() ) + return mStatus.size(); + + if ( index.internalId() == Repo ) + return mStatus[index.row()].type.size(); + + if ( index.internalId() == Status ) + return mStatus[index.parent().row()].type[index.row()].files.size(); + + return 0; +} + +ModelIndex GitStatusModel::parentIndex( const ModelIndex& index ) const { + if ( !index.isValid() || index.internalId() == Repo ) + return {}; + + if ( index.internalId() == Status ) { + RepoStatusType* status = reinterpret_cast( index.internalData() ); + size_t f = 0; + size_t statusSize = mStatus.size(); + for ( size_t i = 0; i < statusSize; i++ ) { + if ( &mStatus[i] == status->parent ) { + f = i; + break; + } + } + return createIndex( f, index.column(), status->parent, Repo ); + } + + if ( index.internalId() == GitFile ) { + DiffFile* file = reinterpret_cast( index.internalData() ); + RepoStatusType* status = file->parent; + RepoStatus* repoStatus = status->parent; + size_t typeSize = repoStatus->type.size(); + size_t f = 0; + for ( size_t i = 0; i < typeSize; i++ ) { + if ( &status->parent->type[i] == status ) { + f = i; + break; + } + } + return createIndex( f, index.column(), status, Status ); + } + + return {}; +} + +ModelIndex GitStatusModel::index( int row, int column, const ModelIndex& parent ) const { + if ( row < 0 || column < 0 ) + return {}; + + if ( !parent.isValid() ) + return createIndex( row, column, &mStatus[row], Repo ); + + if ( parent.internalId() == Repo ) + return createIndex( row, column, &mStatus[parent.row()].type[row], Status ); + + if ( parent.internalId() == Status ) { + size_t pprow = parent.parent().row(); + size_t prow = parent.row(); + return createIndex( row, column, &mStatus[pprow].type[prow].files[row], GitFile ); + } + + return {}; +} + +Variant GitStatusModel::data( const ModelIndex& index, ModelRole role ) const { + switch ( role ) { + case ModelRole::Display: { + if ( index.internalId() == Repo ) { + if ( index.column() == Column::File ) + return Variant( mStatus[index.row()].repo.c_str() ); + return Variant( GIT_EMPTY ); + } else if ( index.internalId() == Status ) { + if ( index.column() == Column::File ) { + return Variant( + mStatus[index.parent().row()].type[index.row()].typeStr.c_str() ); + } + return Variant( GIT_EMPTY ); + } + const Git::DiffFile& s = mStatus[index.parent().parent().row()] + .type[index.parent().row()] + .files[index.row()]; + switch ( index.column() ) { + case Column::File: + return Variant( FileSystem::fileNameFromPath( s.file ) ); + case Column::Inserted: + return Variant( String::format( "+%d ", s.inserts ) ); + case Column::Removed: + return Variant( String::format( "-%d ", s.deletes ) ); + case Column::State: + return Variant( String::format( "%c", s.report.symbol ) ); + case Column::RelativeDirectory: + return Variant( FileSystem::fileRemoveFileName( s.file ) ); + } + break; + } + case ModelRole::Class: { + if ( index.internalId() == GitFile ) { + switch ( index.column() ) { + case Column::Inserted: + return Variant( GIT_SUCCESS ); + case Column::Removed: + return Variant( GIT_ERROR ); + default: + break; + } + } + break; + } + case ModelRole::Icon: { + if ( (Int64)treeColumn() == index.column() ) { + if ( index.internalId() == Repo ) { + return Variant( mPlugin->findIcon( "repo" ) ); + } else if ( index.internalId() == GitFile ) { + const Git::DiffFile& s = mStatus[index.parent().parent().row()] + .type[index.parent().row()] + .files[index.row()]; + std::string iconName = UIIconThemeManager::getIconNameFromFileName( s.file ); + auto* scene = mPlugin->getUISceneNode(); + auto* d = scene->findIcon( iconName ); + if ( !d ) + return scene->findIcon( "file" ); + return d; + } + } + break; + } + default: + break; + } + return {}; +} + +const GitStatusModel::RepoStatus* GitStatusModel::repo( const ModelIndex& index ) const { + if ( index.internalId() != Repo ) + return nullptr; + return &mStatus[index.row()]; +} + +const GitStatusModel::RepoStatusType* GitStatusModel::statusType( const ModelIndex& index ) const { + if ( index.internalId() != Status ) + return nullptr; + return &mStatus[index.parent().row()].type[index.row()]; +} + +const GitStatusModel::DiffFile* GitStatusModel::file( const ModelIndex& index ) const { + if ( index.internalId() != GitFile ) + return nullptr; + return &mStatus[index.parent().parent().row()].type[index.parent().row()].files[index.row()]; +} + +std::vector GitStatusModel::getFiles( const std::string& repo, + uint32_t statusType ) const { + std::vector files; + for ( const auto& status : mStatus ) { + if ( status.repo == repo ) { + for ( const auto& type : status.type ) { + if ( static_cast( type.type ) & statusType ) { + for ( const auto& file : type.files ) + files.push_back( file.file ); + break; + } + } + break; + } + } + return files; +} + +} // namespace ecode diff --git a/src/tools/ecode/plugins/git/gitstatusmodel.hpp b/src/tools/ecode/plugins/git/gitstatusmodel.hpp new file mode 100644 index 000000000..18147df44 --- /dev/null +++ b/src/tools/ecode/plugins/git/gitstatusmodel.hpp @@ -0,0 +1,78 @@ +#ifndef ECODE_GITSTATUSMODEL_HPP +#define ECODE_GITSTATUSMODEL_HPP + +#include "git.hpp" +#include + +using namespace EE; +using namespace EE::UI; +using namespace EE::UI::Models; + +namespace ecode { + +class GitPlugin; + +class GitStatusModel : public Model { + public: + static std::shared_ptr asModel( Git::FilesStatus status, + GitPlugin* gitPlugin ) { + return std::make_shared( std::move( status ), gitPlugin ); + } + + struct RepoStatusType; + struct RepoStatus; + + struct DiffFile : Git::DiffFile { + DiffFile( Git::DiffFile&& df, RepoStatusType* parent ) : + Git::DiffFile( df ), parent( parent ){}; + RepoStatusType* parent; + }; + + struct RepoStatusType { + std::string typeStr; + Git::GitStatusType type; + std::vector files; + RepoStatus* parent{ nullptr }; + }; + + struct RepoStatus { + std::string repo; + std::vector type; + }; + + enum Column { File, State, Inserted, Removed, RelativeDirectory }; + + GitStatusModel( Git::FilesStatus&& status, GitPlugin* gitPlugin ); + + size_t treeColumn() const { return Column::File; } + + size_t rowCount( const ModelIndex& index ) const; + + size_t columnCount( const ModelIndex& ) const { return 5; } + + ModelIndex parentIndex( const ModelIndex& index ) const; + + enum ModelCategory { Repo, Status, GitFile }; + + ModelIndex index( int row, int column, const ModelIndex& parent ) const; + + Variant data( const ModelIndex& index, ModelRole role ) const; + + virtual bool classModelRoleEnabled() { return true; } + + const RepoStatus* repo( const ModelIndex& index ) const; + + const RepoStatusType* statusType( const ModelIndex& index ) const; + + const DiffFile* file( const ModelIndex& index ) const; + + std::vector getFiles( const std::string& repo, uint32_t statusType ) const; + + protected: + std::vector mStatus; + GitPlugin* mPlugin{ nullptr }; +}; + +} // namespace ecode + +#endif // ECODE_GITSTATUSMODEL_HPP