diff --git a/bin/assets/fonts/codicon.ttf b/bin/assets/fonts/codicon.ttf index 3d1dc3d86..4894dfa31 100644 Binary files a/bin/assets/fonts/codicon.ttf and b/bin/assets/fonts/codicon.ttf differ diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css index 9b863c0ac..ae874668f 100644 --- a/bin/assets/ui/breeze.css +++ b/bin/assets/ui/breeze.css @@ -190,6 +190,10 @@ selectbutton:disabled { border-color: var(--disabled-border); } +checkbox:disabled { + color: var(--disabled-color); +} + PushButton::icon, SelectButton::icon, TableView::cell::icon, diff --git a/include/eepp/system/condition.hpp b/include/eepp/system/condition.hpp index 552fe72f5..261a50da4 100644 --- a/include/eepp/system/condition.hpp +++ b/include/eepp/system/condition.hpp @@ -20,7 +20,7 @@ class EE_API Condition : NonCopyable { }; /** Initializes a Condition object and sets its internal value to value. - * Thus using WaitAndLock(value, ...) will immediately return. + * Thus using waitAndLock(value, ...) will immediately return. */ Condition( int value = 0 ); @@ -66,7 +66,7 @@ class EE_API Condition : NonCopyable { void unlock(); - /** Performs an assignement followed by a Signal() call. + /** Performs an assignement followed by a signal() call. * The internal Condition value is updated to value() and the Condition is * signaled. Note that the Condition must be unlocked in order * to be updated, otherwise it'll block until the Condition @@ -92,13 +92,13 @@ class EE_API Condition : NonCopyable { void signal(); /** Signals the Condition and disables blocking calls, - * thus WaitAndLock() does no more wait whatever + * thus waitAndLock() does no more wait whatever * the awaitedValue is and waiting calls are unlocked, returning false. */ void invalidate(); /** Restores the blocking capabilities of the Condition, - * possibly previously disabled with Invalidate() + * possibly previously disabled with invalidate() */ void restore(); diff --git a/src/thirdparty/subprocess/subprocess.h b/src/thirdparty/subprocess/subprocess.h index 90138ddcc..497bf040e 100644 --- a/src/thirdparty/subprocess/subprocess.h +++ b/src/thirdparty/subprocess/subprocess.h @@ -1071,7 +1071,7 @@ int subprocess_join(struct subprocess_s *const process, return 0; #else - int status; + int status = 0; if (process->stdin_file) { fclose(process->stdin_file); diff --git a/src/tools/ecode/iconmanager.cpp b/src/tools/ecode/iconmanager.cpp index 2ec6df77b..c3f013cc9 100644 --- a/src/tools/ecode/iconmanager.cpp +++ b/src/tools/ecode/iconmanager.cpp @@ -207,6 +207,10 @@ void IconManager::init( UISceneNode* sceneNode, FontTrueType* iconFont, FontTrue { "repo-pull", 0xeb40 }, { "repo-push", 0xeb41 }, { "repo-forked", 0xea63 }, + { "repo-fetch", 0xec1d }, + { "git-fetch", 0xf101 }, + { "git-commit", 0xeafc }, + { "remove", 0xeb3b }, { "tag", 0xea66 } }; for ( const auto& icon : codIcons ) diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.cpp b/src/tools/ecode/plugins/formatter/formatterplugin.cpp index 781ec8557..8fa147303 100644 --- a/src/tools/ecode/plugins/formatter/formatterplugin.cpp +++ b/src/tools/ecode/plugins/formatter/formatterplugin.cpp @@ -53,7 +53,7 @@ FormatterPlugin::~FormatterPlugin() { mWorkerCondition.wait( lock, [this]() { return mWorkersCount <= 0; } ); } - for ( auto editor : mEditors ) { + for ( auto& editor : mEditors ) { for ( auto& kb : mKeyBindings ) { editor.first->getKeyBindings().removeCommandKeybind( kb.first ); if ( editor.first->hasDocument() ) diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp index f5698238a..aa6a3ac04 100644 --- a/src/tools/ecode/plugins/git/git.cpp +++ b/src/tools/ecode/plugins/git/git.cpp @@ -1,4 +1,5 @@ #include "git.hpp" +#include #include #include #include @@ -58,6 +59,7 @@ Git::Git( const std::string& projectDir, const std::string& gitPath ) : mGitPath } int Git::git( const std::string& args, const std::string& projectDir, std::string& buf ) const { + Clock clock; buf.clear(); Process p; p.create( mGitPath, args, @@ -65,9 +67,10 @@ int Git::git( const std::string& args, const std::string& projectDir, std::strin Process::Options::InheritEnvironment, { { "LC_ALL", "en_US.UTF-8" } }, projectDir.empty() ? mProjectPath : projectDir ); p.readAllStdOut( buf ); - int retCode; + int retCode = 0; p.join( &retCode ); - Log::info( "GitPlugin run: %s %s", mGitPath, args ); + Log::info( "GitPlugin cmd in %s (%d): %s %s", clock.getElapsedTime().toString(), retCode, + mGitPath, args ); return retCode; } @@ -230,7 +233,8 @@ Git::Result Git::deleteBranch( const std::string& branch, const std::string& pro return gitSimple( String::format( "branch -D %s", branch ), projectDir ); } -Git::Result Git::commit( const std::string& commitMsg, const std::string& projectDir ) { +Git::Result Git::commit( const std::string& commitMsg, bool ammend, + const std::string& projectDir ) { auto tmpPath = Sys::getTempPath() + ".ecode-git-commit-" + String::randString( 16 ); if ( !FileSystem::fileWrite( tmpPath, commitMsg ) ) { Git::Result res; @@ -239,9 +243,9 @@ Git::Result Git::commit( const std::string& commitMsg, const std::string& projec return res; } std::string buf; - int retCode = - git( String::format( "commit --cleanup=whitespace --allow-empty --file=%s", tmpPath ), - projectDir, buf ); + int retCode = git( String::format( "commit %s --cleanup=whitespace --allow-empty --file=%s", + ammend ? "--ammend" : "", tmpPath ), + projectDir, buf ); FileSystem::fileRemove( tmpPath ); Git::Result res; res.returnCode = retCode; diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp index 63f7358dd..10d0e6e12 100644 --- a/src/tools/ecode/plugins/git/git.hpp +++ b/src/tools/ecode/plugins/git/git.hpp @@ -228,7 +228,7 @@ class Git { Result deleteBranch( const std::string& branch, const std::string& projectDir = "" ); - Result commit( const std::string& commitMsg, const std::string& projectDir = "" ); + Result commit( const std::string& commitMsg, bool ammend, const std::string& projectDir = "" ); Result fetch( const std::string& projectDir = "" ); diff --git a/src/tools/ecode/plugins/git/gitbranchmodel.cpp b/src/tools/ecode/plugins/git/gitbranchmodel.cpp index 79c3f647f..8d57f5cf4 100644 --- a/src/tools/ecode/plugins/git/gitbranchmodel.cpp +++ b/src/tools/ecode/plugins/git/gitbranchmodel.cpp @@ -133,4 +133,14 @@ Git::Branch GitBranchModel::branch( const ModelIndex& index ) const { return *static_cast( index.internalData() ); } +Git::Branch GitBranchModel::branch( const std::string& name ) const { + for ( const auto& type : mBranches ) { + for ( const auto& branch : type.data ) { + if ( branch.name == name ) + return branch; + } + } + return {}; +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/git/gitbranchmodel.hpp b/src/tools/ecode/plugins/git/gitbranchmodel.hpp index 26dbbbec3..328a24a2b 100644 --- a/src/tools/ecode/plugins/git/gitbranchmodel.hpp +++ b/src/tools/ecode/plugins/git/gitbranchmodel.hpp @@ -55,6 +55,8 @@ class GitBranchModel : public Model { Git::Branch branch( const ModelIndex& index ) const; + Git::Branch branch( const std::string& name ) const; + protected: std::vector mBranches; GitPlugin* mPlugin{ nullptr }; diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index 26008ca8c..2e5627cc2 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ std::string GitPlugin::statusTypeToString( Git::GitStatusType type ) { } std::vector GitPlugin::repos() { + Lock l( mReposMutex ); std::vector ret; for ( const auto& repo : mRepos ) ret.push_back( repo.first ); @@ -296,13 +298,7 @@ void GitPlugin::updateStatus( bool force ) { if ( !mGit || mGit->getGitFolder().empty() ) return; - decltype( mGitBranches ) prevBranch; - { - auto branches = mGit->branches( repos() ); - Lock l( mGitBranchMutex ); - prevBranch = mGitBranches; - mGitBranches = std::move( branches ); - } + auto prevBranch = updateReposBranches(); Git::Status prevGitStatus; { Lock l( mGitStatusMutex ); @@ -629,16 +625,48 @@ void GitPlugin::branchCreate() { msgBox->showWhenReady(); } -void GitPlugin::commit() { +void GitPlugin::commit( const std::string& repoPath ) { UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::TEXT_EDIT, i18n( "git_commit_message", "Commit Message:" ) ); - msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + + UICheckBox* chkBox = UICheckBox::New(); + chkBox->setLayoutMargin( Rectf( 0, 8, 0, 0 ) ) + ->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent ) + ->setLayoutGravity( UI_HALIGN_LEFT | UI_VALIGN_CENTER ) + ->setClipType( ClipType::None ) + ->setParent( msgBox->getLayoutCont()->getFirstChild() ) + ->setId( "git-ammend" ); + chkBox->setText( i18n( "git_ammend", "Ammend last commit" ) ); + chkBox->toPosition( 2 ); + + if ( !repoPath.empty() ) { + auto branchName = mGitBranches[repoPath]; + if ( !branchName.empty() ) { + if ( repoPath != repoSelected() || mBranchesTree->getModel() == nullptr ) { + + auto branch = mGit->getAllBranchesAndTags( Git::RefType::All, + "refs/heads/" + branchName, repoPath ); + if ( !branch.empty() ) + chkBox->setEnabled( branch.front().ahead > 0 ); + + } else { + auto model = static_cast( mBranchesTree->getModel() ); + auto branch = model->branch( branchName ); + if ( !branch.name.empty() ) + chkBox->setEnabled( branch.ahead > 0 ); + } + } + } + + msgBox->on( Event::OnConfirm, [this, msgBox, chkBox]( const Event* ) { std::string msg( msgBox->getTextEdit()->getText().toUtf8() ); if ( msg.empty() ) return; msgBox->closeWindow(); - runAsync( [this, msg]() { return mGit->commit( msg ); }, true, true ); + runAsync( [this, msg, chkBox]() { return mGit->commit( msg, chkBox->isChecked() ); }, true, + true ); } ); + msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } ); msgBox->setTitle( i18n( "git_commit", "Commit" ) ); msgBox->center(); @@ -798,6 +826,32 @@ bool GitPlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event ) { return false; } +std::unordered_map GitPlugin::updateReposBranches() { + mGit->getSubModules(); + + bool reposEmpty = false; + { + Lock l( mReposMutex ); + reposEmpty = mRepos.empty(); + } + if ( reposEmpty ) + updateRepos(); + + Lock l( mGitBranchMutex ); + + decltype( mGitBranches ) prevBranch; + if ( mGitBranches.empty() || mLastBranchesUpdate.getElapsedTime() > Seconds( 1 ) ) { + prevBranch = mGitBranches; + mGitBranches = mGit->branches( repos() ); + mLastBranchesUpdate.restart(); + } else { + Lock l( mGitBranchMutex ); + prevBranch = mGitBranches; + } + + return prevBranch; +} + void GitPlugin::updateBranches( bool force ) { if ( !mGit || !mGitFound || ( mRunningUpdateBranches && !force ) ) return; @@ -808,15 +862,7 @@ void GitPlugin::updateBranches( bool force ) { if ( !mGit || mGit->getGitFolder().empty() ) return; - mGit->getSubModules(); - - decltype( mGitBranches ) prevBranch; - { - Lock l( mGitBranchMutex ); - prevBranch = mGitBranches; - mGitBranches = mGit->branches( repos() ); - } - + auto prevBranch = updateReposBranches(); auto branches = mGit->getAllBranchesAndTags( Git::RefType::All, {}, repoSelected() ); auto hash = GitBranchModel::hashBranches( branches ); auto model = GitBranchModel::asModel( std::move( branches ), hash, this ); @@ -833,12 +879,7 @@ void GitPlugin::updateBranches( bool force ) { [this]( auto ) { mRunningUpdateBranches--; } ); } -void GitPlugin::updateBranchesUI( std::shared_ptr model ) { - buildSidePanelTab(); - mBranchesTree->setModel( model ); - mBranchesTree->setColumnsVisible( { GitBranchModel::Name } ); - mBranchesTree->expandAll(); - +void GitPlugin::updateRepos() { if ( !mGit ) return; @@ -853,10 +894,19 @@ void GitPlugin::updateBranchesUI( std::shared_ptr model ) { repos.insert( { std::move( subModulePath ), FileSystem::fileNameFromPath( subModule ) } ); } + Lock l( mReposMutex ); if ( repos == mRepos ) return; mRepos = std::move( repos ); +} + +void GitPlugin::updateBranchesUI( std::shared_ptr model ) { + buildSidePanelTab(); + mBranchesTree->setModel( model ); + mBranchesTree->setColumnsVisible( { GitBranchModel::Name } ); + mBranchesTree->expandAll(); + updateRepos(); std::vector items; for ( const auto& repo : mRepos ) items.push_back( repo.second ); @@ -1020,11 +1070,15 @@ void GitPlugin::buildSidePanelTab() { if ( status->type == Git::GitStatusType::Staged || status->type == Git::GitStatusType::Untracked || status->type == Git::GitStatusType::Changed ) { + std::string repoPath; + if ( !status->files.empty() ) + repoPath = mGit->repoPath( status->files.front().file ); + UIPopUpMenu* menu = UIPopUpMenu::New(); menu->setId( "git_status_type_menu" ); if ( status->type == Git::GitStatusType::Staged ) { - addMenuItem( menu, "git-commit", "Commit" ); + addMenuItem( menu, "git-commit", "Commit", "git-commit" ); addMenuItem( menu, "git-unstage-all", "Unstage All" ); } @@ -1033,13 +1087,14 @@ void GitPlugin::buildSidePanelTab() { status->type == Git::GitStatusType::Changed ) addMenuItem( menu, "git-stage-all", "Stage All" ); - menu->on( Event::OnItemClicked, [this, model]( const Event* event ) { + menu->on( Event::OnItemClicked, [this, model, + repoPath]( const Event* event ) { if ( !mGit ) return; UIMenuItem* item = event->getNode()->asType(); std::string id( item->getId() ); if ( id == "git-commit" ) { - commit(); + commit( repoPath ); } else if ( id == "git-stage-all" ) { stage( model->getFiles( mGit->repoName( "" ), (Uint32)Git::GitStatusType::Untracked | @@ -1081,10 +1136,10 @@ void GitPlugin::openBranchMenu( const Git::Branch& branch ) { UIPopUpMenu* menu = UIPopUpMenu::New(); menu->setId( "git_branch_menu" ); - addMenuItem( menu, "git-fetch", "Fetch" ); + addMenuItem( menu, "git-fetch", "Fetch", "repo-fetch" ); if ( gitBranch() != branch.name ) { - addMenuItem( menu, "git-checkout", "Check Out..." ); + addMenuItem( menu, "git-checkout", "Check Out...", "git-fetch" ); } if ( branch.type == Git::RefType::Head ) { @@ -1095,7 +1150,7 @@ void GitPlugin::openBranchMenu( const Git::Branch& branch ) { if ( branch.behind ) addMenuItem( menu, "git-fast-forward-merge", "Fast Forward Merge" ); menu->addSeparator(); - addMenuItem( menu, "git-branch-delete", "Delete" ); + addMenuItem( menu, "git-branch-delete", "Delete", "remove" ); } addMenuItem( menu, "git-create-branch", "Create Branch", "repo-forked", { KEY_F7 } ); @@ -1176,6 +1231,7 @@ void GitPlugin::runAsync( std::function fn, bool _updateStatus, b } if ( _updateBranches ) updateBranches(); + if ( _updateStatus ) updateStatus( true ); } ); diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp index 1077cccbc..78a06488c 100644 --- a/src/tools/ecode/plugins/git/gitplugin.hpp +++ b/src/tools/ecode/plugins/git/gitplugin.hpp @@ -69,6 +69,10 @@ class GitPlugin : public PluginBase { std::vector repos(); + std::unordered_map updateReposBranches(); + + void updateRepos(); + protected: std::unique_ptr mGit; std::unordered_map mGitBranches; @@ -103,11 +107,13 @@ class GitPlugin : public PluginBase { UIWidget* mGitContentView{ nullptr }; UIWidget* mGitNoContentView{ nullptr }; UILoader* mLoader{ nullptr }; - std::atomic mRunningUpdateBranches{ false }; - std::atomic mRunningUpdateStatus{ false }; + std::atomic mRunningUpdateBranches{ 0 }; + std::atomic mRunningUpdateStatus{ 0 }; + Clock mLastBranchesUpdate; Mutex mGitBranchMutex; Mutex mGitStatusMutex; Mutex mRepoMutex; + Mutex mReposMutex; struct CustomTokenizer { SyntaxDefinition def; @@ -152,7 +158,7 @@ class GitPlugin : public PluginBase { void branchCreate(); - void commit(); + void commit( const std::string& repoPath = "" ); void stage( const std::vector& files ); diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index f7b9a809c..8cb0e775d 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -274,10 +274,12 @@ void PluginManager::setFileSystemListener( FileSystemListener* listener ) { } void PluginManager::subscribeFileSystemListener( Plugin* plugin ) { + Lock l( mPluginsFSSubsMutex ); mPluginsFSSubs.insert( plugin ); } void PluginManager::unsubscribeFileSystemListener( Plugin* plugin ) { + Lock l( mPluginsFSSubsMutex ); mPluginsFSSubs.erase( plugin ); } @@ -287,6 +289,7 @@ void PluginManager::subscribeFileSystemListener() { mFileSystemListenerCb = mFileSystemListener->addListener( [this]( const FileEvent& ev, const FileInfo& file ) { + Lock l( mPluginsFSSubsMutex ); for ( Plugin* plugin : mPluginsFSSubs ) plugin->onFileSystemEvent( ev, file ); } ); diff --git a/src/tools/ecode/plugins/pluginmanager.hpp b/src/tools/ecode/plugins/pluginmanager.hpp index 49b7a3c30..faa362914 100644 --- a/src/tools/ecode/plugins/pluginmanager.hpp +++ b/src/tools/ecode/plugins/pluginmanager.hpp @@ -357,6 +357,7 @@ class PluginManager { UICodeEditorSplitter* mSplitter{ nullptr }; FileSystemListener* mFileSystemListener{ nullptr }; Mutex mSubscribedPluginsMutex; + Mutex mPluginsFSSubsMutex; SubscribedPlugins mSubscribedPlugins; OnLoadFileCb mLoadFileFn; Uint64 mFileSystemListenerCb{ 0 };