#ifndef ECODE_GITPLUGIN_HPP #define ECODE_GITPLUGIN_HPP #include "../plugin.hpp" #include "../pluginmanager.hpp" #include "git.hpp" #include "githistorymodel.hpp" #include #include #include #include #include #include #include using namespace EE::UI::Models; using namespace EE::UI; using namespace EE::Scene; namespace EE::UI { class UITreeView; class UIDropDownList; class UIDropDownModelList; class UIStackWidget; class UIListBoxItem; class UIMenu; class UITextView; class UISplitter; } // namespace EE::UI namespace ecode { class Git; class GitBranchModel; class GitHistoryRefModel : public Model { public: explicit GitHistoryRefModel( std::shared_ptr source, String headLabel ); size_t rowCount( const ModelIndex& = {} ) const override { return mSourceIndexes.size() + 1; } size_t columnCount( const ModelIndex& = {} ) const override { return 1; } ModelIndex index( int row, int column, const ModelIndex& parent = {} ) const override; Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const override; std::string revision( size_t index ) const; bool isRevision( size_t index, std::string_view revision ) const; private: std::shared_ptr mSource; std::vector mSourceIndexes; String mHeadLabel; }; 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"; static constexpr const char* GIT_STASH = "git-stash"; static constexpr const char* GIT_STASH_TOOLTIP_CLASS = "git-stash-tooltip"; class GitPlugin : public PluginBase { public: static PluginDefinition Definition() { return { "git", "Git", "Git integration", GitPlugin::New, { 0, 2, 0 }, GitPlugin::NewSync }; } static Plugin* New( PluginManager* pluginManager ); static Plugin* NewSync( PluginManager* pluginManager ); virtual ~GitPlugin(); std::string getId() override { return Definition().id; } std::string getTitle() override { return Definition().name; } std::string getDescription() override { return Definition().description; } bool hasSettingsPage() const override { return true; } void registerSettings( SettingsPage& page ) override; void onSaveState( IniFile* state ) override; void onFileSystemEvent( const FileEvent& ev, const FileInfo& file ) override; FileSystemListenerOptions getFileSystemListenerOptions() const override; void onRegister( UICodeEditor* ) override; void onUnregister( UICodeEditor* ) override; bool onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* menu, const Vector2i& position, const Uint32& flags ) override; bool onKeyDown( UICodeEditor*, const KeyEvent& ) override; bool onMouseLeave( UICodeEditor*, const Vector2i&, const Uint32& ) override; std::string gitBranch(); std::string statusTypeToString( Git::GitStatusType type ); std::vector repos(); std::unordered_map updateReposBranches(); void updateRepos(); bool isSilent() const { return mSilent; } protected: MainThreadLifetime mLifetime; std::shared_ptr mGit; std::unordered_map mGitBranches; Git::Status mGitStatus; std::vector> mRepos; UnorderedSet mGitStatusFilesCache; std::string mProjectPath; std::string mRepoSelected; std::string mHighlightStyleColor; Time mRefreshFreq{ Seconds( 5 ) }; bool mGitFound{ false }; bool mTooltipInfoShowing{ false }; bool mStatusBarDisplayBranch{ true }; bool mStatusBarDisplayModifications{ true }; bool mStatusRecurseSubmodules{ true }; bool mFileTreeHighlightChanges{ true }; bool mOldDontAutoHideOnMouseMove{ false }; bool mOldUsingCustomStyling{ false }; bool mInitialized{ false }; bool mSilent{ true }; Uint32 mOldTextStyle{ 0 }; Uint32 mOldTextAlign{ 0 }; Color mOldBackgroundColor; UITabWidget* mSidePanel{ nullptr }; UITab* mTab{ nullptr }; UIWidget* mTabContents{ nullptr }; UILinearLayout* mStatusBar{ nullptr }; UIPushButton* mStatusButton{ nullptr }; UITreeView* mBranchesTree{ nullptr }; UITreeView* mStatusTree{ nullptr }; UITreeView* mHistoryTree{ nullptr }; UIDropDownModelList* mHistoryRefDropDown{ nullptr }; std::shared_ptr mHistoryRefModel; std::shared_ptr mHistoryModel; UIDropDownList* mPanelSwicher{ nullptr }; UIDropDownList* mRepoDropDown{ nullptr }; UIStackWidget* mStackWidget{ nullptr }; SmallVector mStackMap; UIWidget* mGitContentView{ nullptr }; UIWidget* mGitNoContentView{ nullptr }; UIWidget* mConflictStateBar{ nullptr }; UITextView* mConflictStateText{ nullptr }; UILoader* mLoader{ nullptr }; std::atomic mRunningUpdateBranches{ 0 }; std::atomic mRunningHistoryRequests{ 0 }; std::atomic mHistoryGeneration{ 0 }; std::string mHistoryRepo; std::string mHistoryRevision{ "HEAD" }; bool mUpdatingHistoryRefs{ false }; bool mHistoryLoaded{ false }; struct CommitDetailsState { UIWidget* view{ nullptr }; UITextView* subject{ nullptr }; UITextView* author{ nullptr }; UITextView* dateEmail{ nullptr }; UITextView* message{ nullptr }; UITextView* status{ nullptr }; UIPushButton* messageToggle{ nullptr }; UIPushButton* filesToggle{ nullptr }; UIPushButton* modeToggle{ nullptr }; UIPushButton* gitHub{ nullptr }; UIWidget* diffContainer{ nullptr }; UIScrollView* diff{ nullptr }; std::string messageBody; std::string url; Git::Commit commit; std::string repo; std::atomic generation{ 0 }; std::shared_ptr diffPreparationCancelled; EventConnection closeConnection; Tools::UIDiffView::ViewMode viewMode{ Tools::UIDiffView::ViewMode::Unified }; bool messageExpanded{ false }; bool filesCollapsed{ false }; bool workingTree{ false }; void openCommitDetails( GitPlugin& plugin, const Git::Commit& commit, bool detached, bool workingTree = false ); void loadCommitFiles( GitPlugin& plugin, bool detached ); void cancelDiffPreparation() { if ( diffPreparationCancelled ) diffPreparationCancelled->store( true, std::memory_order_relaxed ); diffPreparationCancelled.reset(); } void reset() { cancelDiffPreparation(); ++generation; view = nullptr; subject = nullptr; author = nullptr; dateEmail = nullptr; message = nullptr; status = nullptr; messageToggle = nullptr; filesToggle = nullptr; modeToggle = nullptr; gitHub = nullptr; diffContainer = nullptr; diff = nullptr; messageBody.clear(); url.clear(); commit = {}; repo.clear(); viewMode = Tools::UIDiffView::ViewMode::Unified; messageExpanded = false; filesCollapsed = false; workingTree = false; } }; struct DetachedHistoryState { UISplitter* view{ nullptr }; UITreeView* tree{ nullptr }; UIWidget* detailsHost{ nullptr }; EventConnection closeConnection; std::string focusHash; CommitDetailsState details; void reset() { view = nullptr; tree = nullptr; detailsHost = nullptr; focusHash.clear(); details.reset(); } }; CommitDetailsState mCommitDetails; DetachedHistoryState mDetachedHistory; std::atomic mRunningUpdateStatus{ 0 }; std::atomic mPendingForcedStatusUpdate{ false }; std::shared_ptr> mRunningAsyncTasks{ std::make_shared>( 0 ) }; Clock mLastBranchesUpdate; Mutex mGitBranchMutex; Mutex mGitStatusMutex; Mutex mGitStatusFileCacheMutex; Mutex mRepoMutex; Mutex mReposMutex; String mLastCommitMsg; struct GitConflictSession { std::string repoPath; std::vector files; size_t currentFile{ 0 }; Uint64 generation{ 0 }; Git::GitOperation operation{ Git::GitOperation::None }; }; UnorderedMap> mConflictSessions; std::string mActiveConflictRepo; Tools::UIMergeView* mConflictView{ nullptr }; EventConnection mConflictViewCloseConnection; Uint64 mConflictGeneration{ 0 }; Uint32 mRepositionCbId{ 0 }; struct CustomTokenizer { SyntaxDefinition def; SyntaxColorScheme scheme; }; std::optional mStatusCustomTokenizer; std::optional mCommitStatusCustomTokenizer; std::optional mTooltipCustomSyntaxDef; Uint32 mModelChangedId{ 0 }; Uint32 mModelStylerId{ 0 }; 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; Color getVarColor( const std::string& var ); void blame( UICodeEditor* editor ); void checkout( Git::Branch branch ); void branchRename( Git::Branch branch ); void branchDelete( Git::Branch branch ); void branchMerge( Git::Branch branch ); void fastForwardMerge( Git::Branch branch ); void pull( const std::string& repoPath ); void push( const std::string& repoPath ); void fetch( const std::string& repoPath ); void branchCreate(); void commit( const std::string& repoPath, bool mergeCommit = false ); void stage( const std::vector& files ); void unstage( const std::vector& files ); enum class FileOperation { Stage, Unstage, Discard, RestoreHead }; void runFileOperation( std::vector files, FileOperation operation ); void discard( const std::vector& files ); void discardConflicts( const std::vector& files ); void discard( const std::string& file ); void diff( const Git::DiffMode mode, const std::string& repoPath ); void diff( const std::string& file, Git::GitStatusType status ); void diff( std::vector files ); void openFile( const std::string& file ); void openConflictResolver( const std::string& file ); void loadConflictResolverView( std::shared_ptr resultDocument, Git::ConflictFile conflict, std::string repository, std::vector files, size_t currentFile, Git::GitOperation operation, Uint64 generation ); void recreateConflict(); void saveAndStageConflict(); void openAdjacentConflict( bool next ); void continueConflictOperation(); void abortConflictOperation(); void acceptConflictSide( const std::string& file, bool stage2 ); void runAsyncTask( std::function task ); void updateStatus( bool force = false ); void updateStatusBarSync(); void styleCommitFilesStatus( UITextView* status ); void updateUI(); void updateUINow( bool force = false ); void updateBranches( bool force = false ); void ensureHistoryLoaded(); void updateHistoryHeader(); void updateHistoryRefs( const std::shared_ptr& model ); void reloadHistory(); void invalidateHistory(); void loadHistoryPage( GitHistoryModel::Node* node, Git::HistoryQuery query, bool append ); void activateHistoryIndex( const ModelIndex& index, bool expand ); void openCommitDetails( const Git::Commit& commit ); void openWorkingTreeDetails( bool detached ); void showGitHistory( const Git::Commit* commit = nullptr ); void focusDetachedHistory(); void openHistoryMenu( const ModelIndex& index, bool showHistoryAction ); void addTag( const Git::Commit& commit ); void createBranchAtCommit( const Git::Commit& commit ); void revertCommit( const Git::Commit& commit ); bool canStartGitOperation(); void openDetachedCommitDetails( const Git::Commit& commit ); void ensureDetachedCommitDetailsHost(); std::string detachedHistoryTitle(); void updateDetachedHistoryTitle(); void buildSidePanelTab(); void updateBranchesUI( std::shared_ptr ); void openBranchMenu( const Git::Branch& branch ); void openFileStatusMenu( std::vector files ); void stashPush( const std::vector& files, const std::string& repoPath ); void stashApply( const Git::Branch& branch ); void stashDrop( const Git::Branch& branch ); void runAsync( std::function fn, bool updateStatus, bool updateBranches, bool displaySuccessMsg = false, bool updateBranchesOnError = false, bool updateStatusOnError = false, bool historyChanged = false ); void runMergeLikeAsync( std::function fn, const std::string& repoPath ); GitConflictSession* conflictSession( const std::string& repoPath ); GitConflictSession* activeConflictSession(); bool updateConflictSessions( UnorderedMap& conflictStates ); void menuAdd( UIMenu* menu, const std::string& cmd, const std::string& text, const std::string& icon = "", const KeyBindings::Shortcut& forcedKeybinding = KeyBindings::Shortcut() ); std::string repoSelected(); std::string projectPath(); std::string repoName( const std::string& repoPath ); std::string repoPath( const std::string& repoName ); std::string repoFullName( const std::string& repoPath ); std::string fixFilePath( const std::string& file ); std::vector fixFilePaths( const std::vector& files ); std::optional getBranchFromRepoPath( const std::string& repoPath ); void initModelStyler(); void endModelStyler(); void hideSidePanel(); }; } // namespace ecode #endif // ECODE_GITPLUGIN_HPP