diff --git a/src/tools/ecode/notificationcenter.cpp b/src/tools/ecode/notificationcenter.cpp index fb3d9f719..32623ed88 100644 --- a/src/tools/ecode/notificationcenter.cpp +++ b/src/tools/ecode/notificationcenter.cpp @@ -41,6 +41,7 @@ void NotificationCenter::addNotification( const String& text, const Time& delay { Actions::FadeIn::New( Seconds( 0.125 ) ), Actions::Delay::New( delay ), Actions::FadeOut::New( Seconds( 0.125 ) ), Actions::Close::New() } ); tv->runAction( sequence ); + Log::info( "Displayed notification:\n%s", text.toUtf8() ); }; if ( Engine::isRunninMainThread() ) diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp index 83b085936..eae60dd35 100644 --- a/src/tools/ecode/plugins/git/git.cpp +++ b/src/tools/ecode/plugins/git/git.cpp @@ -85,11 +85,24 @@ bool Git::isGitRepo( const std::string& projectDir ) { std::string Git::branch( const std::string& projectDir ) { std::string buf; + + if ( EXIT_SUCCESS == git( "symbolic-ref --short HEAD", projectDir, buf ) ) + return String::rTrim( buf, '\n' ); + if ( EXIT_SUCCESS == git( "rev-parse --abbrev-ref HEAD", projectDir, buf ) ) return String::rTrim( buf, '\n' ); + return "HEAD"; } +std::unordered_map +Git::branches( const std::vector& repos ) { + std::unordered_map ret; + for ( const auto& repo : repos ) + ret[repo] = branch( repo ); + return ret; +} + bool Git::setProjectPath( const std::string& projectPath ) { auto lastProjectPath = mProjectPath; mProjectPath = ""; @@ -495,6 +508,8 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) auto filePath = subModulePath + file; auto repo = repoName( filePath, projectDir ); auto repoIt = s.files.find( repo ); + GitStatusReport status = { GitStatus::NotSet, GitStatusType::Untracked, + GitStatusChar::Untracked }; if ( repoIt != s.files.end() ) { bool found = false; for ( auto& fileIt : repoIt->second ) { @@ -506,15 +521,12 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) } } if ( !found ) { - s.files[repo].push_back( { std::move( filePath ), inserts, deletes, - GitStatus::NotSet, GitStatusType::Untracked, - GitStatusChar::Untracked } ); + s.files[repo].push_back( + { std::move( filePath ), inserts, deletes, status } ); } } else { s.files.insert( - { repo, - { { std::move( filePath ), inserts, deletes, GitStatus::NotSet, - GitStatusType::Untracked, GitStatusChar::Untracked } } } ); + { repo, { { std::move( filePath ), inserts, deletes, status } } } ); } s.totalInserts += inserts; s.totalDeletions += deletes; @@ -559,10 +571,10 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) auto status = statusFromShortStatusStr( statusStr ); - if ( status.gitStatus != GitStatus::NotSet ) + if ( status.status == GitStatus::NotSet ) return; - if ( status.gitStatusChar == GitStatusChar::ModifiedSubmodule ) { + if ( status.symbol == GitStatusChar::ModifiedSubmodule ) { modifiedSubmodule = true; return; } @@ -574,21 +586,15 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) bool found = false; for ( auto& fileIt : repoIt->second ) { if ( fileIt.file == filePath ) { - fileIt.statusChar = status.gitStatusChar; - fileIt.status = status.gitStatus; - fileIt.statusType = status.gitStatusType; + fileIt.report = status; found = true; break; } } - if ( !found ) { - s.files[repo].push_back( { std::move( filePath ), 0, 0, status.gitStatus, - status.gitStatusType, status.gitStatusChar } ); - } + if ( !found ) + s.files[repo].push_back( { std::move( filePath ), 0, 0, status } ); } else { - s.files.insert( { repo, - { { std::move( filePath ), 0, 0, status.gitStatus, - status.gitStatusType, status.gitStatusChar } } } ); + s.files.insert( { repo, { { std::move( filePath ), 0, 0, status } } } ); } } } ); @@ -604,7 +610,7 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) for ( auto& [_, repo] : s.files ) { for ( auto& val : repo ) { - if ( val.statusChar == GitStatusChar::Added && val.inserts == 0 ) { + if ( val.report.symbol == GitStatusChar::Added && val.inserts == 0 ) { std::string fileText; FileSystem::fileGet( ( projectDir.empty() ? mProjectPath : projectDir ) + val.file, fileText ); @@ -813,7 +819,7 @@ Git::GitStatusReport Git::statusFromShortStatusStr( const std::string_view& stat } } - return { gitStatus, gitStatusChar, gitStatusType }; + return { gitStatus, gitStatusType, gitStatusChar }; } } // namespace ecode diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp index 7690f2b22..393d8b680 100644 --- a/src/tools/ecode/plugins/git/git.hpp +++ b/src/tools/ecode/plugins/git/git.hpp @@ -99,18 +99,25 @@ class Git { SMM = git_xy( 'm', ANY ), // staged modified submodule }; + struct GitStatusReport { + GitStatus status = GitStatus::NotSet; + GitStatusType type = GitStatusType::Untracked; + GitStatusChar symbol = GitStatusChar::Unknown; + + bool operator==( const GitStatusReport& other ) const { + return status == other.status && symbol == other.symbol && type == other.type; + } + }; + struct DiffFile { std::string file; int inserts{ 0 }; int deletes{ 0 }; - GitStatus status; - GitStatusType statusType; - GitStatusChar statusChar{ GitStatusChar::Unknown }; + GitStatusReport report; bool operator==( const DiffFile& other ) const { return file == other.file && inserts == other.inserts && deletes == other.deletes && - status == other.status && statusType == other.statusType && - statusChar == other.statusChar; + report == other.report; } }; @@ -199,6 +206,8 @@ class Git { std::string branch( const std::string& projectDir = "" ); + std::unordered_map branches( const std::vector& repos ); + Status status( bool recurseSubmodules, const std::string& projectDir = "" ); Result add( std::vector files, const std::string& projectDir = "" ); @@ -279,19 +288,14 @@ class Git { Result gitSimple( const std::string& cmd, const std::string& projectDir ); + GitStatusReport statusFromShortStatusStr( const std::string_view& statusStr ); + protected: std::string mGitPath; std::string mProjectPath; std::string mGitFolder; std::vector mSubModules; bool mSubModulesUpdated{ false }; - - struct GitStatusReport { - GitStatus gitStatus = GitStatus::NotSet; - GitStatusChar gitStatusChar = GitStatusChar::Unknown; - GitStatusType gitStatusType = GitStatusType::Untracked; - }; - GitStatusReport statusFromShortStatusStr( const std::string_view& statusStr ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index 3515dbe35..02b95d285 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -50,6 +50,13 @@ std::string GitPlugin::statusTypeToString( Git::GitStatusType type ) { return ""; } +std::vector GitPlugin::repos() { + std::vector ret; + for ( const auto& repo : mRepos ) + ret.push_back( repo.first ); + return ret; +} + static size_t hashBranches( const std::vector& branches ) { size_t hash = 0; for ( const auto& branch : branches ) @@ -242,7 +249,7 @@ class GitStatusModel : public Model { for ( auto& s : status ) for ( auto& f : s.second ) - typesFound[s.first].insert( f.statusType ); + typesFound[s.first].insert( f.report.type ); for ( const auto& tf : typesFound ) { RepoStatus rs; @@ -262,7 +269,7 @@ class GitStatusModel : public Model { for ( auto& s : status ) { for ( auto& fv : s.second ) { auto pos = repoPos[s.first]; - auto typePos = repoTypePos[pos][fv.statusType]; + 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 ) ); } @@ -376,7 +383,7 @@ class GitStatusModel : public Model { case Column::Removed: return Variant( String::format( "-%d ", s.deletes ) ); case Column::State: - return Variant( String::format( "%c", s.statusChar ) ); + return Variant( String::format( "%c", s.report.symbol ) ); case Column::RelativeDirectory: return Variant( FileSystem::fileRemoveFileName( s.file ) ); } @@ -704,10 +711,10 @@ void GitPlugin::updateStatus( bool force ) { return; if ( !mGit->getGitFolder().empty() ) { - auto prevBranch = mGitBranch; + auto prevBranch = mGitBranches; { Lock l( mGitBranchMutex ); - mGitBranch = mGit->branch(); + mGitBranches = mGit->branches( repos() ); } Git::Status prevGitStatus; { @@ -718,7 +725,7 @@ void GitPlugin::updateStatus( bool force ) { { Lock l( mGitStatusMutex ); mGitStatus = std::move( newGitStatus ); - if ( !force && mGitBranch == prevBranch && mGitStatus == prevGitStatus ) + if ( !force && mGitBranches == prevBranch && mGitStatus == prevGitStatus ) return; } } else if ( !mStatusButton ) { @@ -863,8 +870,9 @@ bool GitPlugin::onMouseLeave( UICodeEditor* editor, const Vector2i&, const Uint3 } std::string GitPlugin::gitBranch() { + std::string repoSel = repoSelected(); Lock l( mGitBranchMutex ); - return mGitBranch; + return mGitBranches[repoSel]; } void GitPlugin::onRegisterListeners( UICodeEditor* editor, std::vector& listeners ) { @@ -917,12 +925,13 @@ void GitPlugin::checkout( Git::Branch branch ) { mLoader->setVisible( true ); mThreadPool->run( [this, branch, createLocal] { auto result = createLocal - ? mGit->checkoutAndCreateLocalBranch( branch.name, mRepoSelected ) - : mGit->checkout( branch.name, mRepoSelected ); + ? mGit->checkoutAndCreateLocalBranch( branch.name, repoSelected() ) + : mGit->checkout( branch.name, repoSelected() ); if ( result.success() ) { { + std::string repoSel = repoSelected(); Lock l( mGitBranchMutex ); - mGitBranch = branch.name; + mGitBranches[repoSel] = branch.name; } if ( mBranchesTree->getModel() ) { if ( createLocal ) @@ -963,9 +972,11 @@ void GitPlugin::branchRename( Git::Branch branch ) { if ( newName.empty() || branch.name == newName ) return; msgBox->closeWindow(); - runAsync( [this, branch, - newName]() { return mGit->renameBranch( branch.name, newName, mRepoSelected ); }, - false, true ); + runAsync( + [this, branch, newName]() { + return mGit->renameBranch( branch.name, newName, repoSelected() ); + }, + false, true ); } ); msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } ); msgBox->setTitle( i18n( "git_rename_branch", "Rename Branch" ) ); @@ -983,7 +994,7 @@ void GitPlugin::branchDelete( Git::Branch branch ) { branch.name ) ); msgBox->on( Event::OnConfirm, [this, branch]( auto ) { - runAsync( [this, branch]() { return mGit->deleteBranch( branch.name, mRepoSelected ); }, + runAsync( [this, branch]() { return mGit->deleteBranch( branch.name, repoSelected() ); }, false, true ); } ); msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } ); @@ -993,7 +1004,7 @@ void GitPlugin::branchDelete( Git::Branch branch ) { } void GitPlugin::pull() { - runAsync( [this]() { return mGit->pull( mRepoSelected ); }, true, true ); + runAsync( [this]() { return mGit->pull( repoSelected() ); }, true, true ); } void GitPlugin::push() { @@ -1003,7 +1014,7 @@ void GitPlugin::push() { "Are you sure you want to push the local changes to the remote server?" ) ); msgBox->on( Event::OnConfirm, [this]( auto ) { - runAsync( [this]() { return mGit->push( mRepoSelected ); }, true, true ); + runAsync( [this]() { return mGit->push( repoSelected() ); }, true, true, true ); } ); msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } ); msgBox->setTitle( i18n( "git_confirm", "Confirm" ) ); @@ -1012,7 +1023,7 @@ void GitPlugin::push() { } void GitPlugin::fetch() { - runAsync( [this]() { return mGit->fetch( mRepoSelected ); }, true, true ); + runAsync( [this]() { return mGit->fetch( repoSelected() ); }, true, true ); } void GitPlugin::branchCreate() { @@ -1025,7 +1036,7 @@ void GitPlugin::branchCreate() { if ( newName.empty() ) return; msgBox->closeWindow(); - runAsync( [this, newName]() { return mGit->createBranch( newName, true, mRepoSelected ); }, + runAsync( [this, newName]() { return mGit->createBranch( newName, true, repoSelected() ); }, false, true ); } ); msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } ); @@ -1053,15 +1064,15 @@ void GitPlugin::commit() { void GitPlugin::fastForwardMerge( Git::Branch branch ) { runAsync( [this, branch]() { - if ( branch.name == mGitBranch ) - return mGit->fastForwardMerge( mRepoSelected ); + if ( branch.name == gitBranch() ) + return mGit->fastForwardMerge( repoSelected() ); auto remoteBranch = mGit->getAllBranchesAndTags( - Git::RefType::Remote, "refs/remotes/" + branch.remote, mRepoSelected ); + Git::RefType::Remote, "refs/remotes/" + branch.remote, repoSelected() ); if ( remoteBranch.empty() ) return Git::Result{ "", -1 }; - return mGit->updateRef( branch.name, remoteBranch[0].lastCommit, mRepoSelected ); + return mGit->updateRef( branch.name, remoteBranch[0].lastCommit, repoSelected() ); }, false, true ); } @@ -1186,21 +1197,21 @@ void GitPlugin::updateBranches( bool force ) { if ( !mGit || mGit->getGitFolder().empty() ) return; - auto prevBranch = mGitBranch; + auto prevBranch = mGitBranches; { Lock l( mGitBranchMutex ); - mGitBranch = mGit->branch(); + mGitBranches = mGit->branches( repos() ); } mGit->getSubModules(); - auto branches = mGit->getAllBranchesAndTags( Git::RefType::All, {}, mRepoSelected ); + auto branches = mGit->getAllBranchesAndTags( Git::RefType::All, {}, repoSelected() ); auto hash = hashBranches( branches ); auto model = GitBranchModel::asModel( std::move( branches ), hash, this ); if ( mBranchesTree && static_cast( mBranchesTree->getModel() )->getHash() == hash ) { - if ( prevBranch != mGitBranch ) + if ( prevBranch != mGitBranches ) mBranchesTree->getModel()->invalidate( Model::DontInvalidateIndexes ); return; } @@ -1222,18 +1233,12 @@ void GitPlugin::updateBranchesUI( std::shared_ptr model ) { auto subModules = mGit->getSubModules(); std::sort( subModules.begin(), subModules.end() ); - size_t selectedIdx = 0; - std::vector> repos; + std::unordered_map repos; repos.clear(); - repos.reserve( subModules.size() + 1 ); - repos.emplace_back( FileSystem::fileNameFromPath( mProjectPath ), mProjectPath ); - size_t i = 1; + repos.insert( { mProjectPath, FileSystem::fileNameFromPath( mProjectPath ) } ); for ( auto& subModule : subModules ) { std::string subModulePath = mProjectPath + subModule; - if ( subModulePath == mRepoSelected ) - selectedIdx = i; - repos.emplace_back( FileSystem::fileNameFromPath( subModule ), std::move( subModulePath ) ); - i++; + repos.insert( { std::move( subModulePath ), FileSystem::fileNameFromPath( subModule ) } ); } if ( repos == mRepos ) @@ -1242,11 +1247,11 @@ void GitPlugin::updateBranchesUI( std::shared_ptr model ) { mRepos = std::move( repos ); std::vector items; for ( const auto& repo : mRepos ) - items.push_back( repo.first ); + items.push_back( repo.second ); mRepoDropDown->getListBox()->clear(); mRepoDropDown->getListBox()->addListBoxItems( items ); - mRepoDropDown->getListBox()->setSelected( selectedIdx ); + mRepoDropDown->getListBox()->setSelected( mRepos[repoSelected()] ); } void GitPlugin::buildSidePanelTab() { @@ -1422,10 +1427,10 @@ void GitPlugin::buildSidePanelTab() { const auto& txt = mRepoDropDown->getListBox()->getItemSelectedText(); for ( const auto& repo : mRepos ) { - if ( txt == repo.first ) { + if ( txt == repo.second ) { { Lock l( mRepoMutex ); - mRepoSelected = repo.second; + mRepoSelected = repo.first; } updateBranches( true ); break; @@ -1440,7 +1445,7 @@ void GitPlugin::openBranchMenu( const Git::Branch& branch ) { addMenuItem( menu, "git-fetch", "Fetch" ); - if ( mGitBranch != branch.name ) { + if ( gitBranch() != branch.name ) { addMenuItem( menu, "git-checkout", "Check Out..." ); if ( branch.type == Git::RefType::Head ) { addMenuItem( menu, "git-branch-rename", "Rename" ); @@ -1492,7 +1497,7 @@ void GitPlugin::openFileStatusMenu( const Git::DiffFile& file ) { addMenuItem( menu, "git-open-file", "Open File" ); - if ( file.statusType != Git::GitStatusType::Staged ) { + if ( file.report.type != Git::GitStatusType::Staged ) { addMenuItem( menu, "git-stage", "Stage" ); } else { addMenuItem( menu, "git-unstage", "Unstage" ); @@ -1521,15 +1526,15 @@ void GitPlugin::openFileStatusMenu( const Git::DiffFile& file ) { menu->showOverMouseCursor(); } -void GitPlugin::runAsync( std::function fn, bool _updateStatus, - bool _updateBranches ) { +void GitPlugin::runAsync( std::function fn, bool _updateStatus, bool _updateBranches, + bool displaySuccessMsg ) { if ( !mGit ) return; mLoader->setVisible( true ); - mThreadPool->run( [this, fn, _updateStatus, _updateBranches] { + mThreadPool->run( [this, fn, _updateStatus, _updateBranches, displaySuccessMsg] { auto res = fn(); getUISceneNode()->runOnMainThread( [this] { mLoader->setVisible( false ); } ); - if ( res.fail() ) { + if ( res.fail() || displaySuccessMsg ) { showMessage( LSPMessageType::Warning, res.result ); return; } @@ -1545,6 +1550,11 @@ void GitPlugin::addMenuItem( UIMenu* menu, const std::string& txtKey, const std: menu->add( i18n( txtKey, txtVal ), iconDrawable( icon, 12 ), KeyBindings::keybindFormat( mKeyBindings[txtKey] ) ) ->setId( txtKey ); -}; +} + +std::string GitPlugin::repoSelected() { + Lock l( mRepoMutex ); + return mRepoSelected; +} } // namespace ecode diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp index 8aaae5e2f..7503abb6e 100644 --- a/src/tools/ecode/plugins/git/gitplugin.hpp +++ b/src/tools/ecode/plugins/git/gitplugin.hpp @@ -59,11 +59,13 @@ class GitPlugin : public PluginBase { std::string statusTypeToString( Git::GitStatusType type ); + std::vector repos(); + protected: std::unique_ptr mGit; - std::string mGitBranch; + std::unordered_map mGitBranches; Git::Status mGitStatus; - std::vector> mRepos; + std::unordered_map mRepos; std::string mProjectPath; std::string mRepoSelected; @@ -170,10 +172,13 @@ class GitPlugin : public PluginBase { void openFileStatusMenu( const Git::DiffFile& file ); - void runAsync( std::function fn, bool updateStatus, bool updateBranches ); + void runAsync( std::function fn, bool updateStatus, bool updateBranches, + bool displaySuccessMsg = false ); void addMenuItem( UIMenu* menu, const std::string& txtKey, const std::string& txtVal, const std::string& icon = "" ); + + std::string repoSelected(); }; } // namespace ecode