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<std::string> 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<std::string> 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<const FileSystemModel*>( index.model() );
-			Lock l( mGitStatusMutex );
-			for ( const auto& status : mGitStatus.files ) {
-				for ( const auto& file : status.second ) {
-					auto node = static_cast<const FileSystemModel::Node*>( 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<const FileSystemModel::Node*>( 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<std::string> 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;
+	}
 	</style>
 	<RelativeLayout id="git_panel" lw="mp" lh="mp">
 		<vbox id="git_content" lw="mp" lh="mp">
@@ -1350,8 +1406,12 @@ void GitPlugin::buildSidePanelTab() {
 	</RelativeLayout>
 	)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<std::string, std::string> mGitBranches;
 	Git::Status mGitStatus;
 	std::vector<std::pair<std::string, std::string>> mRepos;
+	UnorderedSet<std::string> 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 );
