mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-09-22 13:01:05 +03:00
Fixed the autocomplete crash, root cause: getDocumentSymbols() accessed mDocs from a worker thread while document lifecycle callbacks could concurrently modify the same unordered_set, corrupting its internal state.
Added a synthetic “Working Tree / Index” first row whenever the selected repository has local or staged changes. Displays separate changed and staged counts. Updates dynamically from normal status refreshes without reloading commit history. Selecting it opens the shared details view in “Uncommitted changes” mode. Shows tracked changes from HEAD through the working tree, plus untracked-file patches. Supports both sidebar and detached History views. The History shortcut focuses working changes when present, otherwise the newest commit. Added the History button beside Refresh. Added consistent 2dp spacing between the ref dropdown and both buttons. Added translations for the new labels.
This commit is contained in:
@@ -1263,6 +1263,9 @@ Für sichtbare Änderung ecode neu starten.</string>
|
||||
<string name="git_history_refresh">Aktualisieren</string>
|
||||
<string name="git_history_load_older">Ältere Commits laden</string>
|
||||
<string name="git_history_empty">Noch keine Commits</string>
|
||||
<string name="git_working_tree_index">Arbeitsbaum / Index</string>
|
||||
<string name="git_working_tree_summary">%zu geändert, %zu vorgemerkt</string>
|
||||
<string name="git_uncommitted_changes">Nicht übernommene Änderungen</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>
|
||||
|
||||
@@ -1248,6 +1248,9 @@ Restart ecode to see the changes.</string>
|
||||
<string name="git_history_refresh">Refresh</string>
|
||||
<string name="git_history_load_older">Load older commits</string>
|
||||
<string name="git_history_empty">No commits yet</string>
|
||||
<string name="git_working_tree_index">Working Tree / Index</string>
|
||||
<string name="git_working_tree_summary">%zu changed, %zu staged</string>
|
||||
<string name="git_uncommitted_changes">Uncommitted 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>
|
||||
|
||||
@@ -1247,6 +1247,9 @@ Redémarrer ecode pour voir les changements.</string>
|
||||
<string name="git_history_refresh">Actualiser</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_working_tree_index">Arbre de travail / Index</string>
|
||||
<string name="git_working_tree_summary">%zu modifié(s), %zu indexé(s)</string>
|
||||
<string name="git_uncommitted_changes">Modifications non validées</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>
|
||||
|
||||
@@ -1052,6 +1052,9 @@ file in the directory tree.</string>
|
||||
<string name="git_history_refresh">刷新</string>
|
||||
<string name="git_history_load_older">加载更早的提交</string>
|
||||
<string name="git_history_empty">尚无提交</string>
|
||||
<string name="git_working_tree_index">工作树 / 索引</string>
|
||||
<string name="git_working_tree_summary">%zu 个更改,%zu 个已暂存</string>
|
||||
<string name="git_uncommitted_changes">未提交的更改</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>
|
||||
|
||||
@@ -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<DiffLine>& getDiffLines() const { return mLines; }
|
||||
|
||||
const std::vector<size_t>& 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<SyntaxDefinition> 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<UIDiffEditorPlugin>& 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
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
#include <eepp/ui/tools/uidiffview.hpp>
|
||||
#include <eepp/ui/tools/uidocfindreplace.hpp>
|
||||
#include <eepp/ui/tools/uiimageviewer.hpp>
|
||||
#include <eepp/ui/uiicon.hpp>
|
||||
#include <eepp/ui/uiimage.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
#include <eepp/ui/uiscrollbar.hpp>
|
||||
#include <eepp/ui/uiscrollview.hpp>
|
||||
#include <eepp/ui/uistyle.hpp>
|
||||
#include <eepp/ui/uithememanager.hpp>
|
||||
#include <eepp/window/window.hpp>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -747,8 +747,11 @@ void AutoCompletePlugin::onRegister( UICodeEditor* editor ) {
|
||||
std::vector<Uint32> 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<TextDocument>& 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;
|
||||
}
|
||||
|
||||
@@ -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<char>( 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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::Node>
|
||||
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<Node>();
|
||||
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<Node>();
|
||||
@@ -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<Node>();
|
||||
|
||||
@@ -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<Node> commitNode( Git::Commit commit, Node* parent,
|
||||
const Git::HistoryQuery& query ) const;
|
||||
std::unique_ptr<Node> workingTreeNode( const Git::Status& status,
|
||||
const std::string& repoName ) const;
|
||||
|
||||
void fillPage( Nodes& nodes, Node* parent, Git::HistoryPage page,
|
||||
const Git::HistoryQuery& query );
|
||||
|
||||
@@ -29,7 +29,8 @@ Sizef GitHistoryTreeViewCell::updateLayout() {
|
||||
void GitHistoryTreeViewCell::updateCell( Model* model ) {
|
||||
auto* historyModel = static_cast<GitHistoryModel*>( 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;
|
||||
|
||||
@@ -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(
|
||||
<vbox id="git_commit_details" lw="mp" lh="mp">
|
||||
<vbox lw="mp" lh="wc" padding="12dp">
|
||||
<vbox lw="mp" lh="wc" padding="4dp">
|
||||
<hbox lw="mp" lh="wc">
|
||||
<TextView id="git_commit_author" lw="wc" lh="wc" word-wrap="true"
|
||||
focusable="false" />
|
||||
<TextView id="git_commit_author" lw="wc" lh="wc" focusable="false" />
|
||||
<PushButton id="git_commit_message_toggle"
|
||||
text="@string(git_expand_commit_description, Expand Commit Description)"
|
||||
icon="icon(unfold, 12dp)" text-as-fallback="true"
|
||||
@@ -2819,9 +2852,11 @@ void GitPlugin::CommitDetailsState::openCommitDetails( GitPlugin& plugin, const
|
||||
}
|
||||
|
||||
subject->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<UIPushButton>( "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() {
|
||||
</vbox>
|
||||
<vbox id="git_history" lw="mp" lh="mp">
|
||||
<hbox lw="mp" lh="wc" padding="4dp">
|
||||
<DropDownModelList id="git_history_ref" lw="0" lw8="1" lh="wc" menu-width-mode="expand-if-needed" />
|
||||
<PushButton id="git_history_refresh" text="@string(git_history_refresh, Refresh)" icon="icon(refresh, 12dp)" text-as-fallback="true" />
|
||||
<DropDownModelList id="git_history_ref" lw="0" lw8="1" lh="wc" margin-right="2dp" menu-width-mode="expand-if-needed" />
|
||||
<PushButton id="git_history_refresh" text="@string(git_history_refresh, Refresh)" icon="icon(refresh, 12dp)" text-as-fallback="true" margin-right="2dp" />
|
||||
<PushButton id="git_history_open" tooltip="@string(git_show_in_history, Show in Git History)" icon="icon(history, 12dp)" />
|
||||
</hbox>
|
||||
<GitHistoryTreeView id="git_history_tree" lw="mp" lh="0" lw8="1" />
|
||||
</vbox>
|
||||
@@ -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 );
|
||||
} );
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user