Show Git, Cloud, and created date in Details, and hide Free space except on drives.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-26 22:07:35 +02:00
parent ff070beba4
commit b72c375e87
10 changed files with 371 additions and 17 deletions

View File

@@ -0,0 +1,148 @@
using Explorer.Domain;
namespace Explorer.Application;
public static class GitListingOverlay
{
public static string ForNestedRepo(GitStatus status)
{
var parts = new List<string> { status.Branch };
if (!string.IsNullOrEmpty(status.OperationLabel))
{
parts.Add(status.OperationLabel);
}
if (status.ModifiedCount > 0)
{
parts.Add($"{status.ModifiedCount} modified");
}
if (status.UntrackedCount > 0)
{
parts.Add($"{status.UntrackedCount} untracked");
}
return string.Join(" · ", parts);
}
public static string ForItem(string itemFullPath, bool isDirectory, string repoRoot, GitStatus status)
{
var rel = ToGitRelative(repoRoot, itemFullPath);
if (rel is null)
{
return "";
}
return isDirectory ? FolderLabel(status.Changes, rel) : FileLabel(status.Changes, rel);
}
internal static string? ToGitRelative(string repoRoot, string fullPath)
{
var root = PathRules.FromExtended(repoRoot).TrimEnd('\\');
var full = PathRules.FromExtended(fullPath).TrimEnd('\\');
if (full.Equals(root, StringComparison.OrdinalIgnoreCase))
{
return "";
}
if (!full.StartsWith(root + "\\", StringComparison.OrdinalIgnoreCase))
{
return null;
}
return full[(root.Length + 1)..].Replace('\\', '/');
}
private static string FileLabel(IReadOnlyList<GitChange> changes, string rel)
{
if (rel.Length == 0)
{
return "";
}
var hits = changes.Where(c => Matches(c, rel)).ToList();
if (hits.Count == 0)
{
return "";
}
if (hits.Exists(c => c.State == GitChangeState.Unmerged))
{
return "Unmerged";
}
if (hits.Exists(c => c.State == GitChangeState.Untracked))
{
return "Untracked";
}
var staged = hits.Find(c => c.State == GitChangeState.Staged);
var unstaged = hits.Find(c => c.State == GitChangeState.Unstaged);
if (staged is not null && unstaged is not null)
{
return "Staged · Modified";
}
if (staged is not null)
{
return staged.Index switch
{
'R' => "Renamed",
'A' => "Added",
'D' => "Deleted",
_ => "Staged"
};
}
return unstaged is null ? "" : Title(unstaged.ChangeLabel);
}
private static string FolderLabel(IReadOnlyList<GitChange> changes, string rel)
{
var hits = changes.Where(c => Under(c, rel)).ToList();
if (hits.Count == 0)
{
return "";
}
if (hits.Exists(c => c.State == GitChangeState.Unmerged))
{
return "Unmerged";
}
if (hits.TrueForAll(c => c.State == GitChangeState.Untracked))
{
return "Untracked";
}
return "Modified";
}
private static bool Matches(GitChange change, string rel)
=> Same(change.Path, rel)
|| (change.OriginalPath is { Length: > 0 } original && Same(original, rel));
private static bool Under(GitChange change, string rel)
{
if (Matches(change, rel))
{
return true;
}
if (rel.Length == 0)
{
return true;
}
var prefix = rel + "/";
return change.Path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
|| (change.OriginalPath is { Length: > 0 } original
&& original.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
}
private static bool Same(string left, string right)
=> left.Equals(right, StringComparison.OrdinalIgnoreCase);
private static string Title(string value)
=> string.IsNullOrEmpty(value) ? "" : char.ToUpperInvariant(value[0]) + value[1..];
}