diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml
index 98ef84fdc..222214b09 100644
--- a/bin/assets/i18n/de.xml
+++ b/bin/assets/i18n/de.xml
@@ -1264,6 +1264,12 @@ Für sichtbare Änderung ecode neu starten.
Ältere Commits laden
Noch keine Commits
Keine weiteren Commits
+ Im Git-Verlauf anzeigen
+ Git-Commit-Verlauf – %s
+ Commit-SHA
+ Commit-SHA kopieren
%s
+ Zur geteilten Diff-Ansicht wechseln
+ Zur vereinheitlichten Diff-Ansicht wechseln
Git-Verlauf konnte nicht geladen werden
Wiederholen
Lokal
diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml
index d4207b7e3..8c203c154 100644
--- a/bin/assets/i18n/en.xml
+++ b/bin/assets/i18n/en.xml
@@ -1249,6 +1249,12 @@ Restart ecode to see the changes.
Load older commits
No commits yet
No additional commits
+ Show in Git History
+ Git Commit History - %s
+ Commit SHA
+ Copy Commit SHA
%s
+ Switch to split diff view
+ Switch to unified diff view
Could not load Git history
Retry
Local
diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml
index 8655312ca..55582671e 100644
--- a/bin/assets/i18n/fr.xml
+++ b/bin/assets/i18n/fr.xml
@@ -1248,6 +1248,12 @@ Redémarrer ecode pour voir les changements.
Charger les commits précédents
Aucun commit pour le moment
Aucun commit supplémentaire
+ Afficher dans l’historique Git
+ Historique des commits Git – %s
+ SHA du commit
+ Copier le SHA du commit
%s
+ Passer à la vue des différences séparée
+ Passer à la vue des différences unifiée
Impossible de charger l’historique Git
Réessayer
Locale
diff --git a/bin/assets/i18n/zh.xml b/bin/assets/i18n/zh.xml
index 5f7812978..3e815aa44 100644
--- a/bin/assets/i18n/zh.xml
+++ b/bin/assets/i18n/zh.xml
@@ -1053,6 +1053,12 @@ file in the directory tree.
加载更早的提交
尚无提交
没有其他提交
+ 在 Git 历史记录中显示
+ Git 提交历史记录 - %s
+ 提交 SHA
+ 复制提交 SHA
%s
+ 切换到拆分差异视图
+ 切换到统一差异视图
无法加载 Git 历史记录
重试
本地
diff --git a/include/eepp/ui/uistyle.hpp b/include/eepp/ui/uistyle.hpp
index d181ad6be..2476c0aa9 100644
--- a/include/eepp/ui/uistyle.hpp
+++ b/include/eepp/ui/uistyle.hpp
@@ -141,7 +141,7 @@ class EE_API UIStyle : public UIState {
/** Lazily allocated for styles that use substitutions. The common, non-reentrant resolution
* needs no container allocation; nested slots are retained for later reuse. */
std::unique_ptr mPropertyResolutionSlot;
- std::vector> mNestedPropertyResolutionSlots;
+ SmallVector, 2> mNestedPropertyResolutionSlots;
bool mChangingState;
bool mForceReapplyProperties;
bool mDisableAnimations;
diff --git a/include/eepp/ui/uiwidget.hpp b/include/eepp/ui/uiwidget.hpp
index 84db6474c..1fabedec0 100644
--- a/include/eepp/ui/uiwidget.hpp
+++ b/include/eepp/ui/uiwidget.hpp
@@ -1342,6 +1342,17 @@ class EE_API UIWidget : public UINode {
*/
bool hasPseudoClass( const std::string& pseudoCls ) const;
+ /**
+ * @brief Checks if this widget has a specific pseudo-class.
+ *
+ * Determines whether this widget currently has the specified pseudo-class
+ * (e.g., hover, focus, active).
+ *
+ * @param pseudoCls The pseudo-class to check.
+ * @return True if the widget has the pseudo-class, false otherwise.
+ */
+ bool hasPseudoClass( StyleSheetSelectorRule::PseudoClasses pseudoCls ) const;
+
/**
* @brief Checks if the tooltip is enabled for this widget.
*
diff --git a/src/eepp/ui/iconmanager.cpp b/src/eepp/ui/iconmanager.cpp
index 10653ca9f..5f721603d 100644
--- a/src/eepp/ui/iconmanager.cpp
+++ b/src/eepp/ui/iconmanager.cpp
@@ -261,6 +261,8 @@ ResourcePtr IconManager::init( const std::string& iconThemeName,
{ "error", 0xea87 },
{ "search-fuzzy", 0xec0d },
{ "source-control", 0xea68 },
+ { "history", 0xea82 },
+ { "git-commit", 0xeafc },
{ "repo", 0xea62 },
{ "repo-pull", 0xeb40 },
{ "repo-push", 0xeb41 },
diff --git a/src/eepp/ui/uitextview.cpp b/src/eepp/ui/uitextview.cpp
index 5cf4a0eac..408f67a55 100644
--- a/src/eepp/ui/uitextview.cpp
+++ b/src/eepp/ui/uitextview.cpp
@@ -735,6 +735,11 @@ void UITextView::onAlignChange() {
}
void UITextView::onSelectionChange() {
+ if ( mUsingCustomStyling ) {
+ invalidateDraw();
+ return;
+ }
+
mTextCache.invalidateColors();
if ( selCurInit() != selCurEnd() ) {
diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp
index 730ab9890..bce885270 100644
--- a/src/eepp/ui/uitreeview.cpp
+++ b/src/eepp/ui/uitreeview.cpp
@@ -308,7 +308,7 @@ UIWidget* UITreeView::updateCell( const Vector2& posIndex, const ModelInd
if ( hasChildren ) {
UIIcon* icon = getIndexMetadata( index ).open ? mExpandIcon : mContractIcon;
DrawablePtr drawable =
- icon ? icon->createDrawable( mExpanderIconSize ) : DrawablePtr{};
+ icon ? icon->getSource( mExpanderIconSize ) : DrawablePtr{};
if ( drawable == nullptr ) {
image->setVisible( false );
diff --git a/src/eepp/ui/uiwidget.cpp b/src/eepp/ui/uiwidget.cpp
index 6f08fed0e..06d362a4d 100644
--- a/src/eepp/ui/uiwidget.cpp
+++ b/src/eepp/ui/uiwidget.cpp
@@ -1333,6 +1333,10 @@ bool UIWidget::hasPseudoClass( const std::string& pseudoCls ) const {
return ( mPseudoClasses & StyleSheetSelectorRule::toPseudoClass( pseudoCls ) ) != 0;
}
+bool UIWidget::hasPseudoClass( StyleSheetSelectorRule::PseudoClasses pseudoCls ) const {
+ return ( mPseudoClasses & static_cast( pseudoCls ) ) != 0;
+}
+
bool UIWidget::isTooltipEnabled() const {
return ( mFlags & UI_TOOLTIP_ENABLED ) != 0;
}
diff --git a/src/tools/ecode/plugins/git/githistorymodel.cpp b/src/tools/ecode/plugins/git/githistorymodel.cpp
index 3a7c1dfe2..44025547a 100644
--- a/src/tools/ecode/plugins/git/githistorymodel.cpp
+++ b/src/tools/ecode/plugins/git/githistorymodel.cpp
@@ -2,6 +2,7 @@
#include "gitplugin.hpp"
#include
#include
+#include
#include
namespace ecode {
@@ -49,6 +50,19 @@ ModelIndex GitHistoryModel::indexForNode( const Node* item, int column ) const {
return {};
}
+ModelIndex GitHistoryModel::indexForCommit( std::string_view hash, int column ) const {
+ std::function find = [&]( const Nodes& nodes ) -> const Node* {
+ for ( const auto& item : nodes ) {
+ if ( item->type == NodeType::Commit && item->commit.hash == hash )
+ return item.get();
+ if ( const Node* found = find( item->children ) )
+ return found;
+ }
+ return nullptr;
+ };
+ return indexForNode( find( mRoots ), column );
+}
+
ModelIndex GitHistoryModel::parentIndex( const ModelIndex& index ) const {
Node* item = node( index );
return item ? indexForNode( item->parent ) : ModelIndex{};
@@ -241,7 +255,8 @@ void GitHistoryModel::setPageLoading( Node* item ) {
if ( !item || item->childrenLoading )
return;
item->childrenLoading = true;
- item->type = NodeType::Loading;
+ // Keep the semantic node type intact: appendPage() must still identify this row as the
+ // pagination cursor when the asynchronous request completes.
item->message = mPlugin->i18n( "git_history_loading", "Loading..." );
item->error.clear();
invalidate( Model::DontInvalidateIndexes );
diff --git a/src/tools/ecode/plugins/git/githistorymodel.hpp b/src/tools/ecode/plugins/git/githistorymodel.hpp
index 5f01bc28b..277d908bb 100644
--- a/src/tools/ecode/plugins/git/githistorymodel.hpp
+++ b/src/tools/ecode/plugins/git/githistorymodel.hpp
@@ -14,7 +14,7 @@ class GitPlugin;
class GitHistoryModel : public Model {
public:
enum class NodeType : uint8_t { Commit, LoadMore, Loading, Error, Empty };
- enum Column { Subject, Author, Date, Hash };
+ enum Column { Subject, Date, Author, Hash };
struct Node {
Git::Commit commit;
Git::HistoryQuery query;
@@ -43,7 +43,7 @@ class GitHistoryModel : public Model {
size_t rowCount( const ModelIndex& index = {} ) const;
- size_t columnCount( const ModelIndex& = {} ) const { return 1; }
+ size_t columnCount( const ModelIndex& = {} ) const { return 4; }
std::string columnName( const size_t& column ) const;
@@ -61,6 +61,8 @@ class GitHistoryModel : public Model {
ModelIndex indexForNode( const Node* node, int column = 0 ) const;
+ ModelIndex indexForCommit( std::string_view hash, int column = Subject ) const;
+
void setRootLoading();
void setRootPage( Git::HistoryPage page, const Git::HistoryQuery& query );
diff --git a/src/tools/ecode/plugins/git/githistorytreeview.cpp b/src/tools/ecode/plugins/git/githistorytreeview.cpp
index ace54c6d5..0e48190e7 100644
--- a/src/tools/ecode/plugins/git/githistorytreeview.cpp
+++ b/src/tools/ecode/plugins/git/githistorytreeview.cpp
@@ -50,8 +50,8 @@ void GitHistoryTreeViewCell::draw() {
if ( mMetadataText.getString().empty() )
return;
- const bool selected = getParent() && ( getParent()->asType()->getStyleState() &
- UIState::StateFlagSelected );
+ const bool selected = getParent() && ( getParent()->asType()->hasPseudoClass(
+ StyleSheetSelectorRule::PseudoClasses::Selected ) );
mMetadataText.setFillColor( selected ? mTextBox->getFontColor() : mHintColor );
mMetadataText.draw( std::floor( mScreenPos.x + mTextBox->getPixelsPosition().x ),
diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp
index a48fd6f4f..026a6a869 100644
--- a/src/tools/ecode/plugins/git/gitplugin.cpp
+++ b/src/tools/ecode/plugins/git/gitplugin.cpp
@@ -1,4 +1,5 @@
#include "gitplugin.hpp"
+#include "../../appconfig.hpp"
#include "../../settingspage.hpp"
#include "gitbranchmodel.hpp"
#include "githistorymodel.hpp"
@@ -22,6 +23,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -628,6 +630,28 @@ void GitPlugin::updateStatusBarSync() {
mStatusButton->invalidateDraw();
}
+void GitPlugin::styleCommitFilesStatus( UITextView* status ) {
+ if ( !status )
+ return;
+ status->setUsingCustomStyling( true );
+ if ( !mCommitStatusCustomTokenizer.has_value() ) {
+ std::vector patterns;
+ patterns.emplace_back( SyntaxPattern( { ".*%((%d+)%)%s+(%+%d+)%s+(%-%d+)" },
+ { "normal", "warning", "keyword", "type" } ) );
+ SyntaxDefinition syntaxDef( "git_commit_files_status", {}, std::move( patterns ) );
+ SyntaxColorScheme scheme( "git_commit_files_status",
+ { { "normal"_sst, { getVarColor( "--font" ) } },
+ { "warning"_sst, { getVarColor( "--theme-warning" ) } },
+ { "keyword"_sst, { getVarColor( "--theme-success" ) } },
+ { "type"_sst, { getVarColor( "--theme-error" ) } } },
+ {} );
+ mCommitStatusCustomTokenizer = { std::move( syntaxDef ), std::move( scheme ) };
+ }
+ SyntaxTokenizer::tokenizeText( mCommitStatusCustomTokenizer->def,
+ mCommitStatusCustomTokenizer->scheme, status->getTextCache() );
+ status->invalidateDraw();
+}
+
void GitPlugin::updateStatus( bool force ) {
if ( !mGit || !mGitFound )
return;
@@ -793,6 +817,9 @@ PluginRequestHandle GitPlugin::processMessage( const PluginMessage& msg ) {
}
case ecode::PluginMessageType::UIThemeReloaded: {
mStatusCustomTokenizer.reset();
+ mCommitStatusCustomTokenizer.reset();
+ styleCommitFilesStatus( mCommitDetails.status );
+ styleCommitFilesStatus( mDetachedHistory.details.status );
updateUINow( true );
break;
}
@@ -2245,6 +2272,7 @@ void GitPlugin::onRegister( UICodeEditor* editor ) {
doc.setCommand( "git-push", [this] { push( projectPath() ); } );
doc.setCommand( "git-fetch", [this] { fetch( projectPath() ); } );
doc.setCommand( "git-commit", [this] { commit( projectPath() ); } );
+ doc.setCommand( "git-show-history", [this] { showGitHistory(); } );
}
void GitPlugin::onUnregister( UICodeEditor* editor ) {
@@ -2373,6 +2401,7 @@ void GitPlugin::updateHistoryRefs( const std::shared_ptr& model
mHistoryRefDropDown->getListView()->getSelection().set(
mHistoryRefModel->index( selected, 0 ) );
mUpdatingHistoryRefs = false;
+ updateDetachedHistoryTitle();
if ( revisionChanged )
invalidateHistory();
}
@@ -2397,6 +2426,8 @@ void GitPlugin::reloadHistory() {
mHistoryTree->setColumnsVisible( { GitHistoryModel::Subject } );
}
mHistoryTree->clearViewMetadata();
+ if ( mDetachedHistory.tree )
+ mDetachedHistory.tree->clearViewMetadata();
mHistoryModel->setRootLoading();
Git::HistoryQuery query;
query.revision = mHistoryRevision;
@@ -2416,6 +2447,7 @@ void GitPlugin::reloadHistory() {
plugin->mHistoryModel->setRootPage( std::move( page ), query );
else
plugin->mHistoryModel->setRootError( std::move( page.result ) );
+ plugin->focusDetachedHistory();
} );
},
[this]( auto ) { --mRunningHistoryRequests; } );
@@ -2460,6 +2492,8 @@ void GitPlugin::loadHistoryPage( GitHistoryModel::Node* node, Git::HistoryQuery
else
plugin->mHistoryModel->setChildrenPage( node, std::move( page ), query );
plugin->mHistoryTree->recalculateColumnsWidth();
+ if ( plugin->mDetachedHistory.tree )
+ plugin->mDetachedHistory.tree->recalculateColumnsWidth();
} );
},
[this]( auto ) { --mRunningHistoryRequests; } );
@@ -2491,212 +2525,368 @@ void GitPlugin::activateHistoryIndex( const ModelIndex& index, bool expand ) {
}
}
+void GitPlugin::openHistoryMenu( const ModelIndex& index ) {
+ if ( !mHistoryModel )
+ return;
+ const auto* node = mHistoryModel->node( index );
+ if ( !node || node->type != GitHistoryModel::NodeType::Commit )
+ return;
+ UIPopUpMenu* menu = UIPopUpMenu::New();
+ menuAdd( menu, "git-show-history", i18n( "git_show_in_history", "Show in Git History" ),
+ "history" );
+ const Git::Commit commit = node->commit;
+ menu->on( Event::OnItemClicked, [this, commit]( const Event* event ) {
+ if ( event->getNode()->asType()->getId() == "git-show-history" )
+ showGitHistory( &commit );
+ } );
+ menu->showOverMouseCursor();
+}
+
+std::string GitPlugin::detachedHistoryTitle() {
+ String state = String::fromUtf8( mHistoryRevision );
+ if ( mHistoryRefDropDown && mHistoryRefModel ) {
+ const ModelIndex selected = mHistoryRefDropDown->getListView()->getSelection().first();
+ if ( selected.isValid() ) {
+ Variant display = mHistoryRefModel->data( selected );
+ if ( display.is( Variant::Type::StringPtr ) )
+ state = display.asStringPtr();
+ else if ( display.isValid() )
+ state = display.toString();
+ }
+ }
+ return String::format( i18n( "git_commit_history_title", "Git Commit History - %s" ).toUtf8(),
+ state.toUtf8() );
+}
+
+void GitPlugin::updateDetachedHistoryTitle() {
+ if ( !mDetachedHistory.view )
+ return;
+ auto tabs = mManager->getSplitter()->getTabFromOwnedWidgetId( mDetachedHistory.view->getId() );
+ if ( !tabs.empty() )
+ tabs.front().first->setText( detachedHistoryTitle() );
+}
+
+void GitPlugin::showGitHistory( const Git::Commit* commit ) {
+ if ( !mHistoryTree )
+ return;
+ ensureHistoryLoaded();
+ if ( commit )
+ mDetachedHistory.focusHash = commit->hash;
+ else
+ mDetachedHistory.focusHash.clear();
+
+ if ( !mDetachedHistory.view ||
+ !mManager->getSplitter()->ownedWidgetExists( mDetachedHistory.view ) ) {
+ mDetachedHistory.view = getUISceneNode()
+ ->loadLayoutFromString( R"xml(
+
+
+
+
+
+ )xml" )
+ ->asType();
+ mDetachedHistory.view->setAlwaysShowSplitter( false );
+ mDetachedHistory.view->setHideSplitterOnEdge( true );
+ mDetachedHistory.view->setSplitPartition( StyleSheetLength( "100%" ) );
+ mDetachedHistory.tree =
+ mDetachedHistory.view->find( "git_history_detached_tree" );
+ mDetachedHistory.tree->setModel( mHistoryModel );
+ mDetachedHistory.tree->setHeadersVisible( true );
+ mDetachedHistory.tree->setRowHeight( PixelDensity::dpToPx( 24 ) );
+ mDetachedHistory.tree->setMainColumn( GitHistoryModel::Subject );
+ mDetachedHistory.tree->setFitAllColumnsToWidget( true );
+ mDetachedHistory.tree->setColumnWidthMode(
+ UIAbstractTableView::ColumnWidthMode::Percentage );
+ mDetachedHistory.tree->setColumnsWidthPercentage( { 0.55f, 0.15f, 0.2f, 0.1f } );
+ const std::string savedColumns = getPluginContext()->getConfig().iniState.getValue(
+ "git", "commit_history_columns", "" );
+ if ( !savedColumns.empty() ) {
+ auto columns = nlohmann::json::parse( savedColumns, nullptr, false );
+ if ( !columns.is_discarded() )
+ mDetachedHistory.tree->unserializeColumnWidths( columns );
+ }
+ mDetachedHistory.tree->setIndentWidth( PixelDensity::dpToPx( 16 ) );
+ mDetachedHistory.tree->setSetupCellCb( []( UITableCell* cell ) {
+ cell->on( Event::OnTooltipCreated, []( const Event* event ) {
+ auto* tooltip = event->getNode()->asType()->getTooltip();
+ tooltip->setMaxWidthEq( "60%" );
+ tooltip->setWordWrap( true );
+ tooltip->setHorizontalAlign( UI_HALIGN_LEFT );
+ } );
+ } );
+ mDetachedHistory.tree->on( Event::OnModelEvent, [this]( const Event* event ) {
+ const auto* modelEvent = static_cast( event );
+ if ( modelEvent->getModelEventType() == ModelEventType::OpenTree )
+ activateHistoryIndex( modelEvent->getModelIndex(), true );
+ else if ( modelEvent->getModelEventType() == ModelEventType::Open )
+ activateHistoryIndex( modelEvent->getModelIndex(), false );
+ else if ( modelEvent->getModelEventType() == ModelEventType::OpenMenu )
+ openHistoryMenu( modelEvent->getModelIndex() );
+ } );
+ mDetachedHistory.tree->setOnSelection( [this]( const ModelIndex& index ) {
+ if ( const auto* node = mHistoryModel ? mHistoryModel->node( index ) : nullptr;
+ node && node->type == GitHistoryModel::NodeType::Commit )
+ openDetachedCommitDetails( node->commit );
+ } );
+ auto* view = mDetachedHistory.view;
+ mDetachedHistory.closeConnection =
+ view->connect( Event::OnClose, [this, view]( const Event* ) {
+ if ( mDetachedHistory.view != view )
+ return;
+ getPluginContext()->getConfig().iniState.setValue(
+ "git", "commit_history_columns",
+ mDetachedHistory.tree->serializeColumnWidths().dump() );
+ mDetachedHistory.reset();
+ } );
+ auto* tab = mManager->getSplitter()
+ ->createWidget( mDetachedHistory.view, detachedHistoryTitle(), true )
+ .first;
+ if ( tab )
+ tab->setIcon( iconDrawable( "history", 12 ) );
+ } else {
+ auto tabs =
+ mManager->getSplitter()->getTabFromOwnedWidgetId( mDetachedHistory.view->getId() );
+ if ( !tabs.empty() )
+ tabs.front().second->setTabSelected( tabs.front().first );
+ }
+
+ focusDetachedHistory();
+}
+
+void GitPlugin::focusDetachedHistory() {
+ if ( !mDetachedHistory.tree || !mHistoryModel || !mHistoryLoaded )
+ return;
+ ModelIndex index;
+ if ( mDetachedHistory.focusHash.empty() )
+ index = mHistoryModel->index( 0, GitHistoryModel::Subject );
+ else
+ index = mHistoryModel->indexForCommit( mDetachedHistory.focusHash );
+ if ( !index.isValid() )
+ return;
+ mDetachedHistory.tree->setSelection( index, true, true );
+ if ( const auto* node = mHistoryModel->node( index );
+ node && node->type == GitHistoryModel::NodeType::Commit &&
+ ( !mDetachedHistory.details.view ||
+ mDetachedHistory.details.commit.hash != node->commit.hash ) )
+ openDetachedCommitDetails( node->commit );
+}
+
+void GitPlugin::openDetachedCommitDetails( const Git::Commit& commit ) {
+ if ( commit.hash.empty() || !mDetachedHistory.view )
+ return;
+ if ( !mDetachedHistory.detailsHost ) {
+ mDetachedHistory.detailsHost = UILinearLayout::NewVertical();
+ mDetachedHistory.detailsHost->setId( "git_history_detached_details" );
+ mDetachedHistory.detailsHost->setLayoutSizePolicy( SizePolicy::MatchParent,
+ SizePolicy::MatchParent );
+ mDetachedHistory.detailsHost->setParent( mDetachedHistory.view );
+ mDetachedHistory.view->setSplitPartition( StyleSheetLength( "55%" ) );
+ }
+ mDetachedHistory.details.openCommitDetails( *this, commit, true );
+}
+
void GitPlugin::openCommitDetails( const Git::Commit& commit ) {
+ mCommitDetails.openCommitDetails( *this, commit, false );
+}
+
+void GitPlugin::CommitDetailsState::openCommitDetails( GitPlugin& plugin, const Git::Commit& commit,
+ bool detached ) {
if ( commit.hash.empty() )
return;
- const std::string repo = repoSelected();
- const Uint64 generation = ++mCommitDetailsGeneration;
- mCommitDetailsCommit = commit;
- mCommitDetailsRepo = repo;
+ const std::string selectedRepo = plugin.repoSelected();
+ const Uint64 requestGeneration = ++generation;
+ this->commit = commit;
+ repo = selectedRepo;
- const bool newView =
- !mCommitDetailsView || !mManager->getSplitter()->ownedWidgetExists( mCommitDetailsView );
- if ( newView ) {
- mCommitDetailsView = getUISceneNode()->loadLayoutFromString( R"xml(
+ const bool createView = !view;
+ if ( createView ) {
+ view = plugin.getUISceneNode()->loadLayoutFromString( R"xml(
-
-
-
-
-
+ margin-left="8dp" visible="false" class="git_commit_btn" />
+
+
+
+
-
-
+
+
+
+ icon="icon(collapse-all, 12dp)" class="git_commit_btn" />
+ margin-left="4dp" class="git_commit_btn" />
-
)xml" );
- mCommitDetailsSubject = mCommitDetailsView->find( "git_commit_subject" );
- mCommitDetailsMetadata = mCommitDetailsView->find( "git_commit_metadata" );
- mCommitDetailsParents = mCommitDetailsView->find( "git_commit_parents" );
- mCommitDetailsMessage = mCommitDetailsView->find( "git_commit_message" );
- mCommitDetailsStatus = mCommitDetailsView->find( "git_commit_files_status" );
- mCommitDetailsMessageToggle =
- mCommitDetailsView->find( "git_commit_message_toggle" );
- mCommitDetailsFilesToggle =
- mCommitDetailsView->find( "git_commit_files_toggle" );
- mCommitDetailsModeToggle =
- mCommitDetailsView->find( "git_commit_mode_toggle" );
- mCommitDetailsGitHub = mCommitDetailsView->find( "git_commit_github" );
- mCommitDetailsDiffContainer = mCommitDetailsView->find( "git_commit_diff" );
- mCommitDetailsMessageToggle->onClick( [this]( const Event* ) {
- mCommitDetailsMessageExpanded = !mCommitDetailsMessageExpanded;
- mCommitDetailsMessage->setVisible( mCommitDetailsMessageExpanded );
- mCommitDetailsMessageToggle->setText(
- mCommitDetailsMessageExpanded
- ? i18n( "git_collapse_commit_description", "Collapse Commit Description" )
- : i18n( "git_expand_commit_description", "Expand Commit Description" ) );
+ if ( detached )
+ view->setParent( plugin.mDetachedHistory.detailsHost );
+ auto* state = this;
+ auto* owner = &plugin;
+ view->bind( "git_commit_subject", subject );
+ view->bind( "git_commit_author", author );
+ view->bind( "git_commit_date_email", dateEmail );
+ view->bind( "git_commit_message", message );
+ view->bind( "git_commit_files_status", status );
+ view->bind( "git_commit_message_toggle", messageToggle );
+ view->bind( "git_commit_files_toggle", filesToggle );
+ view->bind( "git_commit_mode_toggle", modeToggle );
+ view->bind( "git_commit_github", gitHub );
+ view->bind( "git_commit_diff", diffContainer );
+ messageToggle->onClick( [owner, state]( const Event* ) {
+ state->messageExpanded = !state->messageExpanded;
+ state->message->setVisible( state->messageExpanded );
+ state->messageToggle->setText(
+ state->messageExpanded
+ ? owner->i18n( "git_collapse_commit_description",
+ "Collapse Commit Description" )
+ : owner->i18n( "git_expand_commit_description", "Expand Commit Description" ) );
} );
- mCommitDetailsFilesToggle->onClick( [this]( const Event* ) {
- mCommitDetailsFilesCollapsed = !mCommitDetailsFilesCollapsed;
- UIDiffView::setMultiFileCollapsed( mCommitDetailsDiff, mCommitDetailsFilesCollapsed );
- mCommitDetailsFilesToggle->setTooltipText(
- mCommitDetailsFilesCollapsed
- ? i18n( "git_expand_all_files", "Expand All Files" )
- : i18n( "git_collapse_all_files", "Collapse All Files" ) );
+ filesToggle->onClick( [owner, state]( const Event* ) {
+ state->filesCollapsed = !state->filesCollapsed;
+ UIDiffView::setMultiFileCollapsed( state->diff, state->filesCollapsed );
+ state->filesToggle->setTooltipText(
+ state->filesCollapsed
+ ? owner->i18n( "git_expand_all_files", "Expand All Files" )
+ : owner->i18n( "git_collapse_all_files", "Collapse All Files" ) );
if ( auto* icon =
- findIcon( mCommitDetailsFilesCollapsed ? "expand-all" : "collapse-all" ) )
- mCommitDetailsFilesToggle->setIcon(
- icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
+ owner->findIcon( state->filesCollapsed ? "expand-all" : "collapse-all" ) )
+ state->filesToggle->setIcon( icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
} );
- mCommitDetailsModeToggle->onClick( [this]( const Event* ) {
- mCommitDetailsViewMode = mCommitDetailsViewMode == UIDiffView::ViewMode::Unified
- ? UIDiffView::ViewMode::SideBySide
- : UIDiffView::ViewMode::Unified;
- UIDiffView::setMultiFileViewMode( mCommitDetailsDiff, mCommitDetailsViewMode );
- mCommitDetailsModeToggle->setText( mCommitDetailsViewMode ==
- UIDiffView::ViewMode::Unified
- ? i18n( "git_split_diff", "Split" )
- : i18n( "git_unified_diff", "Unified" ) );
- if ( auto* icon = findIcon( mCommitDetailsViewMode == UIDiffView::ViewMode::Unified
- ? "split-horizontal"
- : "layout" ) )
- mCommitDetailsModeToggle->setIcon(
- icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
+ modeToggle->onClick( [owner, state]( const Event* ) {
+ state->viewMode = state->viewMode == UIDiffView::ViewMode::Unified
+ ? UIDiffView::ViewMode::SideBySide
+ : UIDiffView::ViewMode::Unified;
+ UIDiffView::setMultiFileViewMode( state->diff, state->viewMode );
+ state->modeToggle->setText( state->viewMode == UIDiffView::ViewMode::Unified
+ ? owner->i18n( "git_split_diff", "Split" )
+ : owner->i18n( "git_unified_diff", "Unified" ) );
+ state->modeToggle->setTooltipText(
+ state->viewMode == UIDiffView::ViewMode::Unified
+ ? owner->i18n( "git_switch_to_split_diff", "Switch to split diff view" )
+ : owner->i18n( "git_switch_to_unified_diff", "Switch to unified diff view" ) );
+ if ( auto* icon = owner->findIcon( state->viewMode == UIDiffView::ViewMode::Unified
+ ? "split-horizontal"
+ : "layout" ) )
+ state->modeToggle->setIcon( icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
} );
- mCommitDetailsGitHub->onClick( [this]( const Event* ) {
- if ( !mCommitDetailsURL.empty() )
- Engine::instance()->openURI( mCommitDetailsURL );
+ gitHub->onClick( [state]( const Event* ) {
+ if ( !state->url.empty() )
+ Engine::instance()->openURI( state->url );
} );
- mCommitDetailsView->find( "git_commit_sha" )
- ->onClick( [this]( const Event* ) {
- getUISceneNode()->getWindow()->getClipboard()->setText( mCommitDetailsCommit.hash );
- } );
- auto* view = mCommitDetailsView;
- mCommitDetailsCloseConnection =
- view->connect( Event::OnClose, [this, view]( const Event* ) {
- if ( mCommitDetailsView != view )
- return;
- ++mCommitDetailsGeneration;
- mCommitDetailsView = nullptr;
- mCommitDetailsSubject = nullptr;
- mCommitDetailsMetadata = nullptr;
- mCommitDetailsParents = nullptr;
- mCommitDetailsMessage = nullptr;
- mCommitDetailsStatus = nullptr;
- mCommitDetailsMessageToggle = nullptr;
- mCommitDetailsFilesToggle = nullptr;
- mCommitDetailsModeToggle = nullptr;
- mCommitDetailsGitHub = nullptr;
- mCommitDetailsDiffContainer = nullptr;
- mCommitDetailsDiff = nullptr;
- mCommitDetailsMessageBody.clear();
- mCommitDetailsURL.clear();
- mCommitDetailsRepo.clear();
+ view->find( "git_commit_show_history" )
+ ->onClick(
+ [owner, state]( const Event* ) { owner->showGitHistory( &state->commit ); } );
+ view->find( "git_commit_sha" )->onClick( [owner, state]( const Event* ) {
+ owner->getUISceneNode()->getWindow()->getClipboard()->setText( state->commit.hash );
+ } );
+ if ( !detached ) {
+ auto* view = this->view;
+ closeConnection = view->connect( Event::OnClose, [state, view]( const Event* ) {
+ if ( state->view == view )
+ state->reset();
} );
+ }
}
- mCommitDetailsSubject->setText( String::fromUtf8( commit.subject ) );
- String metadata = String::fromUtf8( commit.authorName ) + "\n" +
- Sys::epochToString( commit.commitTime ) + " - " +
- String::fromUtf8( commit.authorEmail );
- mCommitDetailsMetadata->setText( metadata );
- String parents;
- for ( size_t i = 0; i < commit.parents.size(); ++i ) {
- if ( i )
- parents += ", ";
- parents += String::fromUtf8( commit.parents[i] );
- }
- mCommitDetailsMessageBody.clear();
- mCommitDetailsMessageExpanded = false;
- mCommitDetailsMessage->setText( "" );
- mCommitDetailsMessage->setVisible( false );
- mCommitDetailsMessageToggle->setVisible( false );
- mCommitDetailsParents->setVisible( !parents.empty() );
- mCommitDetailsParents->setText( i18n( "git_parents", "Parents" ) + ": " + parents );
- mCommitDetailsStatus->setText(
- i18n( "git_loading_changed_files", "Loading changed files..." ) );
- mCommitDetailsFilesCollapsed = false;
- mCommitDetailsFilesToggle->setTooltipText(
- i18n( "git_collapse_all_files", "Collapse All Files" ) );
- if ( auto* icon = findIcon( "collapse-all" ) )
- mCommitDetailsFilesToggle->setIcon( icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
- mCommitDetailsURL.clear();
- mCommitDetailsGitHub->setVisible( false );
- mCommitDetailsDiff = nullptr;
- mCommitDetailsDiffContainer->closeAllChildren();
- auto* shaButton = mCommitDetailsView->find( "git_commit_sha" );
- shaButton->setText( String::fromUtf8( commit.shortHash ) );
- shaButton->setTooltipText( String::fromUtf8( commit.hash ) );
+ subject->setText( String::fromUtf8( commit.subject ) );
+ author->setText( String::fromUtf8( commit.authorName ) );
+ dateEmail->setText( Sys::epochToString( commit.commitTime ) + " - " +
+ String::fromUtf8( commit.authorEmail ) );
+ messageBody.clear();
+ messageExpanded = false;
+ message->setText( "" );
+ message->setVisible( false );
+ messageToggle->setVisible( false );
+ status->setText( plugin.i18n( "git_loading_changed_files", "Loading changed files..." ) );
+ plugin.styleCommitFilesStatus( status );
+ filesCollapsed = false;
+ filesToggle->setTooltipText( plugin.i18n( "git_collapse_all_files", "Collapse All Files" ) );
+ if ( auto* icon = plugin.findIcon( "collapse-all" ) )
+ filesToggle->setIcon( icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
+ url.clear();
+ gitHub->setVisible( false );
+ diff = nullptr;
+ diffContainer->closeAllChildren();
+ auto* shaButton = view->find( "git_commit_sha" );
+ shaButton->setTooltipText( String::format(
+ plugin.i18n( "git_copy_commit_sha", "Copy Commit SHA\n%s" ).toUtf8(), commit.hash ) );
const std::string tabName = commit.shortHash + " " + commit.subject;
- if ( newView ) {
- mManager->getSplitter()->createWidget( mCommitDetailsView, tabName, true );
- } else {
- auto tabs = mManager->getSplitter()->getTabFromOwnedWidgetId( mCommitDetailsView->getId() );
+ if ( !detached && !plugin.mManager->getSplitter()->ownedWidgetExists( view ) ) {
+ auto tab = plugin.mManager->getSplitter()->createWidget( view, tabName, true ).first;
+ if ( tab )
+ tab->setIcon( plugin.iconDrawable( "git-commit", 12 ) );
+ } else if ( !detached ) {
+ auto tabs = plugin.mManager->getSplitter()->getTabFromOwnedWidgetId( view->getId() );
if ( !tabs.empty() ) {
tabs.front().first->setText( tabName );
tabs.front().second->setTabSelected( tabs.front().first );
}
}
- if ( generation == mCommitDetailsGeneration )
- loadCommitFiles();
+ if ( requestGeneration == generation )
+ loadCommitFiles( plugin, detached );
}
-void GitPlugin::loadCommitFiles() {
- const Uint64 generation = mCommitDetailsGeneration;
- const std::string repo = mCommitDetailsRepo;
- const Git::Commit commit = mCommitDetailsCommit;
- auto git = mGit;
- const auto lifetime = mLifetime.weakHandle();
- runAsyncTask( [git = std::move( git ), lifetime, generation, repo, commit] {
+void GitPlugin::CommitDetailsState::loadCommitFiles( GitPlugin& plugin, bool detached ) {
+ const Uint64 generation = this->generation;
+ const std::string repo = this->repo;
+ const Git::Commit commit = this->commit;
+ auto git = plugin.mGit;
+ const auto lifetime = plugin.mLifetime.weakHandle();
+ plugin.runAsyncTask( [git = std::move( git ), lifetime, generation, repo, commit, detached] {
auto result = git->commitFiles( commit, repo );
- lifetime.run( [generation, repo, commit,
+ lifetime.run( [generation, repo, commit, detached,
result = std::move( result )]( GitPlugin* plugin ) mutable {
- if ( plugin->mShuttingDown || generation != plugin->mCommitDetailsGeneration ||
- repo != plugin->mCommitDetailsRepo || repo != plugin->repoSelected() ||
- commit.hash != plugin->mCommitDetailsCommit.hash || !plugin->mCommitDetailsView ||
- !plugin->mManager->getSplitter()->ownedWidgetExists( plugin->mCommitDetailsView ) )
+ auto& details = detached ? plugin->mDetachedHistory.details : plugin->mCommitDetails;
+ if ( plugin->mShuttingDown || generation != details.generation ||
+ repo != details.repo || repo != plugin->repoSelected() ||
+ commit.hash != details.commit.hash || !details.view ||
+ ( detached
+ ? !plugin->mDetachedHistory.view
+ : !plugin->mManager->getSplitter()->ownedWidgetExists( details.view ) ) )
return;
if ( result.fail() ) {
- plugin->mCommitDetailsStatus->setText(
+ details.status->setText(
plugin->i18n( "git_changed_files_error", "Could not load changed files" ) +
( result.result.empty() ? "" : ": " + result.result ) );
+ plugin->styleCommitFilesStatus( details.status );
return;
}
std::string message = std::move( result.message );
const size_t subjectEnd = message.find_first_of( "\r\n" );
const std::string_view subject{
message.data(), subjectEnd == std::string::npos ? message.size() : subjectEnd };
- plugin->mCommitDetailsSubject->setText(
+ details.subject->setText(
String::fromUtf8( subject.empty() ? commit.subject : std::string{ subject } ) );
std::string body;
if ( subjectEnd != std::string::npos ) {
@@ -2706,14 +2896,12 @@ void GitPlugin::loadCommitFiles() {
++bodyStart;
body = message.substr( bodyStart );
}
- plugin->mCommitDetailsMessageBody = std::move( body );
- plugin->mCommitDetailsMessage->setText(
- String::fromUtf8( plugin->mCommitDetailsMessageBody ) );
- plugin->mCommitDetailsMessage->setVisible( false );
- plugin->mCommitDetailsMessageExpanded = false;
- plugin->mCommitDetailsMessageToggle->setVisible(
- !plugin->mCommitDetailsMessageBody.empty() );
- plugin->mCommitDetailsMessageToggle->setText(
+ details.messageBody = std::move( body );
+ details.message->setText( String::fromUtf8( details.messageBody ) );
+ details.message->setVisible( false );
+ details.messageExpanded = false;
+ details.messageToggle->setVisible( !details.messageBody.empty() );
+ details.messageToggle->setText(
plugin->i18n( "git_expand_commit_description", "Expand Commit Description" ) );
int totalInserts = 0;
@@ -2723,33 +2911,34 @@ void GitPlugin::loadCommitFiles() {
totalDeletes += file.deletes;
}
if ( result.files.empty() ) {
- plugin->mCommitDetailsStatus->setText(
+ details.status->setText(
plugin->i18n( "git_no_changed_files", "No changed files" ) );
} else {
- plugin->mCommitDetailsStatus->setText( String::format(
+ details.status->setText( String::format(
plugin->i18n( "git_changed_files_summary", "Changed files (%zu) +%d -%d" )
.toUtf8(),
result.files.size(), totalInserts, totalDeletes ) );
}
+ plugin->styleCommitFilesStatus( details.status );
- plugin->mCommitDetailsURL = std::move( result.commitURL );
- plugin->mCommitDetailsGitHub->setVisible( !plugin->mCommitDetailsURL.empty() );
- plugin->mCommitDetailsDiffContainer->closeAllChildren();
- plugin->mCommitDetailsDiff = nullptr;
+ details.url = std::move( result.commitURL );
+ details.gitHub->setVisible( !details.url.empty() );
+ details.diffContainer->closeAllChildren();
+ details.diff = nullptr;
if ( !result.patch.empty() ) {
- plugin->mCommitDetailsDiff = UIDiffView::NewMultiFileDiffViewer(
- result.patch, repo, plugin->mCommitDetailsViewMode );
- plugin->mCommitDetailsDiff->setLayoutSizePolicy( SizePolicy::MatchParent,
- SizePolicy::MatchParent );
- plugin->mCommitDetailsDiff->setParent( plugin->mCommitDetailsDiffContainer );
- for ( auto* diff : UIDiffView::multiFileDiffViews( plugin->mCommitDetailsDiff ) ) {
+ details.diff =
+ UIDiffView::NewMultiFileDiffViewer( result.patch, repo, details.viewMode );
+ details.diff->setLayoutSizePolicy( SizePolicy::MatchParent,
+ SizePolicy::MatchParent );
+ details.diff->setParent( details.diffContainer );
+ for ( auto* diff : UIDiffView::multiFileDiffViews( details.diff ) ) {
if ( const auto* scheme = plugin->getPluginContext()->getCurrentColorScheme() )
diff->setSyntaxColorScheme( *scheme );
}
}
- const bool hasDiff = plugin->mCommitDetailsDiff != nullptr;
- plugin->mCommitDetailsFilesToggle->setVisible( hasDiff );
- plugin->mCommitDetailsModeToggle->setVisible( hasDiff );
+ const bool hasDiff = details.diff != nullptr;
+ details.filesToggle->setVisible( hasDiff );
+ details.modeToggle->setVisible( hasDiff );
} );
} );
}
@@ -2942,6 +3131,34 @@ void GitPlugin::buildSidePanelTab() {
treeview::cell.git_highlight_style_clear > treeview::cell::icon {
foreground-image: none, none;
}
+ #git_commit_details .git_commit_btn {
+ lw: 20dp;
+ lh: 20dp;
+ padding: 0;
+ background-color: var(--list-back);
+ border-color: transparent;
+ }
+ #git_commit_details .git_commit_btn:hover {
+ border-color: var(--primary);
+ }
+ #git_commit_details #git_commit_author {
+ font-size: 11dp;
+ text-stroke-width: 1dp;
+ text-stroke-color: var(--list-back);
+ layout-gravity: center_vertical;
+ }
+ #git_commit_details #git_commit_date_email {
+ color: var(--font-hint);
+ }
+ #git_commit_details #git_commit_subject {
+ font-size: 13dp;
+ text-overflow: ellipsis;
+ word-wrap: true;
+ }
+ #git_commit_details #git_commit_sha {
+ lw: wc;
+ padding: 0dp 4dp;
+ }
@@ -3022,6 +3239,7 @@ void GitPlugin::buildSidePanelTab() {
if ( revision == mHistoryRevision )
return;
mHistoryRevision = revision;
+ updateDetachedHistoryTitle();
invalidateHistory();
} );
updateHistoryRefs(
@@ -3124,8 +3342,13 @@ void GitPlugin::buildSidePanelTab() {
const auto* modelEvent = static_cast( event );
if ( modelEvent->getModelEventType() == ModelEventType::OpenTree )
activateHistoryIndex( modelEvent->getModelIndex(), true );
- else if ( modelEvent->getModelEventType() == ModelEventType::Open )
+ else if ( modelEvent->getModelEventType() == ModelEventType::Open ) {
activateHistoryIndex( modelEvent->getModelIndex(), false );
+ if ( const auto* node = mHistoryModel->node( modelEvent->getModelIndex() );
+ node && node->type == GitHistoryModel::NodeType::Commit )
+ openCommitDetails( node->commit );
+ } else if ( modelEvent->getModelEventType() == ModelEventType::OpenMenu )
+ openHistoryMenu( modelEvent->getModelIndex() );
} );
mHistoryTree->setOnSelection( [this]( const ModelIndex& index ) {
if ( !mHistoryModel )
diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp
index 2511fc538..45814d724 100644
--- a/src/tools/ecode/plugins/git/gitplugin.hpp
+++ b/src/tools/ecode/plugins/git/gitplugin.hpp
@@ -25,6 +25,7 @@ class UIStackWidget;
class UIListBoxItem;
class UIMenu;
class UITextView;
+class UISplitter;
} // namespace EE::UI
namespace ecode {
@@ -167,27 +168,74 @@ class GitPlugin : public PluginBase {
std::string mHistoryRevision{ "HEAD" };
bool mUpdatingHistoryRefs{ false };
bool mHistoryLoaded{ false };
- UIWidget* mCommitDetailsView{ nullptr };
- UITextView* mCommitDetailsSubject{ nullptr };
- UITextView* mCommitDetailsMetadata{ nullptr };
- UITextView* mCommitDetailsMessage{ nullptr };
- UITextView* mCommitDetailsParents{ nullptr };
- UITextView* mCommitDetailsStatus{ nullptr };
- UIPushButton* mCommitDetailsMessageToggle{ nullptr };
- UIPushButton* mCommitDetailsFilesToggle{ nullptr };
- UIPushButton* mCommitDetailsModeToggle{ nullptr };
- UIPushButton* mCommitDetailsGitHub{ nullptr };
- UIWidget* mCommitDetailsDiffContainer{ nullptr };
- UIScrollView* mCommitDetailsDiff{ nullptr };
- std::string mCommitDetailsMessageBody;
- std::string mCommitDetailsURL;
- bool mCommitDetailsMessageExpanded{ false };
- bool mCommitDetailsFilesCollapsed{ false };
- Tools::UIDiffView::ViewMode mCommitDetailsViewMode{ Tools::UIDiffView::ViewMode::Unified };
- Git::Commit mCommitDetailsCommit;
- std::string mCommitDetailsRepo;
- std::atomic mCommitDetailsGeneration{ 0 };
- EventConnection mCommitDetailsCloseConnection;
+ 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 };
+ EventConnection closeConnection;
+ Tools::UIDiffView::ViewMode viewMode{ Tools::UIDiffView::ViewMode::Unified };
+ bool messageExpanded{ false };
+ bool filesCollapsed{ false };
+
+ void openCommitDetails( GitPlugin& plugin, const Git::Commit& commit, bool detached );
+
+ void loadCommitFiles( GitPlugin& plugin, bool detached );
+
+ void reset() {
+ ++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;
+ }
+ };
+ 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 ) };
@@ -217,6 +265,7 @@ class GitPlugin : public PluginBase {
SyntaxColorScheme scheme;
};
std::optional mStatusCustomTokenizer;
+ std::optional mCommitStatusCustomTokenizer;
std::optional mTooltipCustomSyntaxDef;
Uint32 mModelChangedId{ 0 };
Uint32 mModelStylerId{ 0 };
@@ -302,6 +351,8 @@ class GitPlugin : public PluginBase {
void updateStatusBarSync();
+ void styleCommitFilesStatus( UITextView* status );
+
void updateUI();
void updateUINow( bool force = false );
@@ -324,7 +375,17 @@ class GitPlugin : public PluginBase {
void openCommitDetails( const Git::Commit& commit );
- void loadCommitFiles();
+ void showGitHistory( const Git::Commit* commit = nullptr );
+
+ void focusDetachedHistory();
+
+ void openHistoryMenu( const ModelIndex& index );
+
+ void openDetachedCommitDetails( const Git::Commit& commit );
+
+ std::string detachedHistoryTitle();
+
+ void updateDetachedHistoryTitle();
void buildSidePanelTab();