From b8d79fe493008a2afbcc9d865c4125690e039e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Thu, 15 Feb 2024 19:42:24 -0300 Subject: [PATCH] Menu option for Git diff for staged files and repo. Improved visually git file modified indicator. Minor fix in auto complete. Added Main Menu shortcut on welcome screen. Added String::numberClean. --- include/eepp/core/string.hpp | 9 ++ src/eepp/core/string.cpp | 23 +++- src/eepp/ui/uinodedrawable.cpp | 2 +- src/tools/ecode/applayout.xml.hpp | 14 -- src/tools/ecode/ecode.cpp | 3 +- src/tools/ecode/iconmanager.cpp | 6 +- .../autocomplete/autocompleteplugin.cpp | 3 +- src/tools/ecode/plugins/git/git.cpp | 15 ++ src/tools/ecode/plugins/git/git.hpp | 4 + src/tools/ecode/plugins/git/gitplugin.cpp | 129 ++++++++++++++---- src/tools/ecode/plugins/git/gitplugin.hpp | 6 +- src/tools/ecode/uiwelcomescreen.cpp | 26 ++-- 12 files changed, 179 insertions(+), 61 deletions(-) diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index b18117e6a..cf0a4fb80 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -298,6 +298,15 @@ class EE_API String { /** Removes the numbers at the end of the string */ static std::string removeNumbersAtEnd( std::string txt ); + /** Removes the trailing 0 and . in a string number */ + static std::string_view numberClean( std::string_view strNumber ); + + /** Removes the trailing 0 and . in a string number */ + static std::string numberClean( const std::string& strNumber ); + + /** Removes the trailing 0 and . in a string number */ + static void numberCleanInPlace( std::string& strNumber ); + /** Searchs the position of the corresponding close bracket in a string. */ static std::size_t findCloseBracket( const std::string& string, std::size_t startOffset, char openBracket, char closeBracket ); diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index d379a350a..6c7cfbd5a 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -892,7 +892,24 @@ std::string String::randString( size_t len, std::string dictionary ) { return dictionary.substr( 0, len ); } -void numberClean( std::string& strNumber ) { +std::string_view String::numberClean( std::string_view strNumber ) { + while ( strNumber.back() == '0' ) + strNumber.remove_suffix( 1 ); + if ( strNumber.back() == '.' ) + strNumber.remove_suffix( 1 ); + return strNumber; +} + +std::string String::numberClean( const std::string& number ) { + std::string strNumber( number ); + while ( strNumber.back() == '0' ) + strNumber.pop_back(); + if ( strNumber.back() == '.' ) + strNumber.pop_back(); + return strNumber; +} + +void String::numberCleanInPlace( std::string& strNumber ) { while ( strNumber.back() == '0' ) strNumber.pop_back(); if ( strNumber.back() == '.' ) @@ -902,14 +919,14 @@ void numberClean( std::string& strNumber ) { std::string String::fromFloat( const Float& value, const std::string& append, const std::string& prepend ) { std::string val( toString( value ) ); - numberClean( val ); + numberCleanInPlace( val ); return prepend + val + append; } std::string String::fromDouble( const double& value, const std::string& append, const std::string& prepend ) { std::string val( toString( value ) ); - numberClean( val ); + numberCleanInPlace( val ); return prepend + val + append; } diff --git a/src/eepp/ui/uinodedrawable.cpp b/src/eepp/ui/uinodedrawable.cpp index fc43358a2..cf196c9c0 100644 --- a/src/eepp/ui/uinodedrawable.cpp +++ b/src/eepp/ui/uinodedrawable.cpp @@ -595,7 +595,7 @@ Vector2f UINodeDrawable::LayerDrawable::calcPosition( const std::string& positio position.y += ( pos[yFloatIndex] == "bottom" ) ? -yl2Val : yl2Val; } - return position; + return position.round(); } const std::string& UINodeDrawable::LayerDrawable::getSizeEq() const { diff --git a/src/tools/ecode/applayout.xml.hpp b/src/tools/ecode/applayout.xml.hpp index c78ffdd83..304f14d93 100644 --- a/src/tools/ecode/applayout.xml.hpp +++ b/src/tools/ecode/applayout.xml.hpp @@ -359,20 +359,6 @@ TableView#locate_bar_table > tableview::row:selected > tableview::cell:nth-child color: #d48838; } -.theme-primary > tableview::cell::text, -.theme-primary > treeview::cell::text, -.theme-primary > listview::cell::text, -.primary { - color: var(--font-highlight); -} - -.theme-clear > tableview::cell::text, -.theme-clear > treeview::cell::text, -.theme-clear > listview::cell::text, -.clear { - color: var(--font); -} - Anchor.success:hover, Anchor.error:hover { color: var(--primary); diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index be98db0e0..7854baaad 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -1054,7 +1054,8 @@ void App::setUIScaleFactor() { i18n( "set_ui_scale_factor", "Set the UI scale factor (pixel density):\nMinimum value is " "1, and maximum 6. Requires restart." ) ); msgBox->setTitle( mWindowTitle ); - msgBox->getTextInput()->setText( String::format( "%.2f", mConfig.windowState.pixelDensity ) ); + msgBox->getTextInput()->setText( + String::numberClean( String::format( "%.2f", mConfig.windowState.pixelDensity ) ) ); msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); msgBox->showWhenReady(); msgBox->on( Event::OnConfirm, [&, msgBox]( const Event* ) { diff --git a/src/tools/ecode/iconmanager.cpp b/src/tools/ecode/iconmanager.cpp index 6db2f631c..c34dcbe5e 100644 --- a/src/tools/ecode/iconmanager.cpp +++ b/src/tools/ecode/iconmanager.cpp @@ -220,7 +220,11 @@ void IconManager::init( UISceneNode* sceneNode, FontTrueType* iconFont, FontTrue { "diff-single", 0xec22 }, { "remove", 0xeb3b }, { "tag", 0xea66 }, - { "globe", 0xeb01 } }; + { "globe", 0xeb01 }, + { "circle-filled", 0xea71 }, + { "circle", 0xeabc }, + { "diff-multiple", 0xec23 }, + }; for ( const auto& icon : codIcons ) iconTheme->add( UIGlyphIcon::New( icon.first, codIconFont, icon.second ) ); diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index 8290c7959..5588fd49a 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -134,7 +134,8 @@ void AutoCompletePlugin::onRegister( UICodeEditor* editor ) { editor->addEventListener( Event::OnCursorPosChange, [this, editor]( const Event* ) { if ( !mReplacing ) resetSuggestions( editor ); - else if ( mSignatureHelpVisible && mSignatureHelpPosition.isValid() && + + if ( mSignatureHelpVisible && mSignatureHelpPosition.isValid() && !editor->getDocument().getSelection().hasSelection() && mSignatureHelpPosition.line() != editor->getDocument().getSelection().end().line() ) { diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp index 8ff89cbf5..4a33baa6c 100644 --- a/src/tools/ecode/plugins/git/git.cpp +++ b/src/tools/ecode/plugins/git/git.cpp @@ -241,6 +241,21 @@ Git::Result Git::reset( std::vector files, const std::string& proje return gitSimple( String::format( "reset -q HEAD -- %s", asList( files ) ), projectDir ); } +Git::Result Git::diff( DiffMode mode, const std::string& projectDir ) { + std::string modeTxt; + switch ( mode ) { + case DiffHead: { + modeTxt = "HEAD"; + break; + } + case DiffStaged: { + modeTxt = "--staged"; + break; + } + } + return gitSimple( String::format( "diff %s", modeTxt ), projectDir ); +} + Git::Result Git::diff( const std::string& file, bool isStaged, const std::string& projectDir ) { return gitSimple( String::format( "diff%s \"%s\"", isStaged ? " --staged" : "", file ), projectDir ); diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp index a10b52c37..e76181291 100644 --- a/src/tools/ecode/plugins/git/git.hpp +++ b/src/tools/ecode/plugins/git/git.hpp @@ -227,6 +227,8 @@ class Git { const char* typeStr() const { return refTypeToString( type ); } }; + enum DiffMode { DiffHead, DiffStaged }; + Git( const std::string& projectDir = "", const std::string& gitPath = "" ); int git( const std::string& args, const std::string& projectDir, std::string& buf ) const; @@ -253,6 +255,8 @@ class Git { Result reset( std::vector files, const std::string& projectDir = "" ); + Result diff( DiffMode mode, const std::string& projectDir = "" ); + Result diff( const std::string& file, bool isStaged, const std::string& projectDir = "" ); Result createBranch( const std::string& branchName, bool checkout = false, diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index e182df39a..c5f011eb4 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -23,6 +23,8 @@ using namespace EE::UI; using namespace EE::UI::Doc; +using namespace std::literals; + using json = nlohmann::json; #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) #define GIT_THREADED 1 @@ -32,6 +34,8 @@ using json = nlohmann::json; namespace ecode { +static constexpr auto DEFAULT_HIGHLIGHT_COLOR = "var(--font-highlight)"sv; + std::string GitPlugin::statusTypeToString( Git::GitStatusType type ) { switch ( type ) { case Git::GitStatusType::Untracked: @@ -64,7 +68,8 @@ Plugin* GitPlugin::NewSync( PluginManager* pluginManager ) { return eeNew( GitPlugin, ( pluginManager, true ) ); } -GitPlugin::GitPlugin( PluginManager* pluginManager, bool sync ) : PluginBase( pluginManager ) { +GitPlugin::GitPlugin( PluginManager* pluginManager, bool sync ) : + PluginBase( pluginManager ), mHighlightStyleColor( DEFAULT_HIGHLIGHT_COLOR ) { if ( sync ) { load( pluginManager ); } else { @@ -152,11 +157,11 @@ void GitPlugin::load( PluginManager* pluginManager ) { updateConfigFile = true; } - if ( config.contains( "filetree_highlight_style" ) ) - mHighlightStyle = - config.value( "filetree_highlight_style", "color: var(--font-highlight);" ); - else { - config["filetree_highlight_style"] = mHighlightStyle; + if ( config.contains( "filetree_highlight_style_color" ) ) { + mHighlightStyleColor = + config.value( "filetree_highlight_style_color", DEFAULT_HIGHLIGHT_COLOR ); + } else { + config["filetree_highlight_style_color"] = mHighlightStyleColor; updateConfigFile = true; } @@ -237,19 +242,14 @@ void GitPlugin::initModelStyler() { mModelStylerId = projectView->getModel()->subsribeModelStyler( [this]( const ModelIndex& index, const void* data ) -> Variant { static const char* STYLE_MODIFIED = "git_highlight_style"; - static const char* STYLE_NONE = "theme-clear"; + static const char* STYLE_NONE = "git_highlight_style_clear"; auto model = static_cast( index.model() ); - Lock l( mGitStatusMutex ); - for ( const auto& status : mGitStatus.files ) { - for ( const auto& file : status.second ) { - auto node = static_cast( data ); - std::string_view rp = model->getNodeRelativePath( node ); - std::string_view filesv( file.file ); - if ( rp.size() <= file.file.size() && rp == filesv.substr( 0, rp.size() ) ) { - return Variant( STYLE_MODIFIED ); - } - } - } + auto node = static_cast( data ); + Lock l( mGitStatusFileCacheMutex ); + auto found = + mGitStatusFilesCache.find( std::string{ model->getNodeRelativePath( node ) } ); + if ( found != mGitStatusFilesCache.end() ) + return Variant( STYLE_MODIFIED ); return Variant( STYLE_NONE ); } ); } @@ -389,6 +389,26 @@ void GitPlugin::updateStatus( bool force ) { prevGitStatus = mGitStatus; } Git::Status newGitStatus = mGit->status( mStatusRecurseSubmodules ); + UnorderedSet cache; + + for ( const auto& status : newGitStatus.files ) { + for ( const auto& file : status.second ) { + std::string p( FileSystem::fileRemoveFileName( file.file ) ); + std::string lp; + while ( p != lp ) { + cache.insert( p ); + lp = p; + p = FileSystem::removeLastFolderFromPath( p ); + } + cache.insert( file.file ); + } + } + + { + Lock l( mGitStatusFileCacheMutex ); + mGitStatusFilesCache = std::move( cache ); + } + { Lock l( mGitStatusMutex ); mGitStatus = std::move( newGitStatus ); @@ -996,6 +1016,35 @@ void GitPlugin::openFile( const std::string& file ) { } ); } +void GitPlugin::diff( const Git::DiffMode mode, const std::string& repoPath ) { + mThreadPool->run( [this, mode, repoPath] { + auto res = mGit->diff( mode, repoPath ); + if ( res.fail() ) + return; + + std::string repoName = this->repoName( repoPath ); + getUISceneNode()->runOnMainThread( [this, mode, res, repoName] { + auto ret = mManager->getSplitter()->createEditorInNewTab(); + auto doc = ret.second->getDocumentRef(); + std::string modeName; + switch ( mode ) { + case Git::DiffHead: { + modeName = "HEAD"; + break; + } + case Git::DiffStaged: + modeName = "staged"; + break; + } + doc->setDefaultFileName( repoName + "-" + modeName + ".diff" ); + doc->setSyntaxDefinition( SyntaxDefinitionManager::instance()->getByLSPName( "diff" ) ); + doc->textInput( res.result, false ); + doc->moveToStartOfDoc(); + doc->resetUndoRedo(); + } ); + } ); +} + void GitPlugin::diff( const std::string& file, bool isStaged ) { mThreadPool->run( [this, file, isStaged] { auto res = mGit->diff( fixFilePath( file ), isStaged, mGit->repoPath( file ) ); @@ -1316,15 +1365,22 @@ void GitPlugin::buildSidePanelTab() { #git_status_tree ScrollBar:focus-within { opacity: 1; } - .git_highlight_style > tableview::cell::text, - .git_highlight_style > treeview::cell::text, - .git_highlight_style > listview::cell::text, - .git_highlight_style { - %s + .git_highlight_style > treeview::cell::text { + color: %s; } - treeview::row:hover treeview::cell.git_highlight_style { + treeview::row treeview::cell.git_highlight_style_clear, + treeview::row:selected .git_highlight_style > treeview::cell::text, + treeview::row:selected .git_highlight_style > treeview::cell::text { color: var(--font); } + .git_highlight_style > treeview::cell::icon { + foreground-image: icon(circle, 12dpru), icon(circle-filled, 12dpru); + foreground-position: 80%% 80%%, 80%% 80%%; + foreground-tint: black, %s; + } + .git_highlight_style_clear > treeview::cell::icon { + foreground-image: none, none; + } @@ -1350,8 +1406,12 @@ void GitPlugin::buildSidePanelTab() { )html"; UIIcon* icon = findIcon( "source-control" ); + std::string color = + !mHighlightStyleColor.empty() && Color::isColorString( mHighlightStyleColor ) + ? mHighlightStyleColor + : std::string{ DEFAULT_HIGHLIGHT_COLOR }; UIWidget* node = - getUISceneNode()->loadLayoutFromString( String::format( STYLE, mHighlightStyle ) ); + getUISceneNode()->loadLayoutFromString( String::format( STYLE, color, color ) ); mTab = mSidePanel->add( i18n( "source_control", "Source Control" ), node, icon ? icon->getSize( PixelDensity::dpToPx( 12 ) ) : nullptr ); mTab->setId( "source_control" ); @@ -1488,17 +1548,22 @@ void GitPlugin::buildSidePanelTab() { menu->setId( "git_status_type_menu" ); if ( status->type == Git::GitStatusType::Staged ) { - menuAdd( menu, "git-commit", "Commit", "git-commit" ); - menuAdd( menu, "git-unstage-all", "Unstage All" ); + menuAdd( menu, "git-commit", i18n( "git_commit", "Commit" ), + "git-commit" ); + menuAdd( menu, "git-diff-staged", + i18n( "git_diff_staged", "Diff Staged" ), "diff-multiple" ); + menuAdd( menu, "git-unstage-all", + i18n( "git_unstage_all", "Unstage All" ) ); } if ( status->type == Git::GitStatusType::Untracked || status->type == Git::GitStatusType::Changed ) - menuAdd( menu, "git-stage-all", "Stage All" ); + menuAdd( menu, "git-stage-all", i18n( "git_stage_all", "Stage All" ) ); if ( status->type == Git::GitStatusType::Changed ) { menu->addSeparator(); - menuAdd( menu, "git-discard-all", "Discard All" ); + menuAdd( menu, "git-discard-all", + i18n( "git_discard_all", "Discard All" ) ); } menu->on( Event::OnItemClicked, [this, model, @@ -1519,6 +1584,8 @@ void GitPlugin::buildSidePanelTab() { } else if ( id == "git-discard-all" ) { discard( model->getFiles( repoFullName( repoPath ), (Uint32)Git::GitStatusType::Changed ) ); + } else if ( id == "git-diff-staged" ) { + diff( Git::DiffMode::DiffStaged, repoPath ); } } ); @@ -1561,6 +1628,8 @@ void GitPlugin::buildSidePanelTab() { menuAdd( menu, "git-pull", i18n( "git_pull", "Pull" ), "repo-pull" ); menuAdd( menu, "git-push", i18n( "git_push", "Push" ), "repo-push" ); menuAdd( menu, "git-stash", i18n( "git_stash_all", "Stash All" ), "git-stash" ); + menuAdd( menu, "git-diff-head", i18n( "git_diff_head", "Diff HEAD" ), + "diff-multiple" ); menu->on( Event::OnItemClicked, [this, model, repoName, repoPath]( const Event* event ) { @@ -1582,6 +1651,8 @@ void GitPlugin::buildSidePanelTab() { stage( model->getFiles( repoName, (Uint32)Git::GitStatusType::Untracked | (Uint32)Git::GitStatusType::Changed ) ); + } else if ( id == "git-diff-head" ) { + diff( Git::DiffMode::DiffHead, repoPath ); } } ); diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp index 55c032b10..9f84472f7 100644 --- a/src/tools/ecode/plugins/git/gitplugin.hpp +++ b/src/tools/ecode/plugins/git/gitplugin.hpp @@ -81,9 +81,10 @@ class GitPlugin : public PluginBase { std::unordered_map mGitBranches; Git::Status mGitStatus; std::vector> mRepos; + UnorderedSet mGitStatusFilesCache; std::string mProjectPath; std::string mRepoSelected; - std::string mHighlightStyle{ "color: var(--font-highlight);" }; + std::string mHighlightStyleColor; Time mRefreshFreq{ Seconds( 5 ) }; bool mGitFound{ false }; @@ -118,6 +119,7 @@ class GitPlugin : public PluginBase { Clock mLastBranchesUpdate; Mutex mGitBranchMutex; Mutex mGitStatusMutex; + Mutex mGitStatusFileCacheMutex; Mutex mRepoMutex; Mutex mReposMutex; String mLastCommitMsg; @@ -178,6 +180,8 @@ class GitPlugin : public PluginBase { void discard( const std::string& file ); + void diff( const Git::DiffMode mode, const std::string& repoPath ); + void diff( const std::string& file, bool isStaged ); void openFile( const std::string& file ); diff --git a/src/tools/ecode/uiwelcomescreen.cpp b/src/tools/ecode/uiwelcomescreen.cpp index 96bacb279..62467db26 100644 --- a/src/tools/ecode/uiwelcomescreen.cpp +++ b/src/tools/ecode/uiwelcomescreen.cpp @@ -1,5 +1,5 @@ -#include "uiwelcomescreen.hpp" #include "ecode.hpp" +#include "uiwelcomescreen.hpp" #include #include @@ -83,8 +83,12 @@ static const auto LAYOUT = R"xml( - + + + + + @@ -116,22 +120,22 @@ static const auto LAYOUT = R"xml(