From f79d49e7a10ed65e0d57147136b92094922d8cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 14 Jan 2024 02:50:55 -0300 Subject: [PATCH] Git models as trees --- src/tools/ecode/featureshealth.cpp | 19 ++-- src/tools/ecode/plugins/git/git.cpp | 112 ++++++++++++++++---- src/tools/ecode/plugins/git/git.hpp | 12 ++- src/tools/ecode/plugins/git/gitplugin.hpp | 122 +++++++++++++++++++--- 4 files changed, 223 insertions(+), 42 deletions(-) diff --git a/src/tools/ecode/featureshealth.cpp b/src/tools/ecode/featureshealth.cpp index 2abcc0fb9..38f68a888 100644 --- a/src/tools/ecode/featureshealth.cpp +++ b/src/tools/ecode/featureshealth.cpp @@ -266,6 +266,10 @@ class HealthModel : public Model { } virtual Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const { + static const char* SUCCESS = "theme-success"; + static const char* ERROR = "theme-error"; + static const char* NONE = "theme-none"; + eeASSERT( index.row() < (Int64)mData.size() ); static std::string none; static UIIcon* icon = nullptr; @@ -302,25 +306,24 @@ class HealthModel : public Model { case ModelRole::Class: { switch ( index.column() ) { case 1: - return Variant( "theme-success" ); + return Variant( SUCCESS ); case 2: if ( !lang.lsp.name.empty() ) - return Variant( lang.lsp.found ? "theme-success" : "theme-error" ); + return Variant( lang.lsp.found ? SUCCESS : ERROR ); else - return Variant( "theme-none" ); + return Variant( NONE ); break; case 3: if ( !lang.linter.name.empty() ) - return Variant( lang.linter.found ? "theme-success" : "theme-error" ); + return Variant( lang.linter.found ? SUCCESS : ERROR ); else - return Variant( "theme-none" ); + return Variant( NONE ); break; case 4: if ( !lang.formatter.name.empty() ) - return Variant( lang.formatter.found ? "theme-success" - : "theme-error" ); + return Variant( lang.formatter.found ? SUCCESS : ERROR ); else - return Variant( "theme-none" ); + return Variant( NONE ); break; default: { } diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp index ddb9e63a6..22565777f 100644 --- a/src/tools/ecode/plugins/git/git.cpp +++ b/src/tools/ecode/plugins/git/git.cpp @@ -54,6 +54,10 @@ int Git::git( const std::string& args, const std::string& projectDir, std::strin p.readAllStdOut( buf ); int retCode; p.join( &retCode ); + if ( mLastProjectPath != projectDir ) { + const_cast( this )->mLastProjectPath = projectDir; + const_cast( this )->mSubModulesUpdated = false; + } return retCode; } @@ -205,11 +209,53 @@ std::vector Git::getAllBranchesAndTags( RefType ref, const std::str return branches; } +static void readAllLines( const std::string_view& buf, + std::function onLineRead ) { + auto lastNL = 0; + auto nextNL = buf.find_first_of( '\n' ); + while ( nextNL != std::string_view::npos ) { + onLineRead( buf.substr( lastNL, nextNL - lastNL ) ); + lastNL = nextNL; + nextNL = buf.find_first_of( '\n', nextNL + 1 ); + } +} + +std::vector Git::fetchSubModules( const std::string& projectDir ) { + std::vector submodules; + std::string buf; + FileSystem::fileGet( ( !projectDir.empty() ? projectDir : mProjectPath ) + ".gitmodules", buf ); + LuaPattern pattern( "^%s*path%s*=%s*(.+)" ); + readAllLines( buf, [&pattern, &submodules]( const std::string_view& line ) { + LuaPattern::Range matches[2]; + if ( pattern.matches( line.data(), 0, matches, line.size() ) ) { + submodules.emplace_back( + line.substr( matches[1].start, matches[1].end - matches[1].start ) ); + } + } ); + return submodules; +} + +std::vector Git::getSubModules( const std::string& projectDir ) { + if ( !mSubModulesUpdated ) { + mSubModules = fetchSubModules( projectDir ); + mSubModulesUpdated = true; + } + return mSubModules; +} + bool Git::hasSubmodules( const std::string& projectDir ) { return ( !projectDir.empty() && FileSystem::fileExists( projectDir + ".gitmodules" ) ) || ( !mProjectPath.empty() && FileSystem::fileExists( mProjectPath + ".gitmodules" ) ); } +std::string Git::inSubModule( const std::string& file, const std::string& projectDir ) { + for ( const auto& subRepo : mSubModules ) { + if ( String::startsWith( file, subRepo ) ) + return subRepo; + } + return FileSystem::fileNameFromPath( !projectDir.empty() ? projectDir : mProjectPath ); +} + Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) { static constexpr auto DIFF_CMD = "diff --numstat"; static constexpr auto STATUS_CMD = "-c color.status=never status -b -u -s"; @@ -217,11 +263,11 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) std::string buf; if ( EXIT_SUCCESS != git( DIFF_CMD, projectDir, buf ) ) return s; - auto parseNumStat = [&s, &buf]() { + auto parseNumStat = [&s, &buf, &projectDir, this]() { auto lastNL = 0; auto nextNL = buf.find_first_of( '\n' ); + LuaPattern pattern( "(%d+)%s+(%d+)%s+(.+)" ); while ( nextNL != std::string_view::npos ) { - LuaPattern pattern( "(%d+)%s+(%d+)%s+(.+)" ); LuaPattern::Range matches[4]; if ( pattern.matches( buf.c_str(), lastNL, matches, nextNL ) ) { auto inserted = buf.substr( matches[1].start, matches[1].end - matches[1].start ); @@ -231,13 +277,22 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) int deletes; if ( String::fromString( inserts, inserted ) && String::fromString( deletes, deleted ) ) { - auto fileIt = s.files.find( file ); - if ( fileIt != s.files.end() ) { - fileIt->second.file = std::move( file ); - fileIt->second.inserts = inserts; - fileIt->second.deletes = deletes; + auto repo = inSubModule( file, projectDir ); + auto repoIt = s.files.find( repo ); + if ( repoIt != s.files.end() ) { + bool found = false; + for ( auto& fileIt : repoIt->second ) { + if ( fileIt.file == file ) { + fileIt.inserts = inserts; + fileIt.deletes = deletes; + found = true; + break; + } + } + if ( !found ) + s.files[repo].push_back( { std::move( file ), inserts, deletes } ); } else { - s.files.insert( { file, { file, inserts, deletes } } ); + s.files.insert( { repo, { { std::move( file ), inserts, deletes } } } ); } s.totalInserts += inserts; s.totalDeletions += deletes; @@ -257,12 +312,14 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) parseNumStat(); } + getSubModules( projectDir ); + bool modifiedSubmodule = false; - auto parseStatus = [&s, &buf, &modifiedSubmodule]() { + auto parseStatus = [&s, &buf, &modifiedSubmodule, &projectDir, this]() { auto lastNL = 0; auto nextNL = buf.find_first_of( '\n' ); + LuaPattern pattern( "\n([%sA?][MARTUD?%s])%s(.*)" ); while ( nextNL != std::string_view::npos ) { - LuaPattern pattern( "\n([%sA?][MARTUD?%s])%s(.*)" ); LuaPattern::Range matches[3]; if ( pattern.matches( buf.c_str(), lastNL, matches, nextNL ) ) { auto status = buf.substr( matches[1].start, matches[1].end - matches[1].start ); @@ -290,11 +347,22 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) if ( rstatus == FileStatus::ModifiedSubmodule ) modifiedSubmodule = true; else { - auto fileIt = s.files.find( file ); - if ( fileIt != s.files.end() ) - fileIt->second.status = rstatus; - else - s.files.insert( { file, { file, 0, 0, rstatus } } ); + auto repo = inSubModule( file, projectDir ); + auto repoIt = s.files.find( repo ); + if ( repoIt != s.files.end() ) { + bool found = false; + for ( auto& fileIt : repoIt->second ) { + if ( fileIt.file == file ) { + fileIt.status = rstatus; + found = true; + break; + } + } + if ( !found ) + s.files[repo].push_back( { std::move( file ), 0, 0, rstatus } ); + } else { + s.files.insert( { file, { { file, 0, 0, rstatus } } } ); + } } } } @@ -311,12 +379,14 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) parseStatus(); } - for ( auto& [_, val] : s.files ) { - if ( val.status == FileStatus::Added && val.inserts == 0 ) { - std::string fileText; - FileSystem::fileGet( ( projectDir.empty() ? mProjectPath : projectDir ) + val.file, - fileText ); - val.inserts = countLines( fileText ); + for ( auto& [_, repo] : s.files ) { + for ( auto& val : repo ) { + if ( val.status == FileStatus::Added && val.inserts == 0 ) { + std::string fileText; + FileSystem::fileGet( ( projectDir.empty() ? mProjectPath : projectDir ) + val.file, + fileText ); + val.inserts = countLines( fileText ); + } } } diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp index 8dc178a90..34bd0fdac 100644 --- a/src/tools/ecode/plugins/git/git.hpp +++ b/src/tools/ecode/plugins/git/git.hpp @@ -46,7 +46,8 @@ class Git { } }; - using FilesStatus = std::map; + using RepositoryName = std::string; + using FilesStatus = std::map>; struct Status { int totalInserts{ 0 }; @@ -147,12 +148,21 @@ class Git { std::vector getAllBranchesAndTags( RefType ref = RefType::All, const std::string& projectDir = "" ); + std::vector fetchSubModules( const std::string& projectDir ); + + std::vector getSubModules( const std::string& projectDir = "" ); + protected: std::string mGitPath; std::string mProjectPath; std::string mGitFolder; + std::string mLastProjectPath; + std::vector mSubModules; + bool mSubModulesUpdated{ false }; bool hasSubmodules( const std::string& projectDir ); + + std::string inSubModule( const std::string& file, const std::string& projectDir ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp index ee46f789c..626bd9e91 100644 --- a/src/tools/ecode/plugins/git/gitplugin.hpp +++ b/src/tools/ecode/plugins/git/gitplugin.hpp @@ -103,17 +103,65 @@ class GitPlugin : public PluginBase { class GitBranchModel : public Model { public: + static std::shared_ptr asModel( std::vector&& branches ) { + return std::make_shared( std::move( branches ) ); + } + enum Column { Name, Remote, Type, LastCommit }; - GitBranchModel( std::vector&& branches ) : mBranches( std::move( branches ) ) {} + struct BranchData { + std::string branch; + std::vector data; + }; - size_t rowCount( const ModelIndex& ) const { return mBranches.size(); } + GitBranchModel( std::vector&& branches ) { + std::map> branchTypes; + for ( auto& branch : branches ) { + auto& type = branchTypes[Git::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(); + return mBranches[index.row()].data.size(); + } 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 {}; + } + Variant data( const ModelIndex& index, ModelRole role ) const { - if ( role == ModelRole::Display && index.row() < (Int64)mBranches.size() ) { - const Git::Branch& branch = mBranches[index.row()]; + static const char* EMPTY = ""; + if ( role == ModelRole::Display ) { + if ( index.internalId() == -1 ) { + if ( index.column() == Column::Name ) + return mBranches[index.row()].branch.c_str(); + return EMPTY; + } + const Git::Branch& branch = mBranches[index.internalId()].data[index.row()]; switch ( index.column() ) { case Column::Name: return branch.name.c_str(); @@ -125,30 +173,69 @@ class GitBranchModel : public Model { return branch.lastCommit.c_str(); } } - return {}; + return EMPTY; } protected: - std::vector mBranches; + std::vector mBranches; }; class GitStatusModel : public Model { public: + static std::shared_ptr asModel( Git::FilesStatus&& status ) { + return std::make_shared( std::move( status ) ); + } + + struct RepoStatus { + std::string repo; + std::vector files; + }; + enum Column { File, Inserted, Removed, FileStatus }; GitStatusModel( Git::FilesStatus&& status ) { mStatus.reserve( status.size() ); for ( auto& s : status ) - mStatus.emplace_back( std::move( s.second ) ); + mStatus.emplace_back( RepoStatus{ std::move( s.first ), std::move( s.second ) } ); } - size_t rowCount( const ModelIndex& ) const { return mStatus.size(); } + size_t treeColumn() const { return Column::File; } + + size_t rowCount( const ModelIndex& index ) const { + if ( !index.isValid() ) + return mStatus.size(); + return mStatus[index.row()].files.size(); + } 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(), &mStatus[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, &mStatus[row], -1 ); + if ( parent.internalData() ) + return createIndex( row, column, &mStatus[parent.row()].files[row], parent.row() ); + return {}; + } + Variant data( const ModelIndex& index, ModelRole role ) const { - if ( role == ModelRole::Display && index.row() < (Int64)mStatus.size() ) { - const Git::DiffFile& s = mStatus[index.row()]; + static const char* EMPTY = ""; + static const char* SUCCESS = "theme-success"; + static const char* ERROR = "theme-error"; + if ( role == ModelRole::Display ) { + if ( index.internalId() == -1 ) { + if ( index.column() == Column::File ) + return mStatus[index.row()].repo.c_str(); + return EMPTY; + } + const Git::DiffFile& s = mStatus[index.internalId()].files[index.row()]; switch ( index.column() ) { case Column::File: return s.file.c_str(); @@ -159,12 +246,23 @@ class GitStatusModel : public Model { case Column::FileStatus: return Variant( std::string( static_cast( s.status ), 1 ) ); } + } else if ( role == ModelRole::Class ) { + switch ( index.column() ) { + case Column::Inserted: + return SUCCESS; + case Column::Removed: + return ERROR; + default: + break; + } } - return {}; + return EMPTY; } + virtual bool classModelRoleEnabled() { return true; } + protected: - std::vector mStatus; + std::vector mStatus; }; } // namespace ecode