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 + }; }