From bdd018feece132cab3a28b660b42e3b07a389341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Tue, 23 Jan 2024 20:36:01 -0300 Subject: [PATCH] More Git plugin WIP. Added commit. --- include/eepp/ui/uimessagebox.hpp | 26 +- include/eepp/ui/uiwindow.hpp | 3 +- src/eepp/ui/doc/syntaxdefinitionmanager.cpp | 32 ++- src/eepp/ui/uimessagebox.cpp | 18 +- src/eepp/ui/uiscenenode.cpp | 1 + src/eepp/ui/uiwindow.cpp | 3 + .../eepp/maps/mapeditor/tilemapproperties.cpp | 1 + .../src/eepp/maps/mapeditor/uigotypenew.cpp | 1 + .../src/eepp/maps/mapeditor/uimaplayernew.cpp | 1 + src/tools/ecode/iconmanager.cpp | 1 + src/tools/ecode/plugins/git/git.cpp | 54 +++- src/tools/ecode/plugins/git/git.hpp | 23 +- src/tools/ecode/plugins/git/gitplugin.cpp | 260 ++++++++++++++---- src/tools/ecode/plugins/git/gitplugin.hpp | 52 ++-- 14 files changed, 349 insertions(+), 127 deletions(-) diff --git a/include/eepp/ui/uimessagebox.hpp b/include/eepp/ui/uimessagebox.hpp index 569089950..aecec8727 100644 --- a/include/eepp/ui/uimessagebox.hpp +++ b/include/eepp/ui/uimessagebox.hpp @@ -1,21 +1,22 @@ #ifndef EE_UICUIMESSAGEBOX_HPP #define EE_UICUIMESSAGEBOX_HPP -#include -#include -#include -#include #include namespace EE { namespace UI { +class UITextEdit; +class UITextInput; +class UILayout; +class UIPushButton; + #define UI_MESSAGE_BOX_DEFAULT_FLAGS \ UI_WIN_CLOSE_BUTTON | UI_WIN_USE_DEFAULT_BUTTONS_ACTIONS | UI_WIN_MODAL | \ UI_WIN_SHARE_ALPHA_WITH_CHILDS class EE_API UIMessageBox : public UIWindow { public: - enum Type { OK_CANCEL, YES_NO, RETRY_CANCEL, OK, INPUT }; + enum Type { OK_CANCEL, YES_NO, RETRY_CANCEL, OK, INPUT, TEXT_EDIT }; static UIMessageBox* New( const Type& type, const String& message, const Uint32& windowFlags = UI_MESSAGE_BOX_DEFAULT_FLAGS ); @@ -43,16 +44,19 @@ class EE_API UIMessageBox : public UIWindow { UITextInput* getTextInput() const; + UITextEdit* getTextEdit() const; + UILayout* getLayoutCont() const; - protected: + protected: Type mMsgBoxType; - UITextView* mTextBox; - UIPushButton* mButtonOK; - UIPushButton* mButtonCancel; - UITextInput* mTextInput; + UITextView* mTextBox{ nullptr }; + UIPushButton* mButtonOK{ nullptr }; + UIPushButton* mButtonCancel{ nullptr }; + UITextInput* mTextInput{ nullptr }; + UITextEdit* mTextEdit{ nullptr }; KeyBindings::Shortcut mCloseShortcut; - UILayout* mLayoutCont; + UILayout* mLayoutCont{ nullptr }; virtual void onWindowReady(); diff --git a/include/eepp/ui/uiwindow.hpp b/include/eepp/ui/uiwindow.hpp index 35b321b3d..bb99e038d 100644 --- a/include/eepp/ui/uiwindow.hpp +++ b/include/eepp/ui/uiwindow.hpp @@ -2,8 +2,6 @@ #define EE_UICUIWINDOW_HPP #include -#include -#include #include namespace EE { namespace Graphics { @@ -12,6 +10,7 @@ class FrameBuffer; namespace EE { namespace UI { +class UITextView; class UISceneNode; enum UIWindowFlags { diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index dac89ab13..e6de8b633 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -1124,21 +1124,23 @@ static void addCMake() { for ( const auto& keyword : cmake_literals ) cmake_symbols[keyword] = "literal"; - SyntaxDefinitionManager::instance()->add( { "CMake", - { "%.cmake$", "CMakeLists.txt$" }, - { - { { "#", "[^\\]\n" }, "comment" }, - { { "\"", "\"", "\\" }, "string" }, - { { "'", "'", "\\" }, "string" }, - { { "[%a_][%w_]*%s?%f[(]" }, "function" }, - { { "CMAKE_[%w%d_]+" }, "keyword" }, - { { "CTEST_[%w%d_]+" }, "keyword" }, - { { "%u[%u%d_]*_[%u%d_]+" }, "keyword" }, - { { "%${[%a_][%w_]*%}" }, "keyword2" }, - { { "[%a_][%w_]*" }, "symbol" }, - }, - std::move( cmake_symbols ), - "//" } ); + SyntaxDefinitionManager::instance()->add( + { "CMake", + { "%.cmake$", "CMakeLists.txt$" }, + { + { { "#", "[^\\]\n" }, "comment" }, + { { "\"", "\"", "\\" }, "string" }, + { { "'", "'", "\\" }, "string" }, + { { "[%a_][%w_]*%s?%f[(]" }, "function" }, + { { "CMAKE_[%w%d_]+" }, "keyword" }, + { { "CTEST_[%w%d_]+" }, "keyword" }, + { { "%u[%u%d_]*_[%u%d_]+" }, "keyword" }, + { { "%${[%a_][%w_]*%}" }, "keyword2" }, + { { "[%a_][%w_]*" }, "symbol" }, + }, + std::move( cmake_symbols ), + "//", + { "^cmake_minimum_required.*%c" } } ); } static void addJSX() { diff --git a/src/eepp/ui/uimessagebox.cpp b/src/eepp/ui/uimessagebox.cpp index 76d05b9d5..a3b888dd7 100644 --- a/src/eepp/ui/uimessagebox.cpp +++ b/src/eepp/ui/uimessagebox.cpp @@ -1,7 +1,12 @@ +#include #include #include +#include #include #include +#include +#include +#include #include namespace EE { namespace UI { @@ -12,7 +17,7 @@ UIMessageBox* UIMessageBox::New( const Type& type, const String& message, } UIMessageBox::UIMessageBox( const Type& type, const String& message, const Uint32& windowFlags ) : - UIWindow(), mMsgBoxType( type ), mTextInput( NULL ), mCloseShortcut( KEY_UNKNOWN ) { + UIWindow(), mMsgBoxType( type ), mCloseShortcut( KEY_UNKNOWN ) { mVisible = false; mStyleConfig.WinFlags = windowFlags; @@ -41,6 +46,12 @@ UIMessageBox::UIMessageBox( const Type& type, const String& message, const Uint3 ->setParent( vlay ) ->addEventListener( Event::OnPressEnter, [this]( const Event* ) { sendCommonEvent( Event::OnConfirm ); } ); + } else if ( mMsgBoxType == TEXT_EDIT ) { + mTextEdit = UITextEdit::New(); + mTextEdit->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ) + ->setLayoutMargin( Rectf( 0, 4, 0, 4 ) ) + ->setSize( PixelDensity::dpToPx( Vector2f{ 400, 100 } ) ) + ->setParent( vlay ); } UILinearLayout* hlay = UILinearLayout::NewHorizontal(); @@ -58,6 +69,7 @@ UIMessageBox::UIMessageBox( const Type& type, const String& message, const Uint3 switch ( mMsgBoxType ) { case UIMessageBox::INPUT: + case UIMessageBox::TEXT_EDIT: case UIMessageBox::OK_CANCEL: { mButtonOK->setText( getTranslatorString( "@string/msg_box_ok", "Ok" ) ); mButtonCancel->setText( getTranslatorString( "@string/msg_box_cancel", "Cancel" ) ); @@ -180,6 +192,10 @@ UITextInput* UIMessageBox::getTextInput() const { return mTextInput; } +UITextEdit* UIMessageBox::getTextEdit() const { + return mTextEdit; +} + UILayout* UIMessageBox::getLayoutCont() const { return mLayoutCont; } diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index 782fbaac3..150e51c17 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/src/eepp/ui/uiwindow.cpp b/src/eepp/ui/uiwindow.cpp index 8f23d5540..929964930 100644 --- a/src/eepp/ui/uiwindow.cpp +++ b/src/eepp/ui/uiwindow.cpp @@ -9,11 +9,14 @@ #include #include #include +#include #include #include #include +#include #include #include + #define PUGIXML_HEADER_ONLY #include diff --git a/src/modules/maps/src/eepp/maps/mapeditor/tilemapproperties.cpp b/src/modules/maps/src/eepp/maps/mapeditor/tilemapproperties.cpp index ecffca18b..553413bad 100644 --- a/src/modules/maps/src/eepp/maps/mapeditor/tilemapproperties.cpp +++ b/src/modules/maps/src/eepp/maps/mapeditor/tilemapproperties.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include diff --git a/src/modules/maps/src/eepp/maps/mapeditor/uigotypenew.cpp b/src/modules/maps/src/eepp/maps/mapeditor/uigotypenew.cpp index 20d04f007..8f7f91804 100644 --- a/src/modules/maps/src/eepp/maps/mapeditor/uigotypenew.cpp +++ b/src/modules/maps/src/eepp/maps/mapeditor/uigotypenew.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include diff --git a/src/modules/maps/src/eepp/maps/mapeditor/uimaplayernew.cpp b/src/modules/maps/src/eepp/maps/mapeditor/uimaplayernew.cpp index e3e029ebe..4c69e9cde 100644 --- a/src/modules/maps/src/eepp/maps/mapeditor/uimaplayernew.cpp +++ b/src/modules/maps/src/eepp/maps/mapeditor/uimaplayernew.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include diff --git a/src/tools/ecode/iconmanager.cpp b/src/tools/ecode/iconmanager.cpp index 75f44c599..2ec6df77b 100644 --- a/src/tools/ecode/iconmanager.cpp +++ b/src/tools/ecode/iconmanager.cpp @@ -206,6 +206,7 @@ void IconManager::init( UISceneNode* sceneNode, FontTrueType* iconFont, FontTrue { "repo", 0xea62 }, { "repo-pull", 0xeb40 }, { "repo-push", 0xeb41 }, + { "repo-forked", 0xea63 }, { "tag", 0xea66 } }; for ( const auto& icon : codIcons ) diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp index 668f8dac0..615178b62 100644 --- a/src/tools/ecode/plugins/git/git.cpp +++ b/src/tools/ecode/plugins/git/git.cpp @@ -182,16 +182,30 @@ Git::CheckoutResult Git::checkoutAndCreateLocalBranch( const std::string& remote return checkout( newBranchName, projectDir ); } -Git::Result Git::add( const std::string& file, const std::string& projectDir ) { - return gitSimple( String::format( "add --force -- \"%s\"", file ), projectDir ); +static std::string asList( std::vector& files ) { + for ( auto& file : files ) + file = "\"" + file + "\""; + return String::join( files ); +} + +Git::Result Git::add( std::vector files, const std::string& projectDir ) { + return gitSimple( String::format( "add --force -- %s", asList( files ) ), projectDir ); } Git::Result Git::restore( const std::string& file, const std::string& projectDir ) { return gitSimple( String::format( "restore \"%s\"", file ), projectDir ); } -Git::Result Git::reset( const std::string& file, const std::string& projectDir ) { - return gitSimple( String::format( "reset -q HEAD -- \"%s\"", file ), projectDir ); +Git::Result Git::reset( std::vector files, const std::string& projectDir ) { + return gitSimple( String::format( "reset -q HEAD -- %s", asList( files ) ), projectDir ); +} + +Git::Result Git::createBranch( const std::string& branchName, bool _checkout, + const std::string& projectDir ) { + auto res = gitSimple( String::format( "branch --no-track %s", branchName ), projectDir ); + if ( _checkout ) + checkout( branchName ); + return res; } Git::Result Git::renameBranch( const std::string& branch, const std::string& newName, @@ -230,6 +244,12 @@ Git::Result Git::fastForwardMerge( const std::string& projectDir ) { return gitSimple( "merge --no-commit --ff --ff-only", projectDir ); } +Git::Result Git::updateRef( const std::string& headBranch, const std::string& toCommit, + const std::string& projectDir ) { + return gitSimple( String::format( "update-ref refs/heads/%s %s", headBranch, toCommit ), + projectDir ); +} + Git::CountResult Git::branchHistoryPosition( const std::string& localBranch, const std::string& remoteBranch, const std::string& projectDir ) { @@ -345,16 +365,22 @@ static Git::Branch parseTag( std::string_view raw ) { return newBranch; } -std::vector Git::getAllBranchesAndTags( RefType ref, const std::string& projectDir ) { +std::vector Git::getAllBranchesAndTags( RefType ref, std::string_view filterBranch, + const std::string& projectDir ) { // clang-format off std::string args( "for-each-ref --format '%(refname) %(refname:short) %(upstream:short) %(objectname) %(upstream:track,nobracket)' --sort=v:refname" ); // clang-format on - if ( ref & RefType::Head ) - args.append( " refs/heads" ); - if ( ref & RefType::Remote ) - args.append( " refs/remotes" ); - if ( ref & RefType::Tag ) - args.append( " refs/tags" ); + + if ( filterBranch.empty() ) { + if ( ref & RefType::Head ) + args.append( " refs/heads" ); + if ( ref & RefType::Remote ) + args.append( " refs/remotes" ); + if ( ref & RefType::Tag ) + args.append( " refs/tags" ); + } else { + args.append( " " + filterBranch ); + } std::vector branches; std::string buf; @@ -406,7 +432,7 @@ bool Git::hasSubmodules( const std::string& projectDir ) { ( !mProjectPath.empty() && FileSystem::fileExists( mProjectPath + ".gitmodules" ) ); } -std::string Git::inSubModule( const std::string& file, const std::string& projectDir ) { +std::string Git::repoName( const std::string& file, const std::string& projectDir ) { for ( const auto& subRepo : mSubModules ) { if ( String::startsWith( file, subRepo ) && file.size() != subRepo.size() ) return subRepo; @@ -454,7 +480,7 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) if ( String::fromString( inserts, inserted ) && String::fromString( deletes, deleted ) && ( inserts || deletes ) ) { auto filePath = subModulePath + file; - auto repo = inSubModule( filePath, projectDir ); + auto repo = repoName( filePath, projectDir ); auto repoIt = s.files.find( repo ); if ( repoIt != s.files.end() ) { bool found = false; @@ -660,7 +686,7 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) modifiedSubmodule = true; else { auto filePath = subModulePath + file; - auto repo = inSubModule( filePath, projectDir ); + auto repo = repoName( filePath, projectDir ); auto repoIt = s.files.find( repo ); if ( repoIt != s.files.end() ) { bool found = false; diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp index f745113b2..b4e2dc862 100644 --- a/src/tools/ecode/plugins/git/git.hpp +++ b/src/tools/ecode/plugins/git/git.hpp @@ -201,11 +201,14 @@ class Git { Status status( bool recurseSubmodules, const std::string& projectDir = "" ); - Result add( const std::string& file, const std::string& projectDir = "" ); + Result add( std::vector files, const std::string& projectDir = "" ); Result restore( const std::string& file, const std::string& projectDir = "" ); - Result reset( const std::string& file, const std::string& projectDir = "" ); + Result reset( std::vector files, const std::string& projectDir = "" ); + + Result createBranch( const std::string& branchName, bool checkout = false, + const std::string& projectDir = "" ); Result renameBranch( const std::string& branch, const std::string& newName, const std::string& projectDir = "" ); @@ -218,6 +221,9 @@ class Git { Result fastForwardMerge( const std::string& projectDir = "" ); + Result updateRef( const std::string& headBranch, const std::string& toCommit, + const std::string& projectDir = "" ); + CountResult branchHistoryPosition( const std::string& localBranch, const std::string& remoteBranch, const std::string& projectDir = "" ); @@ -256,12 +262,19 @@ class Git { * @brief get all local and remote branches + tags */ std::vector getAllBranchesAndTags( RefType ref = RefType::All, + std::string_view filterBranch = {}, const std::string& projectDir = "" ); std::vector fetchSubModules( const std::string& projectDir ); std::vector getSubModules( const std::string& projectDir = "" ); + std::string repoName( const std::string& file, const std::string& projectDir = "" ); + + bool hasSubmodules( const std::string& projectDir ); + + Result gitSimple( const std::string& cmd, const std::string& projectDir ); + protected: std::string mGitPath; std::string mProjectPath; @@ -269,12 +282,6 @@ class Git { 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 ); - - Result gitSimple( const std::string& cmd, const std::string& projectDir ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index 52c4a6e88..1501f511a 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -52,7 +53,9 @@ std::string GitPlugin::statusTypeToString( Git::GitStatusType type ) { static size_t hashBranches( const std::vector& branches ) { size_t hash = 0; for ( const auto& branch : branches ) - hash = hashCombine( hash, String::hash( branch.name ) ); + hash = hashCombine( hash, String::hash( branch.name ), String::hash( branch.remote ), + String::hash( branch.lastCommit ), branch.type, branch.ahead, + branch.behind ); return hash; } @@ -254,9 +257,6 @@ class GitStatusModel : public Model { rs.type.emplace_back( std::move( rt ) ); } mStatus.emplace_back( std::move( rs ) ); - auto parent = &mStatus[mStatus.size() - 1]; - for ( auto& t : parent->type ) - t.parent = parent; } for ( auto& s : status ) { @@ -267,6 +267,15 @@ class GitStatusModel : public Model { 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; } @@ -293,7 +302,8 @@ class GitStatusModel : public Model { if ( index.internalId() == Status ) { RepoStatusType* status = reinterpret_cast( index.internalData() ); size_t f = 0; - for ( size_t i = 0; i < mStatus.size(); i++ ) { + size_t statusSize = mStatus.size(); + for ( size_t i = 0; i < statusSize; i++ ) { if ( &mStatus[i] == status->parent ) { f = i; break; @@ -305,8 +315,10 @@ class GitStatusModel : public Model { 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 < status->parent->type.size(); i++ ) { + for ( size_t i = 0; i < typeSize; i++ ) { if ( &status->parent->type[i] == status ) { f = i; break; @@ -410,6 +422,18 @@ class GitStatusModel : public Model { 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; @@ -418,6 +442,23 @@ class GitStatusModel : public Model { .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 }; @@ -935,12 +976,62 @@ void GitPlugin::fetch() { runAsync( [this]() { return mGit->fetch(); }, true, true ); } -void GitPlugin::stage( const std::string& file ) { - runAsync( [this, file]() { return mGit->add( file ); }, true, false ); +void GitPlugin::branchCreate() { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, + i18n( "git_create_branch_ask", + "Create new branch at current branch (HEAD).\nEnter the name for the branch:" ) ); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + std::string newName( msgBox->getTextInput()->getText().toUtf8() ); + if ( newName.empty() ) + return; + msgBox->closeWindow(); + runAsync( [this, newName]() { return mGit->createBranch( newName, true ); }, false, true ); + } ); + msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } ); + msgBox->setTitle( i18n( "git_add_branch", "Add Branch" ) ); + msgBox->center(); + msgBox->showWhenReady(); } -void GitPlugin::unstage( const std::string& file ) { - runAsync( [this, file]() { return mGit->reset( file ); }, true, false ); +void GitPlugin::commit() { + UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::TEXT_EDIT, + i18n( "git_commit_message", "Commit Message:" ) ); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + std::string msg( msgBox->getTextEdit()->getText().toUtf8() ); + if ( msg.empty() ) + return; + msgBox->closeWindow(); + runAsync( [this, msg]() { return mGit->commit( msg ); }, false, true ); + } ); + msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } ); + msgBox->setTitle( i18n( "git_commit", "Commit" ) ); + msgBox->center(); + msgBox->showWhenReady(); +} + +void GitPlugin::stage( const std::vector& files ) { + runAsync( [this, files]() { return mGit->add( files ); }, true, false ); +} + +void GitPlugin::unstage( const std::vector& files ) { + runAsync( [this, files]() { return mGit->reset( files ); }, true, false ); +} + +void GitPlugin::fastForwardMerge( Git::Branch branch ) { + runAsync( + [this, branch]() { + if ( branch.name == mGitBranch ) + return mGit->fastForwardMerge(); + + auto remoteBranch = mGit->getAllBranchesAndTags( Git::RefType::Remote, + "refs/remotes/" + branch.remote ); + if ( remoteBranch.empty() ) + return Git::Result{ "", -1 }; + + return mGit->updateRef( branch.name, remoteBranch[0].lastCommit ); + }, + false, true ); } void GitPlugin::discard( const std::string& file ) { @@ -985,7 +1076,9 @@ void GitPlugin::onRegister( UICodeEditor* editor ) { if ( mTab ) mTab->setTabSelected(); } ); - doc.setCommand( "git-pull", [this]() { pull(); } ); + doc.setCommand( "git-pull", [this] { pull(); } ); + doc.setCommand( "git-fetch", [this] { fetch(); } ); + doc.setCommand( "git-commit", [this] { commit(); } ); } void GitPlugin::onUnregister( UICodeEditor* editor ) { @@ -1042,10 +1135,10 @@ void GitPlugin::updateBranches() { if ( !mGit || mGit->getGitFolder().empty() ) return; + auto prevBranch = mGitBranch; { Lock l( mGitBranchMutex ); - if ( mGitBranch.empty() ) - mGitBranch = mGit->branch(); + mGitBranch = mGit->branch(); } auto branches = mGit->getAllBranchesAndTags(); @@ -1053,8 +1146,11 @@ void GitPlugin::updateBranches() { auto model = GitBranchModel::asModel( std::move( branches ), hash, this ); if ( mBranchesTree && - static_cast( mBranchesTree->getModel() )->getHash() == hash ) + static_cast( mBranchesTree->getModel() )->getHash() == hash ) { + if ( prevBranch != mGitBranch ) + mBranchesTree->getModel()->invalidate( Model::DontInvalidateIndexes ); return; + } getUISceneNode()->runOnMainThread( [this, model] { updateBranchesUI( model ); } ); }, @@ -1085,6 +1181,7 @@ void GitPlugin::buildSidePanelTab() { + --> @@ -1159,25 +1256,77 @@ void GitPlugin::buildSidePanelTab() { mStatusTree->setExpandersAsIcons( true ); mStatusTree->on( Event::OnModelEvent, [this]( const Event* event ) { const ModelEvent* modelEvent = static_cast( event ); - if ( modelEvent->getModel() == nullptr || - modelEvent->getModelIndex().internalId() != GitStatusModel::GitFile ) - return; - auto model = static_cast( modelEvent->getModel() ); - const Git::DiffFile* file = model->file( modelEvent->getModelIndex() ); - if ( file == nullptr ) + if ( modelEvent->getModel() == nullptr ) return; - switch ( modelEvent->getModelEventType() ) { - case Abstract::ModelEventType::OpenMenu: { - bool focusOnSelection = mStatusTree->getFocusOnSelection(); - mStatusTree->setFocusOnSelection( false ); - mStatusTree->getSelection().set( modelEvent->getModelIndex() ); - mStatusTree->setFocusOnSelection( focusOnSelection ); - openFileStatusMenu( *file ); - break; + auto model = static_cast( modelEvent->getModel() ); + if ( modelEvent->getModelIndex().internalId() == GitStatusModel::GitFile ) { + const Git::DiffFile* file = model->file( modelEvent->getModelIndex() ); + if ( file == nullptr ) + return; + + switch ( modelEvent->getModelEventType() ) { + case Abstract::ModelEventType::OpenMenu: { + bool focusOnSelection = mStatusTree->getFocusOnSelection(); + mStatusTree->setFocusOnSelection( false ); + mStatusTree->getSelection().set( modelEvent->getModelIndex() ); + mStatusTree->setFocusOnSelection( focusOnSelection ); + openFileStatusMenu( *file ); + break; + } + default: + break; + } + } else if ( modelEvent->getModelIndex().internalId() == GitStatusModel::Status ) { + switch ( modelEvent->getModelEventType() ) { + case Abstract::ModelEventType::OpenMenu: { + bool focusOnSelection = mStatusTree->getFocusOnSelection(); + mStatusTree->setFocusOnSelection( false ); + mStatusTree->getSelection().set( modelEvent->getModelIndex() ); + mStatusTree->setFocusOnSelection( focusOnSelection ); + + const auto* status = model->statusType( modelEvent->getModelIndex() ); + if ( status->type == Git::GitStatusType::Staged || + status->type == Git::GitStatusType::Untracked || + status->type == Git::GitStatusType::Changed ) { + UIPopUpMenu* menu = UIPopUpMenu::New(); + menu->setId( "git_status_type_menu" ); + + if ( status->type == Git::GitStatusType::Staged ) { + addMenuItem( menu, "git-commit", "Commit" ); + + addMenuItem( menu, "git-unstage-all", "Unstage All" ); + } + + if ( status->type == Git::GitStatusType::Untracked || + status->type == Git::GitStatusType::Changed ) + addMenuItem( menu, "git-stage-all", "Stage All" ); + + menu->on( Event::OnItemClicked, [this, model]( const Event* event ) { + if ( !mGit ) + return; + UIMenuItem* item = event->getNode()->asType(); + std::string id( item->getId() ); + if ( id == "git-commit" ) { + commit(); + } else if ( id == "git-stage-all" ) { + stage( model->getFiles( mGit->repoName( "" ), + (Uint32)Git::GitStatusType::Untracked | + (Uint32)Git::GitStatusType::Changed ) ); + } else if ( id == "git-unstage-all" ) { + unstage( model->getFiles( mGit->repoName( "" ), + (Uint32)Git::GitStatusType::Staged ) ); + } + } ); + + menu->showOverMouseCursor(); + } + + break; + } + default: + break; } - default: - break; } } ); } @@ -1186,27 +1335,24 @@ void GitPlugin::openBranchMenu( const Git::Branch& branch ) { UIPopUpMenu* menu = UIPopUpMenu::New(); menu->setId( "git_branch_menu" ); - auto addFn = [this, menu]( const std::string& txtKey, const std::string& txtVal, - const std::string& icon = "" ) { - menu->add( i18n( txtKey, txtVal ), iconDrawable( icon, 12 ), - KeyBindings::keybindFormat( mKeyBindings[txtKey] ) ) - ->setId( txtKey ); - }; - - addFn( "git-fetch", "Fetch" ); + addMenuItem( menu, "git-fetch", "Fetch" ); + addMenuItem( menu, "git-create-branch", "Create Branch", "repo-forked" ); if ( mGitBranch != branch.name ) { - addFn( "git-checkout", "Check Out..." ); + addMenuItem( menu, "git-checkout", "Check Out..." ); if ( branch.type == Git::RefType::Head ) { - addFn( "git-branch-rename", "Rename" ); - addFn( "git-branch-delete", "Delete" ); + addMenuItem( menu, "git-branch-rename", "Rename" ); + addMenuItem( menu, "git-branch-delete", "Delete" ); } } else { if ( branch.type == Git::RefType::Head ) { - addFn( "git-pull", "Pull", "repo-pull" ); + addMenuItem( menu, "git-pull", "Pull", "repo-pull" ); } } + if ( branch.type == Git::RefType::Head && branch.behind ) + addMenuItem( menu, "git-fast-forward-merge", "Fast Forward Merge" ); + menu->on( Event::OnItemClicked, [this, branch]( const Event* event ) { if ( !mGit ) return; @@ -1222,6 +1368,10 @@ void GitPlugin::openBranchMenu( const Git::Branch& branch ) { branchRename( branch ); } else if ( id == "git-fetch" ) { fetch(); + } else if ( id == "git-fast-forward-merge" ) { + fastForwardMerge( branch ); + } else if ( id == "git-create-branch" ) { + branchCreate(); } } ); @@ -1232,24 +1382,17 @@ void GitPlugin::openFileStatusMenu( const Git::DiffFile& file ) { UIPopUpMenu* menu = UIPopUpMenu::New(); menu->setId( "git_file_status_menu" ); - auto addFn = [this, menu]( const std::string& txtKey, const std::string& txtVal, - const std::string& icon = "" ) { - menu->add( i18n( txtKey, txtVal ), iconDrawable( icon, 12 ), - KeyBindings::keybindFormat( mKeyBindings[txtKey] ) ) - ->setId( txtKey ); - }; - - addFn( "git-open-file", "Open File" ); + addMenuItem( menu, "git-open-file", "Open File" ); if ( file.statusType != Git::GitStatusType::Staged ) { - addFn( "git-stage", "Stage" ); + addMenuItem( menu, "git-stage", "Stage" ); } else { - addFn( "git-unstage", "Unstage" ); + addMenuItem( menu, "git-unstage", "Unstage" ); } menu->addSeparator(); - addFn( "git-discard", "Discard" ); + addMenuItem( menu, "git-discard", "Discard" ); menu->on( Event::OnItemClicked, [this, file]( const Event* event ) { if ( !mGit ) @@ -1257,9 +1400,9 @@ void GitPlugin::openFileStatusMenu( const Git::DiffFile& file ) { UIMenuItem* item = event->getNode()->asType(); std::string id( item->getId() ); if ( id == "git-stage" ) { - stage( file.file ); + stage( { file.file } ); } else if ( id == "git-unstage" ) { - unstage( file.file ); + unstage( { file.file } ); } else if ( id == "git-discard" ) { discard( file.file ); } else if ( id == "git-open-file" ) { @@ -1289,4 +1432,11 @@ void GitPlugin::runAsync( std::function fn, bool _updateStatus, } ); } +void GitPlugin::addMenuItem( UIMenu* menu, const std::string& txtKey, const std::string& txtVal, + const std::string& icon ) { + menu->add( i18n( txtKey, txtVal ), iconDrawable( icon, 12 ), + KeyBindings::keybindFormat( mKeyBindings[txtKey] ) ) + ->setId( txtKey ); +}; + } // namespace ecode diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp index c55bf31d0..f296ef136 100644 --- a/src/tools/ecode/plugins/git/gitplugin.hpp +++ b/src/tools/ecode/plugins/git/gitplugin.hpp @@ -16,6 +16,7 @@ class UITreeView; class UIDropDownList; class UIStackWidget; class UIListBoxItem; +class UIMenu; } // namespace EE::UI namespace ecode { @@ -62,26 +63,8 @@ class GitPlugin : public PluginBase { std::unique_ptr mGit; std::string mGitBranch; Git::Status mGitStatus; - UILinearLayout* mStatusBar{ nullptr }; - UIPushButton* mStatusButton{ nullptr }; + Time mRefreshFreq{ Seconds( 5 ) }; - - GitPlugin( PluginManager* pluginManager, bool sync ); - - void load( PluginManager* pluginManager ); - - PluginRequestHandle processMessage( const PluginMessage& msg ); - - void displayTooltip( UICodeEditor* editor, const Git::Blame& blame, const Vector2f& position ); - - void hideTooltip( UICodeEditor* editor ); - - void onRegisterListeners( UICodeEditor*, std::vector& listeners ) override; - - void onBeforeUnregister( UICodeEditor* ) override; - - void onUnregisterDocument( TextDocument* ) override; - bool mGitFound{ false }; bool mTooltipInfoShowing{ false }; bool mStatusBarDisplayBranch{ true }; @@ -96,6 +79,8 @@ class GitPlugin : public PluginBase { UITabWidget* mSidePanel{ nullptr }; UITab* mTab{ nullptr }; + UILinearLayout* mStatusBar{ nullptr }; + UIPushButton* mStatusButton{ nullptr }; UITreeView* mBranchesTree{ nullptr }; UITreeView* mStatusTree{ nullptr }; UIDropDownList* mPanelSwicher{ nullptr }; @@ -116,6 +101,22 @@ class GitPlugin : public PluginBase { std::optional mStatusCustomTokenizer; std::optional mTooltipCustomSyntaxDef; + GitPlugin( PluginManager* pluginManager, bool sync ); + + void load( PluginManager* pluginManager ); + + PluginRequestHandle processMessage( const PluginMessage& msg ); + + void displayTooltip( UICodeEditor* editor, const Git::Blame& blame, const Vector2f& position ); + + void hideTooltip( UICodeEditor* editor ); + + void onRegisterListeners( UICodeEditor*, std::vector& listeners ) override; + + void onBeforeUnregister( UICodeEditor* ) override; + + void onUnregisterDocument( TextDocument* ) override; + Color getVarColor( const std::string& var ); void blame( UICodeEditor* editor ); @@ -126,13 +127,19 @@ class GitPlugin : public PluginBase { void branchDelete( Git::Branch branch ); + void fastForwardMerge( Git::Branch branch ); + void pull(); void fetch(); - void stage( const std::string& file ); + void branchCreate(); - void unstage( const std::string& file ); + void commit(); + + void stage( const std::vector& files ); + + void unstage( const std::vector& files ); void discard( const std::string& file ); @@ -157,6 +164,9 @@ class GitPlugin : public PluginBase { void openFileStatusMenu( const Git::DiffFile& file ); void runAsync( std::function fn, bool updateStatus, bool updateBranches ); + + void addMenuItem( UIMenu* menu, const std::string& txtKey, const std::string& txtVal, + const std::string& icon = "" ); }; } // namespace ecode