From 4cff69abad7278f5f23b1bd033e236955ae48ca9 Mon Sep 17 00:00:00 2001
From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com>
Date: Thu, 30 Oct 2025 00:18:24 +1000
Subject: [PATCH] set file colour in sln explorer based on git status
---
Directory.Packages.props | 3 +-
.../SolutionDiscovery/SharpIdeFile.cs | 1 +
.../VsPersistence/VsPersistenceMapper.cs | 28 +++++++++++++++++++
.../SharpIDE.Application.csproj | 1 +
.../SolutionExplorer/SolutionExplorerPanel.cs | 14 ++++++++--
5 files changed, 44 insertions(+), 3 deletions(-)
diff --git a/Directory.Packages.props b/Directory.Packages.props
index d278646..4c5ae62 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -14,6 +14,7 @@
+
@@ -59,4 +60,4 @@
-
+
\ No newline at end of file
diff --git a/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFile.cs b/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFile.cs
index d152985..68eadd7 100644
--- a/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFile.cs
+++ b/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFile.cs
@@ -17,6 +17,7 @@ public class SharpIdeFile : ISharpIdeNode, IChildSharpIdeNode, IFileOrFolder
public bool IsCshtmlFile => Path.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase);
public bool IsCsharpFile => Path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase);
public bool IsRoslynWorkspaceFile => IsCsharpFile || IsRazorFile || IsCshtmlFile;
+ public GitStatus GitStatus { get; set; } = GitStatus.Unaltered;
public required ReactiveProperty IsDirty { get; init; }
public required bool SuppressDiskChangeEvents { get; set; } // probably has concurrency issues
public required DateTimeOffset? LastIdeWriteTime { get; set; }
diff --git a/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/VsPersistenceMapper.cs b/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/VsPersistenceMapper.cs
index 71c04cf..f97c988 100644
--- a/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/VsPersistenceMapper.cs
+++ b/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/VsPersistenceMapper.cs
@@ -1,4 +1,5 @@
using System.Diagnostics;
+using LibGit2Sharp;
namespace SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
@@ -11,7 +12,34 @@ public static class VsPersistenceMapper
var intermediateModel = await IntermediateMapper.GetIntermediateModel(solutionFilePath, cancellationToken);
var solutionModel = new SharpIdeSolutionModel(solutionFilePath, intermediateModel);
+ using var repo = new Repository(solutionModel.DirectoryPath);
+ var status = repo.RetrieveStatus(new StatusOptions());
+
+ foreach (var entry in status.Where(s => s.State is not FileStatus.Ignored))
+ {
+ // Assumes solution file is at git repo root
+ var filePath = new FileInfo(Path.Combine(solutionModel.DirectoryPath, entry.FilePath)).FullName; // used to normalise path separators
+ var fileInSolution = solutionModel.AllFiles.SingleOrDefault(f => f.Path.Equals(filePath, StringComparison.OrdinalIgnoreCase));
+ if (fileInSolution is null) continue;
+
+ var mappedGitStatus = entry.State switch
+ {
+ FileStatus.NewInIndex | FileStatus.ModifiedInWorkdir => GitStatus.Added, // I've seen these appear together
+ FileStatus.NewInIndex or FileStatus.NewInWorkdir => GitStatus.Added,
+ FileStatus.ModifiedInIndex or FileStatus.ModifiedInWorkdir => GitStatus.Modified,
+ _ => GitStatus.Unaltered // TODO: handle other kinds?
+ };
+
+ fileInSolution.GitStatus = mappedGitStatus;
+ }
return solutionModel;
}
}
+
+public enum GitStatus
+{
+ Unaltered,
+ Modified,
+ Added
+}
diff --git a/src/SharpIDE.Application/SharpIDE.Application.csproj b/src/SharpIDE.Application/SharpIDE.Application.csproj
index 15d8bfc..089a0bb 100644
--- a/src/SharpIDE.Application/SharpIDE.Application.csproj
+++ b/src/SharpIDE.Application/SharpIDE.Application.csproj
@@ -23,6 +23,7 @@
+
diff --git a/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs b/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs
index 177d055..bb646fe 100644
--- a/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs
+++ b/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs
@@ -26,7 +26,8 @@ public partial class SolutionExplorerPanel : MarginContainer
public Texture2D SlnIcon { get; set; } = null!;
private readonly Color _gitNewFileColour = new Color("50964c");
- private readonly Color _gitEditedFileColour = new Color("5988b3");
+ private readonly Color _gitEditedFileColour = new Color("6496ba");
+ private readonly Color _gitUnalteredFileColour = new Color("d4d4d4");
public SharpIdeSolutionModel SolutionModel { get; set; } = null!;
private Tree _tree = null!;
@@ -286,7 +287,8 @@ public partial class SolutionExplorerPanel : MarginContainer
{
var fileItem = tree.CreateItem(parent);
fileItem.SetText(0, sharpIdeFile.Name);
- fileItem.SetIcon(0, CsharpFileIcon);
+ fileItem.SetIcon(0, CsharpFileIcon);
+ fileItem.SetCustomColor(0, GetColorForGitStatus(sharpIdeFile.GitStatus));
fileItem.SetMetadata(0, new RefCountedContainer(sharpIdeFile));
Observable.EveryValueChanged(sharpIdeFile, folder => folder.Name)
@@ -302,4 +304,12 @@ public partial class SolutionExplorerPanel : MarginContainer
{
await this.InvokeAsync(() => item?.Free());
}
+
+ private Color GetColorForGitStatus(GitStatus status) => status switch
+ {
+ GitStatus.Added => _gitNewFileColour,
+ GitStatus.Modified => _gitEditedFileColour,
+ GitStatus.Unaltered => _gitUnalteredFileColour,
+ _ => _gitUnalteredFileColour
+ };
}