mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-02 11:10:31 +03:00
Improve Git History navigation and commit details
Add a detached Git Commit History tab with a traditional table layout, persistent column widths, commit focusing, and a splitter-based commit details pane. Register the git-show-history command and expose “Show in Git History” from commit details and history context menus. Add History and Git Commit tab icons, localized titles, appropriate tooltips, and ref-aware tab naming. Share the commit-details layout and behavior between standalone commit tabs and detached History views. Move view creation, widget binding, state management, controls, and asynchronous changed-file loading into CommitDetailsState. Display complete commit metadata and messages, GitHub links, copyable commit SHAs, colored changed-file statistics, and multi-file diffs with global expand/collapse and unified/split controls. Improve detached-history lifecycle behavior, selection and focus handling, stale-request protection, tooltip formatting, and column recalculation. Restore “Load older commits” by preserving pagination-node semantics while asynchronous loading is in progress. Fix expandable History rows so merge subjects and metadata render correctly without regressing generic UITreeView cells. Add recursive commit lookup for focusing nested history entries. Add deferred tooltip-created notifications and configure History tooltips only after their widgets exist. Preserve tokenizer-applied UITextView color ranges during layout recalculation when custom styling is enabled, preventing changed-file statistics from losing their colors after splitter resizing. Add the required Codicons and translations, expose UIWidget pseudo-class queries, reuse cached tree-expander drawable sources, and use SmallVector for nested stylesheet property resolution.
This commit is contained in:
@@ -1264,6 +1264,12 @@ Für sichtbare Änderung ecode neu starten.</string>
|
|||||||
<string name="git_history_load_older">Ältere Commits laden</string>
|
<string name="git_history_load_older">Ältere Commits laden</string>
|
||||||
<string name="git_history_empty">Noch keine Commits</string>
|
<string name="git_history_empty">Noch keine Commits</string>
|
||||||
<string name="git_history_no_additional_commits">Keine weiteren Commits</string>
|
<string name="git_history_no_additional_commits">Keine weiteren Commits</string>
|
||||||
|
<string name="git_show_in_history">Im Git-Verlauf anzeigen</string>
|
||||||
|
<string name="git_commit_history_title">Git-Commit-Verlauf – %s</string>
|
||||||
|
<string name="git_commit_sha">Commit-SHA</string>
|
||||||
|
<string name="git_copy_commit_sha">Commit-SHA kopieren %s</string>
|
||||||
|
<string name="git_switch_to_split_diff">Zur geteilten Diff-Ansicht wechseln</string>
|
||||||
|
<string name="git_switch_to_unified_diff">Zur vereinheitlichten Diff-Ansicht wechseln</string>
|
||||||
<string name="git_history_error">Git-Verlauf konnte nicht geladen werden</string>
|
<string name="git_history_error">Git-Verlauf konnte nicht geladen werden</string>
|
||||||
<string name="git_history_retry">Wiederholen</string>
|
<string name="git_history_retry">Wiederholen</string>
|
||||||
<string name="git_local_branch">Lokal</string>
|
<string name="git_local_branch">Lokal</string>
|
||||||
|
|||||||
@@ -1249,6 +1249,12 @@ Restart ecode to see the changes.</string>
|
|||||||
<string name="git_history_load_older">Load older commits</string>
|
<string name="git_history_load_older">Load older commits</string>
|
||||||
<string name="git_history_empty">No commits yet</string>
|
<string name="git_history_empty">No commits yet</string>
|
||||||
<string name="git_history_no_additional_commits">No additional commits</string>
|
<string name="git_history_no_additional_commits">No additional commits</string>
|
||||||
|
<string name="git_show_in_history">Show in Git History</string>
|
||||||
|
<string name="git_commit_history_title">Git Commit History - %s</string>
|
||||||
|
<string name="git_commit_sha">Commit SHA</string>
|
||||||
|
<string name="git_copy_commit_sha">Copy Commit SHA %s</string>
|
||||||
|
<string name="git_switch_to_split_diff">Switch to split diff view</string>
|
||||||
|
<string name="git_switch_to_unified_diff">Switch to unified diff view</string>
|
||||||
<string name="git_history_error">Could not load Git history</string>
|
<string name="git_history_error">Could not load Git history</string>
|
||||||
<string name="git_history_retry">Retry</string>
|
<string name="git_history_retry">Retry</string>
|
||||||
<string name="git_local_branch">Local</string>
|
<string name="git_local_branch">Local</string>
|
||||||
|
|||||||
@@ -1248,6 +1248,12 @@ Redémarrer ecode pour voir les changements.</string>
|
|||||||
<string name="git_history_load_older">Charger les commits précédents</string>
|
<string name="git_history_load_older">Charger les commits précédents</string>
|
||||||
<string name="git_history_empty">Aucun commit pour le moment</string>
|
<string name="git_history_empty">Aucun commit pour le moment</string>
|
||||||
<string name="git_history_no_additional_commits">Aucun commit supplémentaire</string>
|
<string name="git_history_no_additional_commits">Aucun commit supplémentaire</string>
|
||||||
|
<string name="git_show_in_history">Afficher dans l’historique Git</string>
|
||||||
|
<string name="git_commit_history_title">Historique des commits Git – %s</string>
|
||||||
|
<string name="git_commit_sha">SHA du commit</string>
|
||||||
|
<string name="git_copy_commit_sha">Copier le SHA du commit %s</string>
|
||||||
|
<string name="git_switch_to_split_diff">Passer à la vue des différences séparée</string>
|
||||||
|
<string name="git_switch_to_unified_diff">Passer à la vue des différences unifiée</string>
|
||||||
<string name="git_history_error">Impossible de charger l’historique Git</string>
|
<string name="git_history_error">Impossible de charger l’historique Git</string>
|
||||||
<string name="git_history_retry">Réessayer</string>
|
<string name="git_history_retry">Réessayer</string>
|
||||||
<string name="git_local_branch">Locale</string>
|
<string name="git_local_branch">Locale</string>
|
||||||
|
|||||||
@@ -1053,6 +1053,12 @@ file in the directory tree.</string>
|
|||||||
<string name="git_history_load_older">加载更早的提交</string>
|
<string name="git_history_load_older">加载更早的提交</string>
|
||||||
<string name="git_history_empty">尚无提交</string>
|
<string name="git_history_empty">尚无提交</string>
|
||||||
<string name="git_history_no_additional_commits">没有其他提交</string>
|
<string name="git_history_no_additional_commits">没有其他提交</string>
|
||||||
|
<string name="git_show_in_history">在 Git 历史记录中显示</string>
|
||||||
|
<string name="git_commit_history_title">Git 提交历史记录 - %s</string>
|
||||||
|
<string name="git_commit_sha">提交 SHA</string>
|
||||||
|
<string name="git_copy_commit_sha">复制提交 SHA %s</string>
|
||||||
|
<string name="git_switch_to_split_diff">切换到拆分差异视图</string>
|
||||||
|
<string name="git_switch_to_unified_diff">切换到统一差异视图</string>
|
||||||
<string name="git_history_error">无法加载 Git 历史记录</string>
|
<string name="git_history_error">无法加载 Git 历史记录</string>
|
||||||
<string name="git_history_retry">重试</string>
|
<string name="git_history_retry">重试</string>
|
||||||
<string name="git_local_branch">本地</string>
|
<string name="git_local_branch">本地</string>
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class EE_API UIStyle : public UIState {
|
|||||||
/** Lazily allocated for styles that use substitutions. The common, non-reentrant resolution
|
/** Lazily allocated for styles that use substitutions. The common, non-reentrant resolution
|
||||||
* needs no container allocation; nested slots are retained for later reuse. */
|
* needs no container allocation; nested slots are retained for later reuse. */
|
||||||
std::unique_ptr<CSS::StyleSheetProperty> mPropertyResolutionSlot;
|
std::unique_ptr<CSS::StyleSheetProperty> mPropertyResolutionSlot;
|
||||||
std::vector<std::unique_ptr<CSS::StyleSheetProperty>> mNestedPropertyResolutionSlots;
|
SmallVector<std::unique_ptr<CSS::StyleSheetProperty>, 2> mNestedPropertyResolutionSlots;
|
||||||
bool mChangingState;
|
bool mChangingState;
|
||||||
bool mForceReapplyProperties;
|
bool mForceReapplyProperties;
|
||||||
bool mDisableAnimations;
|
bool mDisableAnimations;
|
||||||
|
|||||||
@@ -1342,6 +1342,17 @@ class EE_API UIWidget : public UINode {
|
|||||||
*/
|
*/
|
||||||
bool hasPseudoClass( const std::string& pseudoCls ) const;
|
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.
|
* @brief Checks if the tooltip is enabled for this widget.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -261,6 +261,8 @@ ResourcePtr<UIIconTheme> IconManager::init( const std::string& iconThemeName,
|
|||||||
{ "error", 0xea87 },
|
{ "error", 0xea87 },
|
||||||
{ "search-fuzzy", 0xec0d },
|
{ "search-fuzzy", 0xec0d },
|
||||||
{ "source-control", 0xea68 },
|
{ "source-control", 0xea68 },
|
||||||
|
{ "history", 0xea82 },
|
||||||
|
{ "git-commit", 0xeafc },
|
||||||
{ "repo", 0xea62 },
|
{ "repo", 0xea62 },
|
||||||
{ "repo-pull", 0xeb40 },
|
{ "repo-pull", 0xeb40 },
|
||||||
{ "repo-push", 0xeb41 },
|
{ "repo-push", 0xeb41 },
|
||||||
|
|||||||
@@ -735,6 +735,11 @@ void UITextView::onAlignChange() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UITextView::onSelectionChange() {
|
void UITextView::onSelectionChange() {
|
||||||
|
if ( mUsingCustomStyling ) {
|
||||||
|
invalidateDraw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mTextCache.invalidateColors();
|
mTextCache.invalidateColors();
|
||||||
|
|
||||||
if ( selCurInit() != selCurEnd() ) {
|
if ( selCurInit() != selCurEnd() ) {
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ UIWidget* UITreeView::updateCell( const Vector2<Int64>& posIndex, const ModelInd
|
|||||||
if ( hasChildren ) {
|
if ( hasChildren ) {
|
||||||
UIIcon* icon = getIndexMetadata( index ).open ? mExpandIcon : mContractIcon;
|
UIIcon* icon = getIndexMetadata( index ).open ? mExpandIcon : mContractIcon;
|
||||||
DrawablePtr drawable =
|
DrawablePtr drawable =
|
||||||
icon ? icon->createDrawable( mExpanderIconSize ) : DrawablePtr{};
|
icon ? icon->getSource( mExpanderIconSize ) : DrawablePtr{};
|
||||||
|
|
||||||
if ( drawable == nullptr ) {
|
if ( drawable == nullptr ) {
|
||||||
image->setVisible( false );
|
image->setVisible( false );
|
||||||
|
|||||||
@@ -1333,6 +1333,10 @@ bool UIWidget::hasPseudoClass( const std::string& pseudoCls ) const {
|
|||||||
return ( mPseudoClasses & StyleSheetSelectorRule::toPseudoClass( pseudoCls ) ) != 0;
|
return ( mPseudoClasses & StyleSheetSelectorRule::toPseudoClass( pseudoCls ) ) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UIWidget::hasPseudoClass( StyleSheetSelectorRule::PseudoClasses pseudoCls ) const {
|
||||||
|
return ( mPseudoClasses & static_cast<Uint32>( pseudoCls ) ) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool UIWidget::isTooltipEnabled() const {
|
bool UIWidget::isTooltipEnabled() const {
|
||||||
return ( mFlags & UI_TOOLTIP_ENABLED ) != 0;
|
return ( mFlags & UI_TOOLTIP_ENABLED ) != 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "gitplugin.hpp"
|
#include "gitplugin.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <eepp/system/sys.hpp>
|
#include <eepp/system/sys.hpp>
|
||||||
|
#include <functional>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|
||||||
namespace ecode {
|
namespace ecode {
|
||||||
@@ -49,6 +50,19 @@ ModelIndex GitHistoryModel::indexForNode( const Node* item, int column ) const {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModelIndex GitHistoryModel::indexForCommit( std::string_view hash, int column ) const {
|
||||||
|
std::function<const Node*( const Nodes& )> 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 {
|
ModelIndex GitHistoryModel::parentIndex( const ModelIndex& index ) const {
|
||||||
Node* item = node( index );
|
Node* item = node( index );
|
||||||
return item ? indexForNode( item->parent ) : ModelIndex{};
|
return item ? indexForNode( item->parent ) : ModelIndex{};
|
||||||
@@ -241,7 +255,8 @@ void GitHistoryModel::setPageLoading( Node* item ) {
|
|||||||
if ( !item || item->childrenLoading )
|
if ( !item || item->childrenLoading )
|
||||||
return;
|
return;
|
||||||
item->childrenLoading = true;
|
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->message = mPlugin->i18n( "git_history_loading", "Loading..." );
|
||||||
item->error.clear();
|
item->error.clear();
|
||||||
invalidate( Model::DontInvalidateIndexes );
|
invalidate( Model::DontInvalidateIndexes );
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class GitPlugin;
|
|||||||
class GitHistoryModel : public Model {
|
class GitHistoryModel : public Model {
|
||||||
public:
|
public:
|
||||||
enum class NodeType : uint8_t { Commit, LoadMore, Loading, Error, Empty };
|
enum class NodeType : uint8_t { Commit, LoadMore, Loading, Error, Empty };
|
||||||
enum Column { Subject, Author, Date, Hash };
|
enum Column { Subject, Date, Author, Hash };
|
||||||
struct Node {
|
struct Node {
|
||||||
Git::Commit commit;
|
Git::Commit commit;
|
||||||
Git::HistoryQuery query;
|
Git::HistoryQuery query;
|
||||||
@@ -43,7 +43,7 @@ class GitHistoryModel : public Model {
|
|||||||
|
|
||||||
size_t rowCount( const ModelIndex& index = {} ) const;
|
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;
|
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 indexForNode( const Node* node, int column = 0 ) const;
|
||||||
|
|
||||||
|
ModelIndex indexForCommit( std::string_view hash, int column = Subject ) const;
|
||||||
|
|
||||||
void setRootLoading();
|
void setRootLoading();
|
||||||
|
|
||||||
void setRootPage( Git::HistoryPage page, const Git::HistoryQuery& query );
|
void setRootPage( Git::HistoryPage page, const Git::HistoryQuery& query );
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ void GitHistoryTreeViewCell::draw() {
|
|||||||
if ( mMetadataText.getString().empty() )
|
if ( mMetadataText.getString().empty() )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const bool selected = getParent() && ( getParent()->asType<UIWidget>()->getStyleState() &
|
const bool selected = getParent() && ( getParent()->asType<UIWidget>()->hasPseudoClass(
|
||||||
UIState::StateFlagSelected );
|
StyleSheetSelectorRule::PseudoClasses::Selected ) );
|
||||||
|
|
||||||
mMetadataText.setFillColor( selected ? mTextBox->getFontColor() : mHintColor );
|
mMetadataText.setFillColor( selected ? mTextBox->getFontColor() : mHintColor );
|
||||||
mMetadataText.draw( std::floor( mScreenPos.x + mTextBox->getPixelsPosition().x ),
|
mMetadataText.draw( std::floor( mScreenPos.x + mTextBox->getPixelsPosition().x ),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "gitplugin.hpp"
|
#include "gitplugin.hpp"
|
||||||
|
#include "../../appconfig.hpp"
|
||||||
#include "../../settingspage.hpp"
|
#include "../../settingspage.hpp"
|
||||||
#include "gitbranchmodel.hpp"
|
#include "gitbranchmodel.hpp"
|
||||||
#include "githistorymodel.hpp"
|
#include "githistorymodel.hpp"
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
#include <eepp/ui/uipopupmenu.hpp>
|
#include <eepp/ui/uipopupmenu.hpp>
|
||||||
#include <eepp/ui/uiradiobutton.hpp>
|
#include <eepp/ui/uiradiobutton.hpp>
|
||||||
#include <eepp/ui/uiscrollview.hpp>
|
#include <eepp/ui/uiscrollview.hpp>
|
||||||
|
#include <eepp/ui/uisplitter.hpp>
|
||||||
#include <eepp/ui/uistackwidget.hpp>
|
#include <eepp/ui/uistackwidget.hpp>
|
||||||
#include <eepp/ui/uistyle.hpp>
|
#include <eepp/ui/uistyle.hpp>
|
||||||
#include <eepp/ui/uitextedit.hpp>
|
#include <eepp/ui/uitextedit.hpp>
|
||||||
@@ -628,6 +630,28 @@ void GitPlugin::updateStatusBarSync() {
|
|||||||
mStatusButton->invalidateDraw();
|
mStatusButton->invalidateDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GitPlugin::styleCommitFilesStatus( UITextView* status ) {
|
||||||
|
if ( !status )
|
||||||
|
return;
|
||||||
|
status->setUsingCustomStyling( true );
|
||||||
|
if ( !mCommitStatusCustomTokenizer.has_value() ) {
|
||||||
|
std::vector<SyntaxPattern> 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 ) {
|
void GitPlugin::updateStatus( bool force ) {
|
||||||
if ( !mGit || !mGitFound )
|
if ( !mGit || !mGitFound )
|
||||||
return;
|
return;
|
||||||
@@ -793,6 +817,9 @@ PluginRequestHandle GitPlugin::processMessage( const PluginMessage& msg ) {
|
|||||||
}
|
}
|
||||||
case ecode::PluginMessageType::UIThemeReloaded: {
|
case ecode::PluginMessageType::UIThemeReloaded: {
|
||||||
mStatusCustomTokenizer.reset();
|
mStatusCustomTokenizer.reset();
|
||||||
|
mCommitStatusCustomTokenizer.reset();
|
||||||
|
styleCommitFilesStatus( mCommitDetails.status );
|
||||||
|
styleCommitFilesStatus( mDetachedHistory.details.status );
|
||||||
updateUINow( true );
|
updateUINow( true );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2245,6 +2272,7 @@ void GitPlugin::onRegister( UICodeEditor* editor ) {
|
|||||||
doc.setCommand( "git-push", [this] { push( projectPath() ); } );
|
doc.setCommand( "git-push", [this] { push( projectPath() ); } );
|
||||||
doc.setCommand( "git-fetch", [this] { fetch( projectPath() ); } );
|
doc.setCommand( "git-fetch", [this] { fetch( projectPath() ); } );
|
||||||
doc.setCommand( "git-commit", [this] { commit( projectPath() ); } );
|
doc.setCommand( "git-commit", [this] { commit( projectPath() ); } );
|
||||||
|
doc.setCommand( "git-show-history", [this] { showGitHistory(); } );
|
||||||
}
|
}
|
||||||
|
|
||||||
void GitPlugin::onUnregister( UICodeEditor* editor ) {
|
void GitPlugin::onUnregister( UICodeEditor* editor ) {
|
||||||
@@ -2373,6 +2401,7 @@ void GitPlugin::updateHistoryRefs( const std::shared_ptr<GitBranchModel>& model
|
|||||||
mHistoryRefDropDown->getListView()->getSelection().set(
|
mHistoryRefDropDown->getListView()->getSelection().set(
|
||||||
mHistoryRefModel->index( selected, 0 ) );
|
mHistoryRefModel->index( selected, 0 ) );
|
||||||
mUpdatingHistoryRefs = false;
|
mUpdatingHistoryRefs = false;
|
||||||
|
updateDetachedHistoryTitle();
|
||||||
if ( revisionChanged )
|
if ( revisionChanged )
|
||||||
invalidateHistory();
|
invalidateHistory();
|
||||||
}
|
}
|
||||||
@@ -2397,6 +2426,8 @@ void GitPlugin::reloadHistory() {
|
|||||||
mHistoryTree->setColumnsVisible( { GitHistoryModel::Subject } );
|
mHistoryTree->setColumnsVisible( { GitHistoryModel::Subject } );
|
||||||
}
|
}
|
||||||
mHistoryTree->clearViewMetadata();
|
mHistoryTree->clearViewMetadata();
|
||||||
|
if ( mDetachedHistory.tree )
|
||||||
|
mDetachedHistory.tree->clearViewMetadata();
|
||||||
mHistoryModel->setRootLoading();
|
mHistoryModel->setRootLoading();
|
||||||
Git::HistoryQuery query;
|
Git::HistoryQuery query;
|
||||||
query.revision = mHistoryRevision;
|
query.revision = mHistoryRevision;
|
||||||
@@ -2416,6 +2447,7 @@ void GitPlugin::reloadHistory() {
|
|||||||
plugin->mHistoryModel->setRootPage( std::move( page ), query );
|
plugin->mHistoryModel->setRootPage( std::move( page ), query );
|
||||||
else
|
else
|
||||||
plugin->mHistoryModel->setRootError( std::move( page.result ) );
|
plugin->mHistoryModel->setRootError( std::move( page.result ) );
|
||||||
|
plugin->focusDetachedHistory();
|
||||||
} );
|
} );
|
||||||
},
|
},
|
||||||
[this]( auto ) { --mRunningHistoryRequests; } );
|
[this]( auto ) { --mRunningHistoryRequests; } );
|
||||||
@@ -2460,6 +2492,8 @@ void GitPlugin::loadHistoryPage( GitHistoryModel::Node* node, Git::HistoryQuery
|
|||||||
else
|
else
|
||||||
plugin->mHistoryModel->setChildrenPage( node, std::move( page ), query );
|
plugin->mHistoryModel->setChildrenPage( node, std::move( page ), query );
|
||||||
plugin->mHistoryTree->recalculateColumnsWidth();
|
plugin->mHistoryTree->recalculateColumnsWidth();
|
||||||
|
if ( plugin->mDetachedHistory.tree )
|
||||||
|
plugin->mDetachedHistory.tree->recalculateColumnsWidth();
|
||||||
} );
|
} );
|
||||||
},
|
},
|
||||||
[this]( auto ) { --mRunningHistoryRequests; } );
|
[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<UIMenuItem>()->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(
|
||||||
|
<Splitter id="git_history_detached" lw="mp" lh="mp" orientation="horizontal"
|
||||||
|
splitter-partition="55%">
|
||||||
|
<vbox lw="0" lw8="1" lh="mp">
|
||||||
|
<TreeView id="git_history_detached_tree" lw="mp" lh="mp" />
|
||||||
|
</vbox>
|
||||||
|
</Splitter>
|
||||||
|
)xml" )
|
||||||
|
->asType<UISplitter>();
|
||||||
|
mDetachedHistory.view->setAlwaysShowSplitter( false );
|
||||||
|
mDetachedHistory.view->setHideSplitterOnEdge( true );
|
||||||
|
mDetachedHistory.view->setSplitPartition( StyleSheetLength( "100%" ) );
|
||||||
|
mDetachedHistory.tree =
|
||||||
|
mDetachedHistory.view->find<UITreeView>( "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<UIWidget>()->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<const ModelEvent*>( 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 ) {
|
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() )
|
if ( commit.hash.empty() )
|
||||||
return;
|
return;
|
||||||
const std::string repo = repoSelected();
|
const std::string selectedRepo = plugin.repoSelected();
|
||||||
const Uint64 generation = ++mCommitDetailsGeneration;
|
const Uint64 requestGeneration = ++generation;
|
||||||
mCommitDetailsCommit = commit;
|
this->commit = commit;
|
||||||
mCommitDetailsRepo = repo;
|
repo = selectedRepo;
|
||||||
|
|
||||||
const bool newView =
|
const bool createView = !view;
|
||||||
!mCommitDetailsView || !mManager->getSplitter()->ownedWidgetExists( mCommitDetailsView );
|
if ( createView ) {
|
||||||
if ( newView ) {
|
view = plugin.getUISceneNode()->loadLayoutFromString( R"xml(
|
||||||
mCommitDetailsView = getUISceneNode()->loadLayoutFromString( R"xml(
|
|
||||||
<vbox id="git_commit_details" lw="mp" lh="mp">
|
<vbox id="git_commit_details" lw="mp" lh="mp">
|
||||||
<vbox lw="mp" lh="wc" padding="12dp">
|
<vbox lw="mp" lh="wc" padding="12dp">
|
||||||
<hbox lw="mp" lh="wc">
|
<hbox lw="mp" lh="wc">
|
||||||
<TextView id="git_commit_metadata" lw="0" lw8="1" lh="wc"
|
<TextView id="git_commit_author" lw="wc" lh="wc" word-wrap="true"
|
||||||
word-wrap="true" focusable="false" />
|
|
||||||
<PushButton id="git_commit_sha" text="Commit SHA"
|
|
||||||
text-as-fallback="true" margin-left="8dp" />
|
|
||||||
</hbox>
|
|
||||||
<hbox lw="mp" lh="wc" margin-top="8dp">
|
|
||||||
<TextView id="git_commit_subject" lw="0" lw8="1" lh="wc"
|
|
||||||
font-size="14dp" word-wrap="false" text-overflow="ellipsis"
|
|
||||||
focusable="false" />
|
focusable="false" />
|
||||||
<PushButton id="git_commit_message_toggle"
|
<PushButton id="git_commit_message_toggle"
|
||||||
text="@string(git_expand_commit_description, Expand Commit Description)"
|
text="@string(git_expand_commit_description, Expand Commit Description)"
|
||||||
icon="icon(unfold, 12dp)" text-as-fallback="true"
|
icon="icon(unfold, 12dp)" text-as-fallback="true"
|
||||||
margin-left="8dp" visible="false" />
|
margin-left="8dp" visible="false" class="git_commit_btn" />
|
||||||
|
<widget lw="0" lw8="1" lh="wc" />
|
||||||
|
<PushButton id="git_commit_show_history"
|
||||||
|
tooltip="@string(git_show_in_history, Show in Git History)"
|
||||||
|
icon="icon(history, 12dp)" margin-left="8dp" class="git_commit_btn" />
|
||||||
|
<PushButton id="git_commit_github"
|
||||||
|
text="@string(git_view_on_github, View on GitHub)"
|
||||||
|
tooltip="@string(git_view_on_github, View on GitHub)"
|
||||||
|
icon="icon(github, 12dp)" text-as-fallback="true"
|
||||||
|
margin-left="8dp" visible="false" class="git_commit_btn" />
|
||||||
|
<PushButton id="git_commit_sha"
|
||||||
|
text="@string(git_commit_sha, Commit SHA)" icon="icon(copy, 12dp)"
|
||||||
|
margin-left="8dp" class="git_commit_btn" />
|
||||||
</hbox>
|
</hbox>
|
||||||
<TextView id="git_commit_message" lw="mp" lh="wc" margin-top="8dp"
|
<TextView id="git_commit_date_email" lw="mp" lh="wc" word-wrap="true"
|
||||||
word-wrap="true" focusable="false" visible="false" />
|
focusable="false" />
|
||||||
<TextView id="git_commit_parents" lw="mp" lh="wc" margin-top="4dp"
|
<TextView id="git_commit_subject" lw="mp" lh="wc" margin-top="8dp"
|
||||||
word-wrap="true" focusable="false" />
|
focusable="false" />
|
||||||
|
<TextView id="git_commit_message" lw="mp" lh="wc" word-wrap="true"
|
||||||
|
focusable="false" visible="false" />
|
||||||
</vbox>
|
</vbox>
|
||||||
<hbox lw="mp" lh="wc" padding-left="8dp" padding-right="8dp"
|
<hbox lw="mp" lh="wc" padding-left="8dp" padding-right="8dp"
|
||||||
padding-top="4dp" padding-bottom="4dp">
|
padding-top="4dp" padding-bottom="4dp">
|
||||||
<PushButton id="git_commit_files_toggle"
|
<PushButton id="git_commit_files_toggle"
|
||||||
tooltip="@string(git_collapse_all_files, Collapse All Files)"
|
tooltip="@string(git_collapse_all_files, Collapse All Files)"
|
||||||
icon="icon(collapse-all, 12dp)" />
|
icon="icon(collapse-all, 12dp)" class="git_commit_btn" />
|
||||||
<PushButton id="git_commit_mode_toggle"
|
<PushButton id="git_commit_mode_toggle"
|
||||||
text="@string(git_split_diff, Split)"
|
text="@string(git_split_diff, Split)"
|
||||||
|
tooltip="@string(git_switch_to_split_diff, Switch to split diff view)"
|
||||||
icon="icon(split-horizontal, 12dp)" text-as-fallback="true"
|
icon="icon(split-horizontal, 12dp)" text-as-fallback="true"
|
||||||
margin-left="4dp" />
|
margin-left="4dp" class="git_commit_btn" />
|
||||||
<TextView id="git_commit_files_status" lw="0" lw8="1" lh="wc"
|
<TextView id="git_commit_files_status" lw="0" lw8="1" lh="wc"
|
||||||
margin-left="8dp" layout_gravity="center_vertical" focusable="false" />
|
margin-left="8dp" layout_gravity="center_vertical" focusable="false" />
|
||||||
<PushButton id="git_commit_github"
|
|
||||||
text="@string(git_view_on_github, View on GitHub)"
|
|
||||||
icon="icon(github, 12dp)" text-as-fallback="true"
|
|
||||||
visible="false" />
|
|
||||||
</hbox>
|
</hbox>
|
||||||
<vbox id="git_commit_diff" lw="mp" lh="0" lw8="1" />
|
<vbox id="git_commit_diff" lw="mp" lh="0" lw8="1" />
|
||||||
</vbox>
|
</vbox>
|
||||||
)xml" );
|
)xml" );
|
||||||
mCommitDetailsSubject = mCommitDetailsView->find<UITextView>( "git_commit_subject" );
|
if ( detached )
|
||||||
mCommitDetailsMetadata = mCommitDetailsView->find<UITextView>( "git_commit_metadata" );
|
view->setParent( plugin.mDetachedHistory.detailsHost );
|
||||||
mCommitDetailsParents = mCommitDetailsView->find<UITextView>( "git_commit_parents" );
|
auto* state = this;
|
||||||
mCommitDetailsMessage = mCommitDetailsView->find<UITextView>( "git_commit_message" );
|
auto* owner = &plugin;
|
||||||
mCommitDetailsStatus = mCommitDetailsView->find<UITextView>( "git_commit_files_status" );
|
view->bind( "git_commit_subject", subject );
|
||||||
mCommitDetailsMessageToggle =
|
view->bind( "git_commit_author", author );
|
||||||
mCommitDetailsView->find<UIPushButton>( "git_commit_message_toggle" );
|
view->bind( "git_commit_date_email", dateEmail );
|
||||||
mCommitDetailsFilesToggle =
|
view->bind( "git_commit_message", message );
|
||||||
mCommitDetailsView->find<UIPushButton>( "git_commit_files_toggle" );
|
view->bind( "git_commit_files_status", status );
|
||||||
mCommitDetailsModeToggle =
|
view->bind( "git_commit_message_toggle", messageToggle );
|
||||||
mCommitDetailsView->find<UIPushButton>( "git_commit_mode_toggle" );
|
view->bind( "git_commit_files_toggle", filesToggle );
|
||||||
mCommitDetailsGitHub = mCommitDetailsView->find<UIPushButton>( "git_commit_github" );
|
view->bind( "git_commit_mode_toggle", modeToggle );
|
||||||
mCommitDetailsDiffContainer = mCommitDetailsView->find<UIWidget>( "git_commit_diff" );
|
view->bind( "git_commit_github", gitHub );
|
||||||
mCommitDetailsMessageToggle->onClick( [this]( const Event* ) {
|
view->bind( "git_commit_diff", diffContainer );
|
||||||
mCommitDetailsMessageExpanded = !mCommitDetailsMessageExpanded;
|
messageToggle->onClick( [owner, state]( const Event* ) {
|
||||||
mCommitDetailsMessage->setVisible( mCommitDetailsMessageExpanded );
|
state->messageExpanded = !state->messageExpanded;
|
||||||
mCommitDetailsMessageToggle->setText(
|
state->message->setVisible( state->messageExpanded );
|
||||||
mCommitDetailsMessageExpanded
|
state->messageToggle->setText(
|
||||||
? i18n( "git_collapse_commit_description", "Collapse Commit Description" )
|
state->messageExpanded
|
||||||
: i18n( "git_expand_commit_description", "Expand Commit Description" ) );
|
? owner->i18n( "git_collapse_commit_description",
|
||||||
|
"Collapse Commit Description" )
|
||||||
|
: owner->i18n( "git_expand_commit_description", "Expand Commit Description" ) );
|
||||||
} );
|
} );
|
||||||
mCommitDetailsFilesToggle->onClick( [this]( const Event* ) {
|
filesToggle->onClick( [owner, state]( const Event* ) {
|
||||||
mCommitDetailsFilesCollapsed = !mCommitDetailsFilesCollapsed;
|
state->filesCollapsed = !state->filesCollapsed;
|
||||||
UIDiffView::setMultiFileCollapsed( mCommitDetailsDiff, mCommitDetailsFilesCollapsed );
|
UIDiffView::setMultiFileCollapsed( state->diff, state->filesCollapsed );
|
||||||
mCommitDetailsFilesToggle->setTooltipText(
|
state->filesToggle->setTooltipText(
|
||||||
mCommitDetailsFilesCollapsed
|
state->filesCollapsed
|
||||||
? i18n( "git_expand_all_files", "Expand All Files" )
|
? owner->i18n( "git_expand_all_files", "Expand All Files" )
|
||||||
: i18n( "git_collapse_all_files", "Collapse All Files" ) );
|
: owner->i18n( "git_collapse_all_files", "Collapse All Files" ) );
|
||||||
if ( auto* icon =
|
if ( auto* icon =
|
||||||
findIcon( mCommitDetailsFilesCollapsed ? "expand-all" : "collapse-all" ) )
|
owner->findIcon( state->filesCollapsed ? "expand-all" : "collapse-all" ) )
|
||||||
mCommitDetailsFilesToggle->setIcon(
|
state->filesToggle->setIcon( icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
|
||||||
icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
|
|
||||||
} );
|
} );
|
||||||
mCommitDetailsModeToggle->onClick( [this]( const Event* ) {
|
modeToggle->onClick( [owner, state]( const Event* ) {
|
||||||
mCommitDetailsViewMode = mCommitDetailsViewMode == UIDiffView::ViewMode::Unified
|
state->viewMode = state->viewMode == UIDiffView::ViewMode::Unified
|
||||||
? UIDiffView::ViewMode::SideBySide
|
? UIDiffView::ViewMode::SideBySide
|
||||||
: UIDiffView::ViewMode::Unified;
|
: UIDiffView::ViewMode::Unified;
|
||||||
UIDiffView::setMultiFileViewMode( mCommitDetailsDiff, mCommitDetailsViewMode );
|
UIDiffView::setMultiFileViewMode( state->diff, state->viewMode );
|
||||||
mCommitDetailsModeToggle->setText( mCommitDetailsViewMode ==
|
state->modeToggle->setText( state->viewMode == UIDiffView::ViewMode::Unified
|
||||||
UIDiffView::ViewMode::Unified
|
? owner->i18n( "git_split_diff", "Split" )
|
||||||
? i18n( "git_split_diff", "Split" )
|
: owner->i18n( "git_unified_diff", "Unified" ) );
|
||||||
: i18n( "git_unified_diff", "Unified" ) );
|
state->modeToggle->setTooltipText(
|
||||||
if ( auto* icon = findIcon( mCommitDetailsViewMode == UIDiffView::ViewMode::Unified
|
state->viewMode == UIDiffView::ViewMode::Unified
|
||||||
? "split-horizontal"
|
? owner->i18n( "git_switch_to_split_diff", "Switch to split diff view" )
|
||||||
: "layout" ) )
|
: owner->i18n( "git_switch_to_unified_diff", "Switch to unified diff view" ) );
|
||||||
mCommitDetailsModeToggle->setIcon(
|
if ( auto* icon = owner->findIcon( state->viewMode == UIDiffView::ViewMode::Unified
|
||||||
icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
|
? "split-horizontal"
|
||||||
|
: "layout" ) )
|
||||||
|
state->modeToggle->setIcon( icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
|
||||||
} );
|
} );
|
||||||
mCommitDetailsGitHub->onClick( [this]( const Event* ) {
|
gitHub->onClick( [state]( const Event* ) {
|
||||||
if ( !mCommitDetailsURL.empty() )
|
if ( !state->url.empty() )
|
||||||
Engine::instance()->openURI( mCommitDetailsURL );
|
Engine::instance()->openURI( state->url );
|
||||||
} );
|
} );
|
||||||
mCommitDetailsView->find<UIPushButton>( "git_commit_sha" )
|
view->find<UIPushButton>( "git_commit_show_history" )
|
||||||
->onClick( [this]( const Event* ) {
|
->onClick(
|
||||||
getUISceneNode()->getWindow()->getClipboard()->setText( mCommitDetailsCommit.hash );
|
[owner, state]( const Event* ) { owner->showGitHistory( &state->commit ); } );
|
||||||
} );
|
view->find<UIPushButton>( "git_commit_sha" )->onClick( [owner, state]( const Event* ) {
|
||||||
auto* view = mCommitDetailsView;
|
owner->getUISceneNode()->getWindow()->getClipboard()->setText( state->commit.hash );
|
||||||
mCommitDetailsCloseConnection =
|
} );
|
||||||
view->connect( Event::OnClose, [this, view]( const Event* ) {
|
if ( !detached ) {
|
||||||
if ( mCommitDetailsView != view )
|
auto* view = this->view;
|
||||||
return;
|
closeConnection = view->connect( Event::OnClose, [state, view]( const Event* ) {
|
||||||
++mCommitDetailsGeneration;
|
if ( state->view == view )
|
||||||
mCommitDetailsView = nullptr;
|
state->reset();
|
||||||
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();
|
|
||||||
} );
|
} );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mCommitDetailsSubject->setText( String::fromUtf8( commit.subject ) );
|
subject->setText( String::fromUtf8( commit.subject ) );
|
||||||
String metadata = String::fromUtf8( commit.authorName ) + "\n" +
|
author->setText( String::fromUtf8( commit.authorName ) );
|
||||||
Sys::epochToString( commit.commitTime ) + " - " +
|
dateEmail->setText( Sys::epochToString( commit.commitTime ) + " - " +
|
||||||
String::fromUtf8( commit.authorEmail );
|
String::fromUtf8( commit.authorEmail ) );
|
||||||
mCommitDetailsMetadata->setText( metadata );
|
messageBody.clear();
|
||||||
String parents;
|
messageExpanded = false;
|
||||||
for ( size_t i = 0; i < commit.parents.size(); ++i ) {
|
message->setText( "" );
|
||||||
if ( i )
|
message->setVisible( false );
|
||||||
parents += ", ";
|
messageToggle->setVisible( false );
|
||||||
parents += String::fromUtf8( commit.parents[i] );
|
status->setText( plugin.i18n( "git_loading_changed_files", "Loading changed files..." ) );
|
||||||
}
|
plugin.styleCommitFilesStatus( status );
|
||||||
mCommitDetailsMessageBody.clear();
|
filesCollapsed = false;
|
||||||
mCommitDetailsMessageExpanded = false;
|
filesToggle->setTooltipText( plugin.i18n( "git_collapse_all_files", "Collapse All Files" ) );
|
||||||
mCommitDetailsMessage->setText( "" );
|
if ( auto* icon = plugin.findIcon( "collapse-all" ) )
|
||||||
mCommitDetailsMessage->setVisible( false );
|
filesToggle->setIcon( icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
|
||||||
mCommitDetailsMessageToggle->setVisible( false );
|
url.clear();
|
||||||
mCommitDetailsParents->setVisible( !parents.empty() );
|
gitHub->setVisible( false );
|
||||||
mCommitDetailsParents->setText( i18n( "git_parents", "Parents" ) + ": " + parents );
|
diff = nullptr;
|
||||||
mCommitDetailsStatus->setText(
|
diffContainer->closeAllChildren();
|
||||||
i18n( "git_loading_changed_files", "Loading changed files..." ) );
|
auto* shaButton = view->find<UIPushButton>( "git_commit_sha" );
|
||||||
mCommitDetailsFilesCollapsed = false;
|
shaButton->setTooltipText( String::format(
|
||||||
mCommitDetailsFilesToggle->setTooltipText(
|
plugin.i18n( "git_copy_commit_sha", "Copy Commit SHA\n%s" ).toUtf8(), commit.hash ) );
|
||||||
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<UIPushButton>( "git_commit_sha" );
|
|
||||||
shaButton->setText( String::fromUtf8( commit.shortHash ) );
|
|
||||||
shaButton->setTooltipText( String::fromUtf8( commit.hash ) );
|
|
||||||
|
|
||||||
const std::string tabName = commit.shortHash + " " + commit.subject;
|
const std::string tabName = commit.shortHash + " " + commit.subject;
|
||||||
if ( newView ) {
|
if ( !detached && !plugin.mManager->getSplitter()->ownedWidgetExists( view ) ) {
|
||||||
mManager->getSplitter()->createWidget( mCommitDetailsView, tabName, true );
|
auto tab = plugin.mManager->getSplitter()->createWidget( view, tabName, true ).first;
|
||||||
} else {
|
if ( tab )
|
||||||
auto tabs = mManager->getSplitter()->getTabFromOwnedWidgetId( mCommitDetailsView->getId() );
|
tab->setIcon( plugin.iconDrawable( "git-commit", 12 ) );
|
||||||
|
} else if ( !detached ) {
|
||||||
|
auto tabs = plugin.mManager->getSplitter()->getTabFromOwnedWidgetId( view->getId() );
|
||||||
if ( !tabs.empty() ) {
|
if ( !tabs.empty() ) {
|
||||||
tabs.front().first->setText( tabName );
|
tabs.front().first->setText( tabName );
|
||||||
tabs.front().second->setTabSelected( tabs.front().first );
|
tabs.front().second->setTabSelected( tabs.front().first );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( generation == mCommitDetailsGeneration )
|
if ( requestGeneration == generation )
|
||||||
loadCommitFiles();
|
loadCommitFiles( plugin, detached );
|
||||||
}
|
}
|
||||||
|
|
||||||
void GitPlugin::loadCommitFiles() {
|
void GitPlugin::CommitDetailsState::loadCommitFiles( GitPlugin& plugin, bool detached ) {
|
||||||
const Uint64 generation = mCommitDetailsGeneration;
|
const Uint64 generation = this->generation;
|
||||||
const std::string repo = mCommitDetailsRepo;
|
const std::string repo = this->repo;
|
||||||
const Git::Commit commit = mCommitDetailsCommit;
|
const Git::Commit commit = this->commit;
|
||||||
auto git = mGit;
|
auto git = plugin.mGit;
|
||||||
const auto lifetime = mLifetime.weakHandle();
|
const auto lifetime = plugin.mLifetime.weakHandle();
|
||||||
runAsyncTask( [git = std::move( git ), lifetime, generation, repo, commit] {
|
plugin.runAsyncTask( [git = std::move( git ), lifetime, generation, repo, commit, detached] {
|
||||||
auto result = git->commitFiles( commit, repo );
|
auto result = git->commitFiles( commit, repo );
|
||||||
lifetime.run( [generation, repo, commit,
|
lifetime.run( [generation, repo, commit, detached,
|
||||||
result = std::move( result )]( GitPlugin* plugin ) mutable {
|
result = std::move( result )]( GitPlugin* plugin ) mutable {
|
||||||
if ( plugin->mShuttingDown || generation != plugin->mCommitDetailsGeneration ||
|
auto& details = detached ? plugin->mDetachedHistory.details : plugin->mCommitDetails;
|
||||||
repo != plugin->mCommitDetailsRepo || repo != plugin->repoSelected() ||
|
if ( plugin->mShuttingDown || generation != details.generation ||
|
||||||
commit.hash != plugin->mCommitDetailsCommit.hash || !plugin->mCommitDetailsView ||
|
repo != details.repo || repo != plugin->repoSelected() ||
|
||||||
!plugin->mManager->getSplitter()->ownedWidgetExists( plugin->mCommitDetailsView ) )
|
commit.hash != details.commit.hash || !details.view ||
|
||||||
|
( detached
|
||||||
|
? !plugin->mDetachedHistory.view
|
||||||
|
: !plugin->mManager->getSplitter()->ownedWidgetExists( details.view ) ) )
|
||||||
return;
|
return;
|
||||||
if ( result.fail() ) {
|
if ( result.fail() ) {
|
||||||
plugin->mCommitDetailsStatus->setText(
|
details.status->setText(
|
||||||
plugin->i18n( "git_changed_files_error", "Could not load changed files" ) +
|
plugin->i18n( "git_changed_files_error", "Could not load changed files" ) +
|
||||||
( result.result.empty() ? "" : ": " + result.result ) );
|
( result.result.empty() ? "" : ": " + result.result ) );
|
||||||
|
plugin->styleCommitFilesStatus( details.status );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string message = std::move( result.message );
|
std::string message = std::move( result.message );
|
||||||
const size_t subjectEnd = message.find_first_of( "\r\n" );
|
const size_t subjectEnd = message.find_first_of( "\r\n" );
|
||||||
const std::string_view subject{
|
const std::string_view subject{
|
||||||
message.data(), subjectEnd == std::string::npos ? message.size() : subjectEnd };
|
message.data(), subjectEnd == std::string::npos ? message.size() : subjectEnd };
|
||||||
plugin->mCommitDetailsSubject->setText(
|
details.subject->setText(
|
||||||
String::fromUtf8( subject.empty() ? commit.subject : std::string{ subject } ) );
|
String::fromUtf8( subject.empty() ? commit.subject : std::string{ subject } ) );
|
||||||
std::string body;
|
std::string body;
|
||||||
if ( subjectEnd != std::string::npos ) {
|
if ( subjectEnd != std::string::npos ) {
|
||||||
@@ -2706,14 +2896,12 @@ void GitPlugin::loadCommitFiles() {
|
|||||||
++bodyStart;
|
++bodyStart;
|
||||||
body = message.substr( bodyStart );
|
body = message.substr( bodyStart );
|
||||||
}
|
}
|
||||||
plugin->mCommitDetailsMessageBody = std::move( body );
|
details.messageBody = std::move( body );
|
||||||
plugin->mCommitDetailsMessage->setText(
|
details.message->setText( String::fromUtf8( details.messageBody ) );
|
||||||
String::fromUtf8( plugin->mCommitDetailsMessageBody ) );
|
details.message->setVisible( false );
|
||||||
plugin->mCommitDetailsMessage->setVisible( false );
|
details.messageExpanded = false;
|
||||||
plugin->mCommitDetailsMessageExpanded = false;
|
details.messageToggle->setVisible( !details.messageBody.empty() );
|
||||||
plugin->mCommitDetailsMessageToggle->setVisible(
|
details.messageToggle->setText(
|
||||||
!plugin->mCommitDetailsMessageBody.empty() );
|
|
||||||
plugin->mCommitDetailsMessageToggle->setText(
|
|
||||||
plugin->i18n( "git_expand_commit_description", "Expand Commit Description" ) );
|
plugin->i18n( "git_expand_commit_description", "Expand Commit Description" ) );
|
||||||
|
|
||||||
int totalInserts = 0;
|
int totalInserts = 0;
|
||||||
@@ -2723,33 +2911,34 @@ void GitPlugin::loadCommitFiles() {
|
|||||||
totalDeletes += file.deletes;
|
totalDeletes += file.deletes;
|
||||||
}
|
}
|
||||||
if ( result.files.empty() ) {
|
if ( result.files.empty() ) {
|
||||||
plugin->mCommitDetailsStatus->setText(
|
details.status->setText(
|
||||||
plugin->i18n( "git_no_changed_files", "No changed files" ) );
|
plugin->i18n( "git_no_changed_files", "No changed files" ) );
|
||||||
} else {
|
} else {
|
||||||
plugin->mCommitDetailsStatus->setText( String::format(
|
details.status->setText( String::format(
|
||||||
plugin->i18n( "git_changed_files_summary", "Changed files (%zu) +%d -%d" )
|
plugin->i18n( "git_changed_files_summary", "Changed files (%zu) +%d -%d" )
|
||||||
.toUtf8(),
|
.toUtf8(),
|
||||||
result.files.size(), totalInserts, totalDeletes ) );
|
result.files.size(), totalInserts, totalDeletes ) );
|
||||||
}
|
}
|
||||||
|
plugin->styleCommitFilesStatus( details.status );
|
||||||
|
|
||||||
plugin->mCommitDetailsURL = std::move( result.commitURL );
|
details.url = std::move( result.commitURL );
|
||||||
plugin->mCommitDetailsGitHub->setVisible( !plugin->mCommitDetailsURL.empty() );
|
details.gitHub->setVisible( !details.url.empty() );
|
||||||
plugin->mCommitDetailsDiffContainer->closeAllChildren();
|
details.diffContainer->closeAllChildren();
|
||||||
plugin->mCommitDetailsDiff = nullptr;
|
details.diff = nullptr;
|
||||||
if ( !result.patch.empty() ) {
|
if ( !result.patch.empty() ) {
|
||||||
plugin->mCommitDetailsDiff = UIDiffView::NewMultiFileDiffViewer(
|
details.diff =
|
||||||
result.patch, repo, plugin->mCommitDetailsViewMode );
|
UIDiffView::NewMultiFileDiffViewer( result.patch, repo, details.viewMode );
|
||||||
plugin->mCommitDetailsDiff->setLayoutSizePolicy( SizePolicy::MatchParent,
|
details.diff->setLayoutSizePolicy( SizePolicy::MatchParent,
|
||||||
SizePolicy::MatchParent );
|
SizePolicy::MatchParent );
|
||||||
plugin->mCommitDetailsDiff->setParent( plugin->mCommitDetailsDiffContainer );
|
details.diff->setParent( details.diffContainer );
|
||||||
for ( auto* diff : UIDiffView::multiFileDiffViews( plugin->mCommitDetailsDiff ) ) {
|
for ( auto* diff : UIDiffView::multiFileDiffViews( details.diff ) ) {
|
||||||
if ( const auto* scheme = plugin->getPluginContext()->getCurrentColorScheme() )
|
if ( const auto* scheme = plugin->getPluginContext()->getCurrentColorScheme() )
|
||||||
diff->setSyntaxColorScheme( *scheme );
|
diff->setSyntaxColorScheme( *scheme );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const bool hasDiff = plugin->mCommitDetailsDiff != nullptr;
|
const bool hasDiff = details.diff != nullptr;
|
||||||
plugin->mCommitDetailsFilesToggle->setVisible( hasDiff );
|
details.filesToggle->setVisible( hasDiff );
|
||||||
plugin->mCommitDetailsModeToggle->setVisible( hasDiff );
|
details.modeToggle->setVisible( hasDiff );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
@@ -2942,6 +3131,34 @@ void GitPlugin::buildSidePanelTab() {
|
|||||||
treeview::cell.git_highlight_style_clear > treeview::cell::icon {
|
treeview::cell.git_highlight_style_clear > treeview::cell::icon {
|
||||||
foreground-image: none, none;
|
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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<RelativeLayout id="git_panel" lw="mp" lh="mp">
|
<RelativeLayout id="git_panel" lw="mp" lh="mp">
|
||||||
<vbox id="git_content" lw="mp" lh="mp">
|
<vbox id="git_content" lw="mp" lh="mp">
|
||||||
@@ -3022,6 +3239,7 @@ void GitPlugin::buildSidePanelTab() {
|
|||||||
if ( revision == mHistoryRevision )
|
if ( revision == mHistoryRevision )
|
||||||
return;
|
return;
|
||||||
mHistoryRevision = revision;
|
mHistoryRevision = revision;
|
||||||
|
updateDetachedHistoryTitle();
|
||||||
invalidateHistory();
|
invalidateHistory();
|
||||||
} );
|
} );
|
||||||
updateHistoryRefs(
|
updateHistoryRefs(
|
||||||
@@ -3124,8 +3342,13 @@ void GitPlugin::buildSidePanelTab() {
|
|||||||
const auto* modelEvent = static_cast<const ModelEvent*>( event );
|
const auto* modelEvent = static_cast<const ModelEvent*>( event );
|
||||||
if ( modelEvent->getModelEventType() == ModelEventType::OpenTree )
|
if ( modelEvent->getModelEventType() == ModelEventType::OpenTree )
|
||||||
activateHistoryIndex( modelEvent->getModelIndex(), true );
|
activateHistoryIndex( modelEvent->getModelIndex(), true );
|
||||||
else if ( modelEvent->getModelEventType() == ModelEventType::Open )
|
else if ( modelEvent->getModelEventType() == ModelEventType::Open ) {
|
||||||
activateHistoryIndex( modelEvent->getModelIndex(), false );
|
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 ) {
|
mHistoryTree->setOnSelection( [this]( const ModelIndex& index ) {
|
||||||
if ( !mHistoryModel )
|
if ( !mHistoryModel )
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class UIStackWidget;
|
|||||||
class UIListBoxItem;
|
class UIListBoxItem;
|
||||||
class UIMenu;
|
class UIMenu;
|
||||||
class UITextView;
|
class UITextView;
|
||||||
|
class UISplitter;
|
||||||
} // namespace EE::UI
|
} // namespace EE::UI
|
||||||
|
|
||||||
namespace ecode {
|
namespace ecode {
|
||||||
@@ -167,27 +168,74 @@ class GitPlugin : public PluginBase {
|
|||||||
std::string mHistoryRevision{ "HEAD" };
|
std::string mHistoryRevision{ "HEAD" };
|
||||||
bool mUpdatingHistoryRefs{ false };
|
bool mUpdatingHistoryRefs{ false };
|
||||||
bool mHistoryLoaded{ false };
|
bool mHistoryLoaded{ false };
|
||||||
UIWidget* mCommitDetailsView{ nullptr };
|
struct CommitDetailsState {
|
||||||
UITextView* mCommitDetailsSubject{ nullptr };
|
UIWidget* view{ nullptr };
|
||||||
UITextView* mCommitDetailsMetadata{ nullptr };
|
UITextView* subject{ nullptr };
|
||||||
UITextView* mCommitDetailsMessage{ nullptr };
|
UITextView* author{ nullptr };
|
||||||
UITextView* mCommitDetailsParents{ nullptr };
|
UITextView* dateEmail{ nullptr };
|
||||||
UITextView* mCommitDetailsStatus{ nullptr };
|
UITextView* message{ nullptr };
|
||||||
UIPushButton* mCommitDetailsMessageToggle{ nullptr };
|
UITextView* status{ nullptr };
|
||||||
UIPushButton* mCommitDetailsFilesToggle{ nullptr };
|
UIPushButton* messageToggle{ nullptr };
|
||||||
UIPushButton* mCommitDetailsModeToggle{ nullptr };
|
UIPushButton* filesToggle{ nullptr };
|
||||||
UIPushButton* mCommitDetailsGitHub{ nullptr };
|
UIPushButton* modeToggle{ nullptr };
|
||||||
UIWidget* mCommitDetailsDiffContainer{ nullptr };
|
UIPushButton* gitHub{ nullptr };
|
||||||
UIScrollView* mCommitDetailsDiff{ nullptr };
|
UIWidget* diffContainer{ nullptr };
|
||||||
std::string mCommitDetailsMessageBody;
|
UIScrollView* diff{ nullptr };
|
||||||
std::string mCommitDetailsURL;
|
std::string messageBody;
|
||||||
bool mCommitDetailsMessageExpanded{ false };
|
std::string url;
|
||||||
bool mCommitDetailsFilesCollapsed{ false };
|
Git::Commit commit;
|
||||||
Tools::UIDiffView::ViewMode mCommitDetailsViewMode{ Tools::UIDiffView::ViewMode::Unified };
|
std::string repo;
|
||||||
Git::Commit mCommitDetailsCommit;
|
std::atomic<Uint64> generation{ 0 };
|
||||||
std::string mCommitDetailsRepo;
|
EventConnection closeConnection;
|
||||||
std::atomic<Uint64> mCommitDetailsGeneration{ 0 };
|
Tools::UIDiffView::ViewMode viewMode{ Tools::UIDiffView::ViewMode::Unified };
|
||||||
EventConnection mCommitDetailsCloseConnection;
|
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<int> mRunningUpdateStatus{ 0 };
|
std::atomic<int> mRunningUpdateStatus{ 0 };
|
||||||
std::atomic<bool> mPendingForcedStatusUpdate{ false };
|
std::atomic<bool> mPendingForcedStatusUpdate{ false };
|
||||||
std::shared_ptr<std::atomic<int>> mRunningAsyncTasks{ std::make_shared<std::atomic<int>>( 0 ) };
|
std::shared_ptr<std::atomic<int>> mRunningAsyncTasks{ std::make_shared<std::atomic<int>>( 0 ) };
|
||||||
@@ -217,6 +265,7 @@ class GitPlugin : public PluginBase {
|
|||||||
SyntaxColorScheme scheme;
|
SyntaxColorScheme scheme;
|
||||||
};
|
};
|
||||||
std::optional<CustomTokenizer> mStatusCustomTokenizer;
|
std::optional<CustomTokenizer> mStatusCustomTokenizer;
|
||||||
|
std::optional<CustomTokenizer> mCommitStatusCustomTokenizer;
|
||||||
std::optional<SyntaxDefinition> mTooltipCustomSyntaxDef;
|
std::optional<SyntaxDefinition> mTooltipCustomSyntaxDef;
|
||||||
Uint32 mModelChangedId{ 0 };
|
Uint32 mModelChangedId{ 0 };
|
||||||
Uint32 mModelStylerId{ 0 };
|
Uint32 mModelStylerId{ 0 };
|
||||||
@@ -302,6 +351,8 @@ class GitPlugin : public PluginBase {
|
|||||||
|
|
||||||
void updateStatusBarSync();
|
void updateStatusBarSync();
|
||||||
|
|
||||||
|
void styleCommitFilesStatus( UITextView* status );
|
||||||
|
|
||||||
void updateUI();
|
void updateUI();
|
||||||
|
|
||||||
void updateUINow( bool force = false );
|
void updateUINow( bool force = false );
|
||||||
@@ -324,7 +375,17 @@ class GitPlugin : public PluginBase {
|
|||||||
|
|
||||||
void openCommitDetails( const Git::Commit& commit );
|
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();
|
void buildSidePanelTab();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user