Add shared controls for multi-file diff views

- Introduce UIMultiDiffView as a first-class container for multi-file diffs, with a compact toolbar for expanding or collapsing all files, switching between unified and split views, and displaying aggregate file and line-change statistics.
	- Calculate addition and removal totals during diff preparation so asynchronously prepared diffs do not need to rescan their contents on the UI thread. Cache the child diff views and expose the multi-diff state through the new component.
	- Use UIMultiDiffView for regular diff tabs and Git commit details, removing the duplicated commit-specific toolbar, styling logic, tokenizer state, and view-mode handling. Preserve the original Git commit toolbar appearance, spacing, icons, tooltips, and colored changed-files summary.
	- Keep the existing UIDiffView multi-file factory available for compatibility and add regression coverage for aggregate statistics, toolbar visibility, unified/split propagation, collapse state, prepared diffs, and large patches.
	- Add localized changed-files summaries to all ecode translation catalogs.
	- Fix the secondary-window UI test to use the appropriate std::string assertion helper.
This commit is contained in:
Martín Lucas Golini
2026-09-10 23:15:46 -03:00
parent 095fe7919e
commit 3effffd16f
11 changed files with 318 additions and 137 deletions
+1
View File
@@ -1292,6 +1292,7 @@ Für sichtbare Änderung ecode neu starten.</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_changed_files_summary">Geänderte Dateien (%zu) +%zu -%zu</string>
<string name="git_commit_sha">Commit-SHA</string>
<string name="git_copy_commit_sha">Commit-SHA kopieren&#10;%s</string>
<string name="git_switch_to_split_diff">Zur geteilten Diff-Ansicht wechseln</string>
+1
View File
@@ -1277,6 +1277,7 @@ Restart ecode to see the changes.</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_changed_files_summary">Changed files (%zu) +%zu -%zu</string>
<string name="git_commit_sha">Commit SHA</string>
<string name="git_copy_commit_sha">Copy Commit SHA&#10;%s</string>
<string name="git_switch_to_split_diff">Switch to split diff view</string>
+1
View File
@@ -1276,6 +1276,7 @@ Redémarrer ecode pour voir les changements.</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_changed_files_summary">Fichiers modifiés (%zu) +%zu -%zu</string>
<string name="git_commit_sha">SHA du commit</string>
<string name="git_copy_commit_sha">Copier le SHA du commit&#10;%s</string>
<string name="git_switch_to_split_diff">Passer à la vue des différences séparée</string>
+1
View File
@@ -1081,6 +1081,7 @@ file in the directory tree.</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_changed_files_summary">已更改文件(%zu) +%zu -%zu</string>
<string name="git_commit_sha">提交 SHA</string>
<string name="git_copy_commit_sha">复制提交 SHA&#10;%s</string>
<string name="git_switch_to_split_diff">切换到拆分差异视图</string>
+66
View File
@@ -17,11 +17,14 @@ class Sprite;
namespace UI {
class UIScrollView;
class UIPushButton;
class UITextView;
namespace Tools {
class UIImageViewer;
class UIDiffEditorPlugin;
class UIMultiDiffView;
class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
public:
@@ -182,6 +185,8 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
std::string mImageDiffOldPath;
std::string mImageDiffNewPath;
friend class UIMultiDiffView;
UIDiffView();
virtual void onSizePolicyChange() override;
@@ -231,6 +236,67 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
void updateFileHeaderInfo();
};
class EE_API UIMultiDiffView : public UILinearLayout {
public:
static UIMultiDiffView* New( const std::string& patchText, const std::string& repoPath = "",
UIDiffView::ViewMode viewMode = UIDiffView::ViewMode::Unified,
bool interactiveFileHeaders = false );
static UIMultiDiffView* New( std::shared_ptr<UIDiffView::PreparedMultiFileDiff> preparedDiff,
const std::string& repoPath = "",
UIDiffView::ViewMode viewMode = UIDiffView::ViewMode::Unified,
bool interactiveFileHeaders = false );
const std::vector<UIDiffView*>& getDiffViews() const { return mDiffViews; }
UIScrollView* getScrollView() const { return mScrollView; }
void setViewMode( UIDiffView::ViewMode mode );
UIDiffView::ViewMode getViewMode() const { return mViewMode; }
void setCollapsed( bool collapsed );
bool isCollapsed() const { return mCollapsed; }
void setToolbarVisible( bool visible );
bool isToolbarVisible() const;
size_t getFileCount() const { return mFileCount; }
size_t getAddedLines() const { return mAddedLines; }
size_t getRemovedLines() const { return mRemovedLines; }
protected:
UILinearLayout* mToolbar{ nullptr };
UIPushButton* mFilesToggle{ nullptr };
UIPushButton* mModeToggle{ nullptr };
UITextView* mFilesStatus{ nullptr };
UIScrollView* mScrollView{ nullptr };
std::vector<UIDiffView*> mDiffViews;
UIDiffView::ViewMode mViewMode{ UIDiffView::ViewMode::Unified };
size_t mFileCount{ 0 };
size_t mAddedLines{ 0 };
size_t mRemovedLines{ 0 };
bool mCollapsed{ false };
UIMultiDiffView();
void load( std::shared_ptr<UIDiffView::PreparedMultiFileDiff> preparedDiff,
const std::string& repoPath, UIDiffView::ViewMode viewMode,
bool interactiveFileHeaders );
void updateFilesToggle();
void updateModeToggle();
void updateStatus();
virtual void onThemeLoaded() override;
};
} // namespace Tools
} // namespace UI
} // namespace EE
+205 -2
View File
@@ -14,10 +14,12 @@
#include <eepp/ui/tools/uiimageviewer.hpp>
#include <eepp/ui/uiicon.hpp>
#include <eepp/ui/uiimage.hpp>
#include <eepp/ui/uipushbutton.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uiscrollbar.hpp>
#include <eepp/ui/uiscrollview.hpp>
#include <eepp/ui/uistyle.hpp>
#include <eepp/ui/uitextview.hpp>
#include <eepp/ui/uithememanager.hpp>
#include <eepp/window/window.hpp>
@@ -38,6 +40,8 @@ struct UIDiffView::PreparedPatch {
class UIDiffView::PreparedMultiFileDiff {
public:
std::vector<PreparedPatch> patches;
size_t addedLines{ 0 };
size_t removedLines{ 0 };
};
static bool imagesHaveSameDimensions( const std::string& oldFilePath,
@@ -160,6 +164,201 @@ void UIDiffView::setMultiFileCollapsed( UIScrollView* multiDiff, bool collapsed
diff->setCollapsed( collapsed );
}
UIMultiDiffView* UIMultiDiffView::New( const std::string& patchText, const std::string& repoPath,
UIDiffView::ViewMode viewMode,
bool interactiveFileHeaders ) {
return New( UIDiffView::prepareMultiFileDiff( patchText ), repoPath, viewMode,
interactiveFileHeaders );
}
UIMultiDiffView*
UIMultiDiffView::New( std::shared_ptr<UIDiffView::PreparedMultiFileDiff> preparedDiff,
const std::string& repoPath, UIDiffView::ViewMode viewMode,
bool interactiveFileHeaders ) {
if ( !preparedDiff )
return nullptr;
auto* view = eeNew( UIMultiDiffView, () );
view->load( std::move( preparedDiff ), repoPath, viewMode, interactiveFileHeaders );
return view;
}
UIMultiDiffView::UIMultiDiffView() : UILinearLayout( "multidiffview", UIOrientation::Vertical ) {
beginAttributesTransaction();
setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent );
mToolbar = UILinearLayout::NewHorizontal();
mToolbar->setParent( this );
mToolbar->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent );
mToolbar->setPadding( Rectf( 8, 4, 8, 4 ) );
mFilesToggle = UIPushButton::New();
mFilesToggle->setParent( mToolbar );
mFilesToggle->addClass( "git_commit_btn" );
mFilesToggle->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent );
mFilesToggle->onClick( [this]( const Event* ) { setCollapsed( !mCollapsed ); } );
mModeToggle = UIPushButton::New();
mModeToggle->setParent( mToolbar );
mModeToggle->addClass( "git_commit_btn" );
mModeToggle->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent );
mModeToggle->setLayoutMarginLeft( 4 );
mModeToggle->setTextAsFallback( true );
mModeToggle->onClick( [this]( const Event* ) {
setViewMode( mViewMode == UIDiffView::ViewMode::Unified ? UIDiffView::ViewMode::SideBySide
: UIDiffView::ViewMode::Unified );
} );
mFilesStatus = UITextView::New();
mFilesStatus->setParent( mToolbar );
mFilesStatus->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::WrapContent );
mFilesStatus->setLayoutWeight( 1 );
mFilesStatus->setLayoutMarginLeft( 8 );
mFilesStatus->setGravity( UI_VALIGN_CENTER );
mFilesStatus->setLayoutGravity( UI_VALIGN_CENTER );
mFilesStatus->setUsingCustomStyling( true );
mScrollView = UIScrollView::New();
mScrollView->setParent( this );
mScrollView->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::Fixed );
mScrollView->setLayoutWeight( 1 );
endAttributesTransaction();
}
void UIMultiDiffView::load( std::shared_ptr<UIDiffView::PreparedMultiFileDiff> preparedDiff,
const std::string& repoPath, UIDiffView::ViewMode viewMode,
bool interactiveFileHeaders ) {
auto* uiSceneNode = SceneManager::instance()->getUISceneNode();
const bool wasLoading = uiSceneNode && uiSceneNode->isLoading();
if ( uiSceneNode )
uiSceneNode->setIsLoading( true );
mViewMode = viewMode;
mFileCount = preparedDiff->patches.size();
mAddedLines = preparedDiff->addedLines;
mRemovedLines = preparedDiff->removedLines;
auto* content = UILinearLayout::NewVertical();
content->setParent( mScrollView );
content->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent );
mDiffViews.reserve( preparedDiff->patches.size() );
for ( auto& patch : preparedDiff->patches ) {
auto* diffView = UIDiffView::New();
diffView->setViewMode( viewMode );
diffView->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent );
diffView->setParent( content );
diffView->setHeadersVisible( true );
diffView->setViewModeToggleVisible( false );
diffView->setCompleteViewToggleVisible( false );
diffView->setInteractiveFileHeader( interactiveFileHeaders );
diffView->loadPreparedPatch( std::move( patch ), "", "", repoPath );
mDiffViews.emplace_back( diffView );
}
updateFilesToggle();
updateModeToggle();
updateStatus();
if ( uiSceneNode ) {
uiSceneNode->setIsLoading( wasLoading );
if ( !wasLoading ) {
uiSceneNode->invalidateStyle( this, true );
uiSceneNode->invalidateStyleState( this, true, true );
}
}
}
void UIMultiDiffView::setViewMode( UIDiffView::ViewMode mode ) {
if ( mViewMode == mode )
return;
mViewMode = mode;
for ( auto* diff : mDiffViews )
diff->setViewMode( mode );
updateModeToggle();
}
void UIMultiDiffView::setCollapsed( bool collapsed ) {
if ( mCollapsed == collapsed )
return;
mCollapsed = collapsed;
for ( auto* diff : mDiffViews )
diff->setCollapsed( collapsed );
updateFilesToggle();
}
void UIMultiDiffView::setToolbarVisible( bool visible ) {
mToolbar->setVisible( visible );
}
bool UIMultiDiffView::isToolbarVisible() const {
return mToolbar->isVisible();
}
void UIMultiDiffView::updateFilesToggle() {
const String text = mCollapsed ? i18n( "git_expand_all_files", "Expand All Files" )
: i18n( "git_collapse_all_files", "Collapse All Files" );
mFilesToggle->setTooltipText( text );
if ( auto* scene = getUISceneNode() ) {
if ( auto* icon = scene->findIcon( mCollapsed ? "expand-all" : "collapse-all" ) )
mFilesToggle->setIcon( icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
}
mFilesToggle->setText( mFilesToggle->hasIcon() ? String{} : text );
}
void UIMultiDiffView::updateModeToggle() {
const bool unified = mViewMode == UIDiffView::ViewMode::Unified;
mModeToggle->setText( unified ? i18n( "git_split_diff", "Split" )
: i18n( "git_unified_diff", "Unified" ) );
mModeToggle->setTooltipText(
unified ? i18n( "git_switch_to_split_diff", "Switch to split diff view" )
: i18n( "git_switch_to_unified_diff", "Switch to unified diff view" ) );
if ( auto* scene = getUISceneNode() ) {
if ( auto* icon = scene->findIcon( unified ? "split-horizontal" : "layout" ) )
mModeToggle->setIcon( icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
}
}
void UIMultiDiffView::updateStatus() {
mFilesStatus->setText( String::format(
i18n( "git_changed_files_summary", "Changed files (%zu) +%zu -%zu" ).toUtf8(), mFileCount,
mAddedLines, mRemovedLines ) );
if ( auto* scene = getUISceneNode();
scene && scene->getRoot() && scene->getRoot()->getUIStyle() ) {
auto* root = scene->getRoot();
auto font = root->getUIStyle()->getVariable( "--font" );
auto warning = root->getUIStyle()->getVariable( "--theme-warning" );
auto success = root->getUIStyle()->getVariable( "--theme-success" );
auto error = root->getUIStyle()->getVariable( "--theme-error" );
std::vector<SyntaxPattern> patterns;
patterns.emplace_back( SyntaxPattern( { ".*%((%d+)%)%s+(%+%d+)%s+(%-%d+)" },
{ "normal", "warning", "keyword", "type" } ) );
SyntaxDefinition definition( "multi_diff_files_status", {}, std::move( patterns ) );
SyntaxColorScheme scheme(
"multi_diff_files_status",
{ { "normal"_sst,
{ font.isEmpty() ? mFilesStatus->getFontColor()
: Color::fromString( font.getValue() ) } },
{ "warning"_sst,
{ warning.isEmpty() ? Color( 220, 170, 0 )
: Color::fromString( warning.getValue() ) } },
{ "keyword"_sst,
{ success.isEmpty() ? Color( 0, 180, 60 )
: Color::fromString( success.getValue() ) } },
{ "type"_sst,
{ error.isEmpty() ? Color( 220, 50, 70 )
: Color::fromString( error.getValue() ) } } },
{} );
SyntaxTokenizer::tokenizeText( definition, scheme, mFilesStatus->getTextCache() );
mFilesStatus->invalidateDraw();
}
}
void UIMultiDiffView::onThemeLoaded() {
UILinearLayout::onThemeLoaded();
updateFilesToggle();
updateModeToggle();
updateStatus();
}
class UIDiffEditorPlugin : public UICodeEditorPlugin {
public:
UIDiffEditorPlugin( UIDiffView* view ) : mView( view ) {}
@@ -1410,8 +1609,12 @@ UIDiffView::prepareMultiFileDiff( const std::string& patchText,
for ( const auto& diff : diffs ) {
if ( cancelled && cancelled->load( std::memory_order_relaxed ) )
return {};
prepared->patches.emplace_back(
preparePatch( diff, "", SubLineDiffAlgorithm::LCS, cancelled ) );
auto patch = preparePatch( diff, "", SubLineDiffAlgorithm::LCS, cancelled );
for ( const auto& line : patch.lines ) {
prepared->addedLines += line.type == DiffLineType::Added;
prepared->removedLines += line.type == DiffLineType::Removed;
}
prepared->patches.emplace_back( std::move( patch ) );
}
if ( cancelled && cancelled->load( std::memory_order_relaxed ) )
+13 -8
View File
@@ -125,10 +125,13 @@ diff --git a/second.txt b/second.txt
+after
)patch";
auto* viewer =
UIDiffView::NewMultiFileDiffViewer( patchText, "", UIDiffView::ViewMode::SideBySide, true );
auto diffViews = viewer->findAllByType<UIDiffView>( UI_TYPE_DIFF_VIEW );
auto* viewer = UIMultiDiffView::New( patchText, "", UIDiffView::ViewMode::SideBySide, true );
const auto& diffViews = viewer->getDiffViews();
ASSERT_EQ( size_t{ 2 }, diffViews.size() );
EXPECT_EQ( size_t{ 2 }, viewer->getFileCount() );
EXPECT_EQ( size_t{ 2 }, viewer->getAddedLines() );
EXPECT_EQ( size_t{ 2 }, viewer->getRemovedLines() );
EXPECT_TRUE( viewer->isToolbarVisible() );
for ( const auto* diffView : diffViews ) {
EXPECT_EQ( UIDiffView::ViewMode::SideBySide, diffView->getViewMode() );
EXPECT_FALSE( diffView->isViewModeToggleVisible() );
@@ -136,14 +139,16 @@ diff --git a/second.txt b/second.txt
EXPECT_TRUE( diffView->isInteractiveFileHeader() );
}
UIDiffView::setMultiFileViewMode( viewer, UIDiffView::ViewMode::Unified );
UIDiffView::setMultiFileCollapsed( viewer, true );
viewer->setViewMode( UIDiffView::ViewMode::Unified );
viewer->setCollapsed( true );
EXPECT_EQ( UIDiffView::ViewMode::Unified, viewer->getViewMode() );
EXPECT_TRUE( viewer->isCollapsed() );
for ( const auto* diffView : diffViews ) {
EXPECT_EQ( UIDiffView::ViewMode::Unified, diffView->getViewMode() );
EXPECT_TRUE( diffView->isCollapsed() );
EXPECT_TRUE( diffView->getViewLines().empty() );
}
UIDiffView::setMultiFileCollapsed( viewer, false );
viewer->setCollapsed( false );
for ( const auto* diffView : diffViews ) {
EXPECT_FALSE( diffView->isCollapsed() );
EXPECT_FALSE( diffView->getViewLines().empty() );
@@ -202,8 +207,8 @@ UTEST( UIDiffView, MultiFileViewerHandlesLargePatches ) {
"\n+++ b/" + fileName + "\n@@ -1 +1 @@\n-old\n+new\n";
}
auto* viewer = UIDiffView::NewMultiFileDiffViewer( patchText );
EXPECT_EQ( fileCount, viewer->findAllByType<UIDiffView>( UI_TYPE_DIFF_VIEW ).size() );
auto* viewer = UIMultiDiffView::New( patchText );
EXPECT_EQ( fileCount, viewer->getDiffViews().size() );
EXPECT_TRUE( viewer->findAllByType<UIImageViewer>( UI_TYPE_IMAGE_VIEWER ).empty() );
EXPECT_FALSE( viewer->getUISceneNode()->isLoading() );
+1 -1
View File
@@ -140,7 +140,7 @@ UTEST( UIApplication, CreatesSecondaryWindowWithoutChangingAmbientScene ) {
secondaryUI->getWindow()->getInput()->beginInputFrame();
primaryWindow->getInput()->processEventForWindow( &textEvent );
secondaryUI->getWindow()->getInput()->endInputFrame();
EXPECT_STREQ( textInput->getText().toUtf8().c_str(), "x" );
EXPECT_STDSTREQ( textInput->getText().toUtf8(), "x" );
}
EXPECT_EQ( SceneManager::instance()->getUISceneNode(), app.getUI() );
+11 -22
View File
@@ -2663,21 +2663,16 @@ void App::loadDiffFromMemory( const std::string& content, const std::string& ori
if ( !icon )
icon = getUISceneNode()->findIcon( "file" );
auto scrollView = UIDiffView::NewMultiFileDiffViewer(
content, repoPath, mConfig.editor.diffViewMode, interactiveFileHeaders );
auto [tab, iv] = getSplitter()->createWidget( scrollView, diffViewTitle );
auto multiDiff = UIMultiDiffView::New( content, repoPath, mConfig.editor.diffViewMode,
interactiveFileHeaders );
auto [tab, iv] = getSplitter()->createWidget( multiDiff, diffViewTitle );
if ( icon )
tab->setIcon( icon->createDrawable( getMenuIconSize() ) );
tab->setText( diffViewTitle );
auto diffView = scrollView->getFirstChild()->asType<UILinearLayout>()->getFirstChild();
while ( diffView ) {
if ( diffView->isType( UI_TYPE_DIFF_VIEW ) ) {
configureDiffView( diffView->asType<UIDiffView>() );
diffView->asType<UIDiffView>()->setSyntaxColorScheme( *getCurrentColorScheme() );
}
diffView = diffView->getNextNode();
for ( auto* diffView : multiDiff->getDiffViews() ) {
configureDiffView( diffView );
diffView->setSyntaxColorScheme( *getCurrentColorScheme() );
}
return;
}
@@ -2719,21 +2714,15 @@ void App::loadDiffFromPath( const std::string& path ) {
if ( !icon )
icon = getUISceneNode()->findIcon( "file" );
auto scrollView =
UIDiffView::NewMultiFileDiffViewer( content, "", mConfig.editor.diffViewMode );
auto [tab, iv] = getSplitter()->createWidget( scrollView, diffViewTitle );
auto multiDiff = UIMultiDiffView::New( content, "", mConfig.editor.diffViewMode );
auto [tab, iv] = getSplitter()->createWidget( multiDiff, diffViewTitle );
if ( icon )
tab->setIcon( icon->createDrawable( getMenuIconSize() ) );
tab->setText( diffViewTitle );
auto diffView = scrollView->getFirstChild()->asType<UILinearLayout>()->getFirstChild();
while ( diffView ) {
if ( diffView->isType( UI_TYPE_DIFF_VIEW ) ) {
configureDiffView( diffView->asType<UIDiffView>() );
diffView->asType<UIDiffView>()->setSyntaxColorScheme( *getCurrentColorScheme() );
}
diffView = diffView->getNextNode();
for ( auto* diffView : multiDiff->getDiffViews() ) {
configureDiffView( diffView );
diffView->setSyntaxColorScheme( *getCurrentColorScheme() );
}
return;
}
+17 -92
View File
@@ -646,28 +646,6 @@ void GitPlugin::updateStatusBarSync() {
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 ) {
if ( !mGit || !mGitFound )
return;
@@ -843,9 +821,6 @@ PluginRequestHandle GitPlugin::processMessage( const PluginMessage& msg ) {
}
case ecode::PluginMessageType::UIThemeReloaded: {
mStatusCustomTokenizer.reset();
mCommitStatusCustomTokenizer.reset();
styleCommitFilesStatus( mCommitDetails.status );
styleCommitFilesStatus( mDetachedHistory.details.status );
updateUINow( true );
break;
}
@@ -3060,19 +3035,8 @@ void GitPlugin::CommitDetailsState::openCommitDetails( GitPlugin& plugin, const
<TextView id="git_commit_message" lw="mp" lh="wc" word-wrap="true"
focusable="false" visible="false" />
</vbox>
<hbox lw="mp" lh="wc" padding-left="8dp" padding-right="8dp"
padding-top="4dp" padding-bottom="4dp">
<PushButton id="git_commit_files_toggle"
tooltip="@string(git_collapse_all_files, Collapse All Files)"
icon="icon(collapse-all, 12dp)" class="git_commit_btn" />
<PushButton id="git_commit_mode_toggle"
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"
margin-left="4dp" class="git_commit_btn" />
<TextView id="git_commit_files_status" lw="0" lw8="1" lh="wc"
margin-left="8dp" layout_gravity="center_vertical" focusable="false" />
</hbox>
<TextView id="git_commit_files_status" lw="mp" lh="wc" padding="8dp"
focusable="false" />
<vbox id="git_commit_diff" lw="mp" lh="0" lw8="1" />
</vbox>
)xml" );
@@ -3086,8 +3050,6 @@ void GitPlugin::CommitDetailsState::openCommitDetails( GitPlugin& plugin, const
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* ) {
@@ -3099,34 +3061,6 @@ void GitPlugin::CommitDetailsState::openCommitDetails( GitPlugin& plugin, const
"Collapse Commit Description" )
: owner->i18n( "git_expand_commit_description", "Expand Commit Description" ) );
} );
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 =
owner->findIcon( state->filesCollapsed ? "expand-all" : "collapse-all" ) )
state->filesToggle->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 ) ) );
} );
gitHub->onClick( [state]( const Event* ) {
if ( !state->url.empty() )
Engine::instance()->openURI( state->url );
@@ -3158,11 +3092,7 @@ void GitPlugin::CommitDetailsState::openCommitDetails( GitPlugin& plugin, const
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 ) ) );
status->setVisible( true );
url.clear();
gitHub->setVisible( false );
view->find( "git_commit_sha" )->setVisible( !isWorkingTree );
@@ -3233,7 +3163,6 @@ void GitPlugin::CommitDetailsState::loadCommitFiles( GitPlugin& plugin, bool det
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 );
@@ -3258,42 +3187,28 @@ void GitPlugin::CommitDetailsState::loadCommitFiles( GitPlugin& plugin, bool det
details.messageToggle->setText(
plugin->i18n( "git_expand_commit_description", "Expand Commit Description" ) );
int totalInserts = 0;
int totalDeletes = 0;
for ( const auto& file : result.files ) {
totalInserts += file.inserts;
totalDeletes += file.deletes;
}
if ( result.files.empty() ) {
details.status->setText(
plugin->i18n( "git_no_changed_files", "No changed files" ) );
} else {
details.status->setText( String::format(
plugin->i18n( "git_changed_files_summary", "Changed files (%zu) +%d -%d" )
.toUtf8(),
result.files.size(), totalInserts, totalDeletes ) );
details.status->setVisible( false );
}
plugin->styleCommitFilesStatus( details.status );
details.url = std::move( result.commitURL );
details.gitHub->setVisible( !details.url.empty() );
details.diffContainer->closeAllChildren();
details.diff = nullptr;
if ( preparedDiff ) {
details.diff = UIDiffView::NewMultiFileDiffViewer( std::move( preparedDiff ), repo,
details.viewMode );
details.diff = UIMultiDiffView::New( std::move( preparedDiff ), repo,
UIDiffView::ViewMode::Unified, true );
details.diff->setLayoutSizePolicy( SizePolicy::MatchParent,
SizePolicy::MatchParent );
details.diff->setParent( details.diffContainer );
for ( auto* diff : UIDiffView::multiFileDiffViews( details.diff ) ) {
diff->setInteractiveFileHeader( true );
for ( auto* diff : details.diff->getDiffViews() ) {
if ( const auto* scheme = plugin->getPluginContext()->getCurrentColorScheme() )
diff->setSyntaxColorScheme( *scheme );
}
}
const bool hasDiff = details.diff != nullptr;
details.filesToggle->setVisible( hasDiff );
details.modeToggle->setVisible( hasDiff );
} );
} );
}
@@ -3496,6 +3411,16 @@ void GitPlugin::buildSidePanelTab() {
#git_commit_details .git_commit_btn:hover {
border-color: var(--primary);
}
multidiffview .git_commit_btn {
lw: 20dp;
lh: 20dp;
padding: 0;
background-color: var(--list-back);
border-color: transparent;
}
multidiffview .git_commit_btn:hover {
border-color: var(--primary);
}
#git_commit_details #git_commit_author {
font-size: 11dp;
text-stroke-width: 1dp;
+1 -12
View File
@@ -178,11 +178,9 @@ class GitPlugin : public PluginBase {
UITextView* message{ nullptr };
UITextView* status{ nullptr };
UIPushButton* messageToggle{ nullptr };
UIPushButton* filesToggle{ nullptr };
UIPushButton* modeToggle{ nullptr };
UIPushButton* gitHub{ nullptr };
UIWidget* diffContainer{ nullptr };
UIScrollView* diff{ nullptr };
Tools::UIMultiDiffView* diff{ nullptr };
std::string messageBody;
std::string url;
Git::Commit commit;
@@ -190,9 +188,7 @@ class GitPlugin : public PluginBase {
std::atomic<Uint64> generation{ 0 };
std::shared_ptr<std::atomic_bool> diffPreparationCancelled;
EventConnection closeConnection;
Tools::UIDiffView::ViewMode viewMode{ Tools::UIDiffView::ViewMode::Unified };
bool messageExpanded{ false };
bool filesCollapsed{ false };
bool workingTree{ false };
void openCommitDetails( GitPlugin& plugin, const Git::Commit& commit, bool detached,
@@ -216,8 +212,6 @@ class GitPlugin : public PluginBase {
message = nullptr;
status = nullptr;
messageToggle = nullptr;
filesToggle = nullptr;
modeToggle = nullptr;
gitHub = nullptr;
diffContainer = nullptr;
diff = nullptr;
@@ -225,9 +219,7 @@ class GitPlugin : public PluginBase {
url.clear();
commit = {};
repo.clear();
viewMode = Tools::UIDiffView::ViewMode::Unified;
messageExpanded = false;
filesCollapsed = false;
workingTree = false;
}
};
@@ -278,7 +270,6 @@ class GitPlugin : public PluginBase {
SyntaxColorScheme scheme;
};
std::optional<CustomTokenizer> mStatusCustomTokenizer;
std::optional<CustomTokenizer> mCommitStatusCustomTokenizer;
std::optional<SyntaxDefinition> mTooltipCustomSyntaxDef;
Uint32 mModelChangedId{ 0 };
Uint32 mModelStylerId{ 0 };
@@ -365,8 +356,6 @@ class GitPlugin : public PluginBase {
void updateStatusBarSync();
void styleCommitFilesStatus( UITextView* status );
void updateUI();
void updateUINow( bool force = false );