diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml
index 222214b09..3d22fe538 100644
--- a/bin/assets/i18n/de.xml
+++ b/bin/assets/i18n/de.xml
@@ -1263,6 +1263,9 @@ Für sichtbare Änderung ecode neu starten.
Aktualisieren
Ältere Commits laden
Noch keine Commits
+ Arbeitsbaum / Index
+ %zu geändert, %zu vorgemerkt
+ Nicht übernommene Änderungen
Keine weiteren Commits
Im Git-Verlauf anzeigen
Git-Commit-Verlauf – %s
diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml
index 8c203c154..27065ba12 100644
--- a/bin/assets/i18n/en.xml
+++ b/bin/assets/i18n/en.xml
@@ -1248,6 +1248,9 @@ Restart ecode to see the changes.
Refresh
Load older commits
No commits yet
+ Working Tree / Index
+ %zu changed, %zu staged
+ Uncommitted changes
No additional commits
Show in Git History
Git Commit History - %s
diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml
index 55582671e..9be5a2b4b 100644
--- a/bin/assets/i18n/fr.xml
+++ b/bin/assets/i18n/fr.xml
@@ -1247,6 +1247,9 @@ Redémarrer ecode pour voir les changements.
Actualiser
Charger les commits précédents
Aucun commit pour le moment
+ Arbre de travail / Index
+ %zu modifié(s), %zu indexé(s)
+ Modifications non validées
Aucun commit supplémentaire
Afficher dans l’historique Git
Historique des commits Git – %s
diff --git a/bin/assets/i18n/zh.xml b/bin/assets/i18n/zh.xml
index 3e815aa44..7e91ee8c9 100644
--- a/bin/assets/i18n/zh.xml
+++ b/bin/assets/i18n/zh.xml
@@ -1052,6 +1052,9 @@ file in the directory tree.
刷新
加载更早的提交
尚无提交
+ 工作树 / 索引
+ %zu 个更改,%zu 个已暂存
+ 未提交的更改
没有其他提交
在 Git 历史记录中显示
Git 提交历史记录 - %s
diff --git a/include/eepp/ui/tools/uidiffview.hpp b/include/eepp/ui/tools/uidiffview.hpp
index ef50cfb76..5126368e8 100644
--- a/include/eepp/ui/tools/uidiffview.hpp
+++ b/include/eepp/ui/tools/uidiffview.hpp
@@ -45,21 +45,29 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
virtual ~UIDiffView();
virtual Uint32 getType() const override;
+
virtual bool isType( const Uint32& type ) const override;
void loadFromPatch( const std::string& patchText, const std::string& originalFilePath = "",
const std::string& oldFilePath = "", const std::string& repoPath = "" );
+
void loadFromStrings( const std::string& oldText, const std::string& newText,
const std::string& originalFilePath = "" );
+
void loadFromFile( const std::string& oldFilePath, const std::string& newFilePath );
UICodeEditor* getEditor() const { return mEditor; }
+
UICodeEditor* getLeftEditor() const { return mLeftEditor; }
+
UICodeEditor* getRightEditor() const { return mRightEditor; }
+
UIImageViewer* getLeftImageViewer() const { return mLeftImageViewer; }
+
UIImageViewer* getRightImageViewer() const { return mRightImageViewer; }
enum class DiffLineType { Common, Added, Removed, Header };
+
struct DiffLine {
DiffLineType type{ DiffLineType::Common };
String text;
@@ -69,27 +77,37 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
};
const std::vector& getDiffLines() const { return mLines; }
+
const std::vector& getViewLines() const { return mViewLines; }
void setViewMode( ViewMode mode );
+
ViewMode getViewMode() const { return mViewMode; }
void setViewModeToggleVisible( bool visible );
+
bool isViewModeToggleVisible() const { return mViewModeToggleVisible; }
void setCompleteView( bool complete );
+
bool isCompleteView() const { return mShowCompleteView; }
void setCompleteViewToggleVisible( bool visible );
+
bool isCompleteViewToggleVisible() const { return mCompleteViewToggleVisible; }
void setSubLineDiffAlgorithm( SubLineDiffAlgorithm algo );
+
SubLineDiffAlgorithm getSubLineDiffAlgorithm() const { return mSubLineDiffAlgorithm; }
void setSyntaxColorScheme( const SyntaxColorScheme& colorScheme );
void setHeadersVisible( bool visible );
+ void setInteractiveFileHeader( bool enabled );
+
+ bool isInteractiveFileHeader() const { return mInteractiveFileHeader; }
+
void setCollapsed( bool collapsed );
bool isCollapsed() const { return mCollapsed; }
@@ -97,6 +115,14 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
const String& getFileName() const { return mFileName; }
+ const String& getFileDisplayName() const { return mFileDisplayName; }
+
+ const String& getFileDisplayPath() const { return mFileDisplayPath; }
+
+ const String& getAddedLinesText() const { return mAddedLinesText; }
+
+ const String& getRemovedLinesText() const { return mRemovedLinesText; }
+
bool isImageDiff() const { return mIsImageDiff; }
void setAutoDeleteOldTempImage( bool set ) { mAutoDeleteOldTempImage = set; }
@@ -125,8 +151,13 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
bool mIsImageDiff{ false };
bool mAutoDeleteOldTempImage{ false };
bool mCollapsed{ false };
+ bool mInteractiveFileHeader{ false };
std::shared_ptr mSyntaxDef;
String mFileName;
+ String mFileDisplayName;
+ String mFileDisplayPath;
+ String mAddedLinesText;
+ String mRemovedLinesText;
std::string mImageDiffOldPath;
std::string mImageDiffNewPath;
@@ -141,18 +172,32 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
virtual Uint32 onKeyDown( const KeyEvent& event ) override;
void createEditor( UICodeEditor*& editor, std::unique_ptr& plugin );
+
void syncScroll( UICodeEditor* source, UICodeEditor* target, bool emitEvent = false );
+
void updateModeButton();
+
void computeSubLineDiff( DiffLine& oldLine, DiffLine& newLine );
+
void updateEditorsText();
+
void updateButtonsText();
+
void updateButtonsVisibility();
+
void createImageViewers();
+
bool loadImageDiffFromPaths( const std::string& oldFilePath, const std::string& newFilePath );
+
void updateImageDiffView();
+
void resetToTextDiffView();
+
void imageDisplayState( bool& displayDiffImage, bool& displayLeftImage );
+
void updateImagesPosAndSize();
+
+ void updateFileHeaderInfo();
};
} // namespace Tools
diff --git a/src/eepp/ui/tools/uidiffview.cpp b/src/eepp/ui/tools/uidiffview.cpp
index 7a19f2014..07fb6758d 100644
--- a/src/eepp/ui/tools/uidiffview.cpp
+++ b/src/eepp/ui/tools/uidiffview.cpp
@@ -11,10 +11,12 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
#include
#include
@@ -152,6 +154,20 @@ class UIDiffEditorPlugin : public UICodeEditorPlugin {
if ( mView->areHeadersVisible() ) {
editor->registerTopSpace( this, mPluginTopSpace, 0 );
}
+
+ if ( mView->isInteractiveFileHeader() ) {
+ mHeaderIconWidth = 0;
+ const int iconSize = PixelDensity::dpToPxI( 14 );
+ if ( auto* icon = editor->getUISceneNode()->findIcon( "chevron-down" ) )
+ mExpandedIcon = icon->createDrawable( iconSize );
+ if ( auto* icon = editor->getUISceneNode()->findIcon( "chevron-right" ) )
+ mCollapsedIcon = icon->createDrawable( iconSize );
+ if ( mExpandedIcon )
+ mHeaderIconWidth = mExpandedIcon->getPixelsSize().getWidth();
+ if ( mCollapsedIcon )
+ mHeaderIconWidth =
+ std::max( mHeaderIconWidth, mCollapsedIcon->getPixelsSize().getWidth() );
+ }
}
Float getPluginTopSpace() const { return mPluginTopSpace; }
@@ -188,13 +204,88 @@ class UIDiffEditorPlugin : public UICodeEditorPlugin {
Float fontSize = editor->getUISceneNode()->getUIThemeManager()->getDefaultFontSize();
Float textOffsetY =
eefloor( ( size.getHeight() - font->getLineSpacing( fontSize ) ) * 0.5f );
- Color textColor( editor->getColorScheme().getEditorColor( SyntaxStyleTypes::LineNumber2 ) );
- Vector2f pos( screenStart.x + eefloor( PixelDensity::dpToPx( 8 ) ),
- screenStart.y + textOffsetY );
+ const Uint32 textHints = mView->getFileName().getTextHints() | mView->getDefaultTextHints();
+ Color textColor( editor->getColorScheme().getEditorColor( SyntaxStyleTypes::Text ) );
+ Color hintColor( editor->getColorScheme().getEditorColor( SyntaxStyleTypes::LineNumber2 ) );
+ FontStyleConfig textConfig;
+ textConfig.Font = font;
+ textConfig.CharacterSize = fontSize;
+ textConfig.FontColor = textColor;
+ Float left = screenStart.x + eefloor( PixelDensity::dpToPx( 8 ) );
+ const Float gap = eefloor( PixelDensity::dpToPx( 6 ) );
- Text::draw( mView->getFileName(), pos, font, fontSize, textColor, 0, 0.f, Color::Black,
- Color::Black, { 1, 1 }, 4,
- mView->getFileName().getTextHints() | mView->getDefaultTextHints() );
+ if ( mView->isInteractiveFileHeader() ) {
+ auto& icon = mView->isCollapsed() ? mCollapsedIcon : mExpandedIcon;
+ if ( icon ) {
+ icon->setColor( textColor );
+ const Sizef iconSize = icon->getPixelsSize();
+ icon->draw(
+ { left + eefloor( ( mHeaderIconWidth - iconSize.x ) * 0.5f ),
+ screenStart.y + eefloor( ( size.getHeight() - iconSize.y ) * 0.5f ) } );
+ }
+ left += mHeaderIconWidth + gap;
+ }
+
+ Vector2f pos( left, screenStart.y + textOffsetY );
+ const String& fileName = mView->getFileDisplayName().empty() ? mView->getFileName()
+ : mView->getFileDisplayName();
+ Text::draw( fileName, pos, font, fontSize, textColor, 0, 0.f, Color::Black, Color::Black,
+ { 1, 1 }, 4, textHints );
+ pos.x += Text::getTextWidth( fileName, textConfig, 4, textHints ) + gap;
+ if ( !mView->getFileDisplayPath().empty() )
+ Text::draw( mView->getFileDisplayPath(), pos, font, fontSize, hintColor, 0, 0.f,
+ Color::Black, Color::Black, { 1, 1 }, 4, textHints );
+
+ if ( mView->isInteractiveFileHeader() ) {
+ const auto variableColor = [editor]( const char* variable, Color fallback ) {
+ auto value =
+ editor->getUISceneNode()->getRoot()->getUIStyle()->getVariable( variable );
+ return value.isEmpty() ? fallback : Color::fromString( value.getValue() );
+ };
+ const Color addedColor = variableColor( "--theme-success", Color( 0, 180, 60 ) );
+ const Color removedColor = variableColor( "--theme-error", Color( 220, 50, 70 ) );
+ FontStyleConfig addedConfig( textConfig );
+ addedConfig.FontColor = addedColor;
+ FontStyleConfig removedConfig( textConfig );
+ removedConfig.FontColor = removedColor;
+ const Float removedWidth =
+ Text::getTextWidth( mView->getRemovedLinesText(), removedConfig, 4, textHints );
+ const Float addedWidth =
+ Text::getTextWidth( mView->getAddedLinesText(), addedConfig, 4, textHints );
+ Float right = screenStart.x + width - eefloor( PixelDensity::dpToPx( 8 ) );
+ right -= removedWidth;
+ Text::draw( mView->getRemovedLinesText(), { right, screenStart.y + textOffsetY }, font,
+ fontSize, removedColor, 0, 0.f, Color::Black, Color::Black, { 1, 1 }, 4,
+ textHints );
+ right -= gap + addedWidth;
+ Text::draw( mView->getAddedLinesText(), { right, screenStart.y + textOffsetY }, font,
+ fontSize, addedColor, 0, 0.f, Color::Black, Color::Black, { 1, 1 }, 4,
+ textHints );
+ }
+ }
+
+ bool onMouseClick( UICodeEditor* editor, const Vector2i& position,
+ const Uint32& flags ) override {
+ if ( !mView->isInteractiveFileHeader() || !( flags & EE_BUTTON_LMASK ) )
+ return false;
+ const Vector2f localPos( editor->convertToNodeSpace( position.asFloat() ) );
+ if ( localPos.x >= 0 && localPos.x < editor->getTopAreaWidth() && localPos.y >= 0 &&
+ localPos.y < mPluginTopSpace ) {
+ mView->setCollapsed( !mView->isCollapsed() );
+ return true;
+ }
+ return false;
+ }
+
+ bool onMouseMove( UICodeEditor* editor, const Vector2i& position,
+ const Uint32& /*flags*/ ) override {
+ if ( !mView->isInteractiveFileHeader() )
+ return false;
+ const Vector2f localPos( editor->convertToNodeSpace( position.asFloat() ) );
+ if ( localPos.x >= 0 && localPos.x < editor->getTopAreaWidth() && localPos.y >= 0 &&
+ localPos.y < mPluginTopSpace )
+ editor->getUISceneNode()->setCursor( Cursor::Hand );
+ return false;
}
void drawBeforeLineText( UICodeEditor* editor, const Int64& index, Vector2f position,
@@ -321,6 +412,9 @@ class UIDiffEditorPlugin : public UICodeEditorPlugin {
UIDiffView* mView;
Float mGutterWidth{ 0 };
Float mPluginTopSpace{ 0 };
+ Float mHeaderIconWidth{ 0 };
+ DrawablePtr mExpandedIcon;
+ DrawablePtr mCollapsedIcon;
};
UIDiffView* UIDiffView::New() {
@@ -1119,6 +1213,7 @@ void UIDiffView::loadFromPatch( const std::string& patchText, const std::string&
if ( loadImageDiffFromPaths( oldImagePath, newImagePath ) ) {
if ( !imagePatch.fileName.empty() )
mFileName = std::move( imagePatch.fileName );
+ updateFileHeaderInfo();
return;
}
}
@@ -1230,6 +1325,7 @@ void UIDiffView::loadFromPatch( const std::string& patchText, const std::string&
SyntaxDefinitionManager::instance()->getLanguageDefinition( def.getLanguageIndex() );
mFileName = std::move( filename );
}
+ updateFileHeaderInfo();
updateEditorsText();
updateButtonsText();
@@ -1282,6 +1378,7 @@ void UIDiffView::loadFromStrings( const std::string& oldText, const std::string&
SyntaxDefinitionManager::instance()->getLanguageDefinition( def.getLanguageIndex() );
mFileName = FileSystem::fileNameFromPath( originalFilePath );
}
+ updateFileHeaderInfo();
updateEditorsText();
updateButtonsText();
@@ -1357,6 +1454,33 @@ void UIDiffView::setHeadersVisible( bool visible ) {
updateModeButton();
}
+void UIDiffView::setInteractiveFileHeader( bool enabled ) {
+ if ( enabled == mInteractiveFileHeader )
+ return;
+ mInteractiveFileHeader = enabled;
+ mPlugin->registerUpdate( mEditor );
+ mLeftPlugin->registerUpdate( mLeftEditor );
+ mRightPlugin->registerUpdate( mRightEditor );
+ mEditor->invalidateDraw();
+ mLeftEditor->invalidateDraw();
+ mRightEditor->invalidateDraw();
+}
+
+void UIDiffView::updateFileHeaderInfo() {
+ const std::string fileName( mFileName.toUtf8() );
+ mFileDisplayName = String::fromUtf8( FileSystem::fileNameFromPath( fileName ) );
+ mFileDisplayPath = String::fromUtf8( FileSystem::fileRemoveFileName( fileName ) );
+
+ std::size_t added = 0;
+ std::size_t removed = 0;
+ for ( const auto& line : mLines ) {
+ added += line.type == DiffLineType::Added;
+ removed += line.type == DiffLineType::Removed;
+ }
+ mAddedLinesText = String::format( "+ %zu", added );
+ mRemovedLinesText = String::format( "- %zu", removed );
+}
+
Uint32 UIDiffView::onKeyDown( const KeyEvent& event ) {
auto editor = mViewMode == ViewMode::Unified ? mEditor : mRightEditor;
diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp
index 5d1ff1f39..0e3a02f4b 100644
--- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp
+++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp
@@ -747,8 +747,11 @@ void AutoCompletePlugin::onRegister( UICodeEditor* editor ) {
std::vector listeners;
listeners.push_back( editor->on( Event::OnDocumentLoaded, [this, editor]( const Event* ) {
mDirty = true;
- mDocs.insert( editor->getDocumentRef().get() );
- mEditorDocs[editor] = editor->getDocumentRef().get();
+ {
+ Lock l( mDocMutex );
+ mDocs.insert( editor->getDocumentRef().get() );
+ mEditorDocs[editor] = editor->getDocumentRef().get();
+ }
tryRequestCapabilities( editor );
} ) );
@@ -2587,8 +2590,11 @@ AutoCompletePlugin::getDocumentSymbols( const std::shared_ptr& doc
} ) )
symbols.push_back( std::move( matchStr ) );
}
- if ( mShuttingDown || mDocs.find( doc ) == mDocs.end() )
- break;
+ {
+ Lock l( mDocMutex );
+ if ( mShuttingDown || mDocs.find( doc ) == mDocs.end() )
+ break;
+ }
}
return symbols;
}
diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp
index 5211369da..3d7c4e56b 100644
--- a/src/tools/ecode/plugins/git/git.cpp
+++ b/src/tools/ecode/plugins/git/git.cpp
@@ -286,6 +286,64 @@ Git::CommitFiles Git::commitFiles( const Commit& commit, const std::string& proj
return result;
}
+Git::CommitFiles Git::workingTreeFiles( const std::string& projectDir ) {
+ CommitFiles result;
+ Status current = status( false, projectDir );
+ for ( const auto& [_, files] : current.files ) {
+ for ( const auto& file : files ) {
+ auto found = std::find_if(
+ result.files.begin(), result.files.end(),
+ [&file]( const CommitFile& item ) { return item.path == file.file; } );
+ if ( found == result.files.end() ) {
+ CommitFile item;
+ item.path = file.file;
+ item.status = std::string( 1, static_cast( file.report.symbol ) );
+ item.inserts = file.inserts;
+ item.deletes = file.deletes;
+ item.isBinary = file.isBinary;
+ result.files.emplace_back( std::move( item ) );
+ } else {
+ found->inserts += file.inserts;
+ found->deletes += file.deletes;
+ found->isBinary |= file.isBinary;
+ }
+ }
+ }
+
+ result.returnCode = git( { "diff", "--no-ext-diff", "--no-color", "-M", "HEAD", "--" },
+ projectDir, result.patch );
+ if ( result.fail() ) {
+ result.patch.clear();
+ result.returnCode =
+ git( { "diff", "--no-ext-diff", "--no-color", "-M", "--" }, projectDir, result.patch );
+ }
+ if ( result.fail() ) {
+ result.result = std::move( result.patch );
+ return result;
+ }
+
+ for ( const auto& file : result.files ) {
+ auto statusFile =
+ std::find_if( current.files.begin(), current.files.end(), [&file]( const auto& repo ) {
+ return std::any_of( repo.second.begin(), repo.second.end(),
+ [&]( const DiffFile& item ) {
+ return item.file == file.path &&
+ item.report.type == GitStatusType::Untracked;
+ } );
+ } );
+ if ( statusFile == current.files.end() )
+ continue;
+ auto patch = diffUntracked( file.path, projectDir );
+ if ( patch.success() ) {
+ if ( !result.patch.empty() && result.patch.back() != '\n' )
+ result.patch += '\n';
+ result.patch += patch.result;
+ }
+ }
+ result.result.clear();
+ return result;
+}
+
Git::Result Git::commitDiff( const Commit& commit, const CommitFile& file,
const std::string& projectDir ) const {
Result result;
diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp
index fcde7edd9..9ea2eda65 100644
--- a/src/tools/ecode/plugins/git/git.hpp
+++ b/src/tools/ecode/plugins/git/git.hpp
@@ -468,6 +468,8 @@ class Git {
CommitFiles commitFiles( const Commit& commit, const std::string& projectDir = "" ) const;
+ CommitFiles workingTreeFiles( const std::string& projectDir = "" );
+
Result commitDiff( const Commit& commit, const CommitFile& file,
const std::string& projectDir = "" ) const;
diff --git a/src/tools/ecode/plugins/git/githistorymodel.cpp b/src/tools/ecode/plugins/git/githistorymodel.cpp
index 44025547a..5654b576a 100644
--- a/src/tools/ecode/plugins/git/githistorymodel.cpp
+++ b/src/tools/ecode/plugins/git/githistorymodel.cpp
@@ -82,9 +82,11 @@ Variant GitHistoryModel::data( const ModelIndex& index, ModelRole role ) const {
if ( !item )
return {};
if ( role == ModelRole::Class ) {
+ if ( item->type == NodeType::WorkingTree )
+ return Variant( "git_history_working_tree" );
if ( item->type == NodeType::LoadMore || item->type == NodeType::Error )
return Variant( "git_history_action" );
- if ( item->type != NodeType::Commit )
+ if ( item->type != NodeType::Commit && item->type != NodeType::WorkingTree )
return Variant( "git_history_secondary" );
if ( item->commit.isMerge() )
return Variant( "git_history_merge" );
@@ -99,7 +101,7 @@ Variant GitHistoryModel::data( const ModelIndex& index, ModelRole role ) const {
}
if ( role != ModelRole::Display )
return {};
- if ( item->type != NodeType::Commit )
+ if ( item->type != NodeType::Commit && item->type != NodeType::WorkingTree )
return index.column() == Subject ? Variant( &item->message ) : Variant( "" );
switch ( index.column() ) {
case Subject:
@@ -138,6 +140,29 @@ GitHistoryModel::commitNode( Git::Commit commit, Node* parent,
return item;
}
+std::unique_ptr
+GitHistoryModel::workingTreeNode( const Git::Status& status, const std::string& repoName ) const {
+ auto repo = status.files.find( repoName );
+ if ( repo == status.files.end() || repo->second.empty() )
+ return {};
+ size_t changed = 0;
+ size_t staged = 0;
+ for ( const auto& file : repo->second ) {
+ if ( file.report.type == Git::GitStatusType::Staged )
+ ++staged;
+ else
+ ++changed;
+ }
+ auto item = std::make_unique();
+ item->type = NodeType::WorkingTree;
+ item->subject = mPlugin->i18n( "git_working_tree_index", "Working Tree / Index" );
+ item->message = String::format(
+ mPlugin->i18n( "git_working_tree_summary", "%zu changed, %zu staged" ).toUtf8(), changed,
+ staged );
+ item->tooltip = item->subject + String{ "\n" } + item->message;
+ return item;
+}
+
void GitHistoryModel::fillPage( Nodes& nodes, Node* parent, Git::HistoryPage page,
const Git::HistoryQuery& query ) {
for ( auto& commit : page.commits )
@@ -179,8 +204,11 @@ void GitHistoryModel::setRootLoading() {
invalidate();
}
-void GitHistoryModel::setRootPage( Git::HistoryPage page, const Git::HistoryQuery& query ) {
+void GitHistoryModel::setRootPage( Git::HistoryPage page, const Git::HistoryQuery& query,
+ const Git::Status& status, const std::string& repoName ) {
mRoots.clear();
+ if ( auto item = workingTreeNode( status, repoName ) )
+ mRoots.emplace_back( std::move( item ) );
fillPage( mRoots, nullptr, std::move( page ), query );
if ( mRoots.empty() ) {
auto item = std::make_unique();
@@ -191,6 +219,25 @@ void GitHistoryModel::setRootPage( Git::HistoryPage page, const Git::HistoryQuer
invalidate();
}
+void GitHistoryModel::setWorkingTreeStatus( const Git::Status& status,
+ const std::string& repoName ) {
+ auto item = workingTreeNode( status, repoName );
+ const bool hasItem = !mRoots.empty() && mRoots.front()->type == NodeType::WorkingTree;
+ if ( hasItem && !item ) {
+ beginDeleteRows( {}, 0, 0 );
+ mRoots.erase( mRoots.begin() );
+ endDeleteRows();
+ } else if ( !hasItem && item ) {
+ beginInsertRows( {}, 0, 0 );
+ mRoots.insert( mRoots.begin(), std::move( item ) );
+ endInsertRows();
+ } else if ( hasItem && item ) {
+ mRoots.front()->message = std::move( item->message );
+ mRoots.front()->tooltip = std::move( item->tooltip );
+ }
+ invalidate( Model::DontInvalidateIndexes );
+}
+
void GitHistoryModel::setRootError( std::string error ) {
mRoots.clear();
auto item = std::make_unique();
diff --git a/src/tools/ecode/plugins/git/githistorymodel.hpp b/src/tools/ecode/plugins/git/githistorymodel.hpp
index 277d908bb..e4bb5c5ac 100644
--- a/src/tools/ecode/plugins/git/githistorymodel.hpp
+++ b/src/tools/ecode/plugins/git/githistorymodel.hpp
@@ -13,7 +13,7 @@ class GitPlugin;
class GitHistoryModel : public Model {
public:
- enum class NodeType : uint8_t { Commit, LoadMore, Loading, Error, Empty };
+ enum class NodeType : uint8_t { WorkingTree, Commit, LoadMore, Loading, Error, Empty };
enum Column { Subject, Date, Author, Hash };
struct Node {
Git::Commit commit;
@@ -65,7 +65,10 @@ class GitHistoryModel : public Model {
void setRootLoading();
- void setRootPage( Git::HistoryPage page, const Git::HistoryQuery& query );
+ void setRootPage( Git::HistoryPage page, const Git::HistoryQuery& query,
+ const Git::Status& status, const std::string& repoName );
+
+ void setWorkingTreeStatus( const Git::Status& status, const std::string& repoName );
void setRootError( std::string error );
@@ -94,6 +97,8 @@ class GitHistoryModel : public Model {
std::unique_ptr commitNode( Git::Commit commit, Node* parent,
const Git::HistoryQuery& query ) const;
+ std::unique_ptr workingTreeNode( const Git::Status& status,
+ const std::string& repoName ) const;
void fillPage( Nodes& nodes, Node* parent, Git::HistoryPage page,
const Git::HistoryQuery& query );
diff --git a/src/tools/ecode/plugins/git/githistorytreeview.cpp b/src/tools/ecode/plugins/git/githistorytreeview.cpp
index 0e48190e7..bd10206d0 100644
--- a/src/tools/ecode/plugins/git/githistorytreeview.cpp
+++ b/src/tools/ecode/plugins/git/githistorytreeview.cpp
@@ -29,7 +29,8 @@ Sizef GitHistoryTreeViewCell::updateLayout() {
void GitHistoryTreeViewCell::updateCell( Model* model ) {
auto* historyModel = static_cast( model );
const auto* item = historyModel->node( getCurIndex() );
- if ( !item || item->type != GitHistoryModel::NodeType::Commit ) {
+ if ( !item || ( item->type != GitHistoryModel::NodeType::Commit &&
+ item->type != GitHistoryModel::NodeType::WorkingTree ) ) {
mMetadataText.setString( "" );
mTextBox->setTextAlign( UI_HALIGN_LEFT | UI_VALIGN_CENTER );
return;
diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp
index 026a6a869..04725ebbf 100644
--- a/src/tools/ecode/plugins/git/gitplugin.cpp
+++ b/src/tools/ecode/plugins/git/gitplugin.cpp
@@ -739,6 +739,16 @@ void GitPlugin::updateStatus( bool force ) {
[conflictStates = std::move( conflictStates )]( GitPlugin* plugin ) mutable {
const bool selectStatusPanel = plugin->updateConflictSessions( conflictStates );
plugin->updateStatusBarSync();
+ if ( plugin->mHistoryLoaded && plugin->mHistoryModel ) {
+ Git::Status status;
+ {
+ Lock l( plugin->mGitStatusMutex );
+ status = plugin->mGitStatus;
+ }
+ const std::string repo = plugin->repoSelected();
+ plugin->mHistoryModel->setWorkingTreeStatus(
+ status, plugin->mGit->repoName( repo, true, repo ) );
+ }
if ( selectStatusPanel && plugin->mPanelSwicher )
plugin->mPanelSwicher->getListBox()->setSelected( 1 );
} );
@@ -2437,18 +2447,22 @@ void GitPlugin::reloadHistory() {
mThreadPool->run(
[git = std::move( git ), lifetime, repo, generation, query]() mutable {
auto page = git->history( query, repo );
- lifetime.run(
- [repo, generation, query, page = std::move( page )]( GitPlugin* plugin ) mutable {
- if ( plugin->mShuttingDown || generation != plugin->mHistoryGeneration ||
- repo != plugin->repoSelected() )
- return;
- plugin->mHistoryLoaded = true;
- if ( page.success() )
- plugin->mHistoryModel->setRootPage( std::move( page ), query );
- else
- plugin->mHistoryModel->setRootError( std::move( page.result ) );
- plugin->focusDetachedHistory();
- } );
+ auto status = git->status( false, repo );
+ auto repoName = git->repoName( repo, true, repo );
+ lifetime.run( [repo, generation, query, page = std::move( page ),
+ status = std::move( status ),
+ repoName = std::move( repoName )]( GitPlugin* plugin ) mutable {
+ if ( plugin->mShuttingDown || generation != plugin->mHistoryGeneration ||
+ repo != plugin->repoSelected() )
+ return;
+ plugin->mHistoryLoaded = true;
+ if ( page.success() )
+ plugin->mHistoryModel->setRootPage( std::move( page ), query, status,
+ repoName );
+ else
+ plugin->mHistoryModel->setRootError( std::move( page.result ) );
+ plugin->focusDetachedHistory();
+ } );
},
[this]( auto ) { --mRunningHistoryRequests; } );
}
@@ -2626,9 +2640,12 @@ void GitPlugin::showGitHistory( const Git::Commit* commit ) {
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 );
+ if ( const auto* node = mHistoryModel ? mHistoryModel->node( index ) : nullptr; node ) {
+ if ( node->type == GitHistoryModel::NodeType::WorkingTree )
+ openWorkingTreeDetails( true );
+ else if ( node->type == GitHistoryModel::NodeType::Commit )
+ openDetachedCommitDetails( node->commit );
+ }
} );
auto* view = mDetachedHistory.view;
mDetachedHistory.closeConnection =
@@ -2666,16 +2683,24 @@ void GitPlugin::focusDetachedHistory() {
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 );
+ if ( const auto* node = mHistoryModel->node( index ); node ) {
+ if ( node->type == GitHistoryModel::NodeType::WorkingTree )
+ openWorkingTreeDetails( true );
+ else if ( 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;
+ ensureDetachedCommitDetailsHost();
+ mDetachedHistory.details.openCommitDetails( *this, commit, true );
+}
+
+void GitPlugin::ensureDetachedCommitDetailsHost() {
if ( !mDetachedHistory.detailsHost ) {
mDetachedHistory.detailsHost = UILinearLayout::NewVertical();
mDetachedHistory.detailsHost->setId( "git_history_detached_details" );
@@ -2684,30 +2709,38 @@ void GitPlugin::openDetachedCommitDetails( const Git::Commit& commit ) {
mDetachedHistory.detailsHost->setParent( mDetachedHistory.view );
mDetachedHistory.view->setSplitPartition( StyleSheetLength( "55%" ) );
}
- mDetachedHistory.details.openCommitDetails( *this, commit, true );
}
void GitPlugin::openCommitDetails( const Git::Commit& commit ) {
mCommitDetails.openCommitDetails( *this, commit, false );
}
+void GitPlugin::openWorkingTreeDetails( bool detached ) {
+ Git::Commit commit;
+ commit.subject = i18n( "git_working_tree_index", "Working Tree / Index" );
+ if ( detached )
+ ensureDetachedCommitDetailsHost();
+ ( detached ? mDetachedHistory.details : mCommitDetails )
+ .openCommitDetails( *this, commit, detached, true );
+}
+
void GitPlugin::CommitDetailsState::openCommitDetails( GitPlugin& plugin, const Git::Commit& commit,
- bool detached ) {
- if ( commit.hash.empty() )
+ bool detached, bool isWorkingTree ) {
+ if ( commit.hash.empty() && !isWorkingTree )
return;
const std::string selectedRepo = plugin.repoSelected();
const Uint64 requestGeneration = ++generation;
this->commit = commit;
+ workingTree = isWorkingTree;
repo = selectedRepo;
const bool createView = !view;
if ( createView ) {
view = plugin.getUISceneNode()->loadLayoutFromString( R"xml(
-
+
-
+
setText( String::fromUtf8( commit.subject ) );
- author->setText( String::fromUtf8( commit.authorName ) );
- dateEmail->setText( Sys::epochToString( commit.commitTime ) + " - " +
- String::fromUtf8( commit.authorEmail ) );
+ author->setText( isWorkingTree ? plugin.i18n( "git_uncommitted_changes", "Uncommitted changes" )
+ : String::fromUtf8( commit.authorName ) );
+ dateEmail->setText( isWorkingTree ? String{}
+ : Sys::epochToString( commit.commitTime ) + " - " +
+ String::fromUtf8( commit.authorEmail ) );
messageBody.clear();
messageExpanded = false;
message->setText( "" );
@@ -2835,13 +2870,16 @@ void GitPlugin::CommitDetailsState::openCommitDetails( GitPlugin& plugin, const
filesToggle->setIcon( icon->createDrawable( PixelDensity::dpToPxI( 12 ) ) );
url.clear();
gitHub->setVisible( false );
+ view->find( "git_commit_sha" )->setVisible( !isWorkingTree );
+ view->find( "git_commit_show_history" )->setVisible( !isWorkingTree );
diff = nullptr;
diffContainer->closeAllChildren();
auto* shaButton = view->find( "git_commit_sha" );
shaButton->setTooltipText( String::format(
plugin.i18n( "git_copy_commit_sha", "Copy Commit SHA\n%s" ).toUtf8(), commit.hash ) );
- const std::string tabName = commit.shortHash + " " + commit.subject;
+ const std::string tabName =
+ isWorkingTree ? commit.subject : commit.shortHash + " " + commit.subject;
if ( !detached && !plugin.mManager->getSplitter()->ownedWidgetExists( view ) ) {
auto tab = plugin.mManager->getSplitter()->createWidget( view, tabName, true ).first;
if ( tab )
@@ -2861,16 +2899,20 @@ void GitPlugin::CommitDetailsState::loadCommitFiles( GitPlugin& plugin, bool det
const Uint64 generation = this->generation;
const std::string repo = this->repo;
const Git::Commit commit = this->commit;
+ const bool workingTree = this->workingTree;
auto git = plugin.mGit;
const auto lifetime = plugin.mLifetime.weakHandle();
- plugin.runAsyncTask( [git = std::move( git ), lifetime, generation, repo, commit, detached] {
- auto result = git->commitFiles( commit, repo );
- lifetime.run( [generation, repo, commit, detached,
+ plugin.runAsyncTask( [git = std::move( git ), lifetime, generation, repo, commit, detached,
+ workingTree] {
+ auto result =
+ workingTree ? git->workingTreeFiles( repo ) : git->commitFiles( commit, repo );
+ lifetime.run( [generation, repo, commit, detached, workingTree,
result = std::move( result )]( GitPlugin* plugin ) mutable {
auto& details = detached ? plugin->mDetachedHistory.details : plugin->mCommitDetails;
if ( plugin->mShuttingDown || generation != details.generation ||
repo != details.repo || repo != plugin->repoSelected() ||
- commit.hash != details.commit.hash || !details.view ||
+ commit.hash != details.commit.hash || workingTree != details.workingTree ||
+ !details.view ||
( detached
? !plugin->mDetachedHistory.view
: !plugin->mManager->getSplitter()->ownedWidgetExists( details.view ) ) )
@@ -2932,6 +2974,7 @@ void GitPlugin::CommitDetailsState::loadCommitFiles( GitPlugin& plugin, bool det
SizePolicy::MatchParent );
details.diff->setParent( details.diffContainer );
for ( auto* diff : UIDiffView::multiFileDiffViews( details.diff ) ) {
+ diff->setInteractiveFileHeader( true );
if ( const auto* scheme = plugin->getPluginContext()->getCurrentColorScheme() )
diff->setSyntaxColorScheme( *scheme );
}
@@ -3185,8 +3228,9 @@ void GitPlugin::buildSidePanelTab() {
-
-
+
+
+
@@ -3227,6 +3271,7 @@ void GitPlugin::buildSidePanelTab() {
mTabContents->find( "branch_push" )->onClick( [this]( auto ) { push( repoSelected() ); } );
mTabContents->find( "branch_add" )->onClick( [this]( auto ) { branchCreate(); } );
mTabContents->find( "git_history_refresh" )->onClick( [this]( auto ) { reloadHistory(); } );
+ mTabContents->find( "git_history_open" )->onClick( [this]( auto ) { showGitHistory(); } );
mHistoryRefDropDown->getListView()->setColumnsVisible( { 0 } );
mHistoryRefDropDown->getListView()->setAutoExpandOnSingleColumn( true );
mHistoryRefDropDown->on( Event::OnItemSelected, [this]( const Event* ) {
@@ -3344,9 +3389,12 @@ void GitPlugin::buildSidePanelTab() {
activateHistoryIndex( modelEvent->getModelIndex(), true );
else if ( modelEvent->getModelEventType() == ModelEventType::Open ) {
activateHistoryIndex( modelEvent->getModelIndex(), false );
- if ( const auto* node = mHistoryModel->node( modelEvent->getModelIndex() );
- node && node->type == GitHistoryModel::NodeType::Commit )
- openCommitDetails( node->commit );
+ if ( const auto* node = mHistoryModel->node( modelEvent->getModelIndex() ); node ) {
+ if ( node->type == GitHistoryModel::NodeType::WorkingTree )
+ openWorkingTreeDetails( false );
+ else if ( node->type == GitHistoryModel::NodeType::Commit )
+ openCommitDetails( node->commit );
+ }
} else if ( modelEvent->getModelEventType() == ModelEventType::OpenMenu )
openHistoryMenu( modelEvent->getModelIndex() );
} );
@@ -3354,7 +3402,9 @@ void GitPlugin::buildSidePanelTab() {
if ( !mHistoryModel )
return;
const auto* node = mHistoryModel->node( index );
- if ( node && node->type == GitHistoryModel::NodeType::Commit )
+ if ( node && node->type == GitHistoryModel::NodeType::WorkingTree )
+ openWorkingTreeDetails( false );
+ else if ( node && node->type == GitHistoryModel::NodeType::Commit )
openCommitDetails( node->commit );
} );
diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp
index 45814d724..8f298fca3 100644
--- a/src/tools/ecode/plugins/git/gitplugin.hpp
+++ b/src/tools/ecode/plugins/git/gitplugin.hpp
@@ -190,8 +190,10 @@ class GitPlugin : public PluginBase {
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 );
+ void openCommitDetails( GitPlugin& plugin, const Git::Commit& commit, bool detached,
+ bool workingTree = false );
void loadCommitFiles( GitPlugin& plugin, bool detached );
@@ -216,6 +218,7 @@ class GitPlugin : public PluginBase {
viewMode = Tools::UIDiffView::ViewMode::Unified;
messageExpanded = false;
filesCollapsed = false;
+ workingTree = false;
}
};
struct DetachedHistoryState {
@@ -374,6 +377,7 @@ class GitPlugin : public PluginBase {
void activateHistoryIndex( const ModelIndex& index, bool expand );
void openCommitDetails( const Git::Commit& commit );
+ void openWorkingTreeDetails( bool detached );
void showGitHistory( const Git::Commit* commit = nullptr );
@@ -382,6 +386,7 @@ class GitPlugin : public PluginBase {
void openHistoryMenu( const ModelIndex& index );
void openDetachedCommitDetails( const Git::Commit& commit );
+ void ensureDetachedCommitDetailsHost();
std::string detachedHistoryTitle();