Skip to content

Commit

Permalink
Add support to colorize FileInfo file names (#14403)
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveL-MSFT committed Jul 12, 2021
1 parent adc236c commit bfb4a81
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 7 deletions.
Expand Up @@ -276,6 +276,10 @@ internal static IEnumerable<ExtendedTypeDefinition> GetFormatData()
"System.Management.Automation.PSStyle+ProgressConfiguration",
ViewsOf_System_Management_Automation_PSStyleProgressConfiguration());

yield return new ExtendedTypeDefinition(
"System.Management.Automation.PSStyle+FileInfoFormatting",
ViewsOf_System_Management_Automation_PSStyleFileInfoFormat());

yield return new ExtendedTypeDefinition(
"System.Management.Automation.PSStyle+ForegroundColor",
ViewsOf_System_Management_Automation_PSStyleForegroundColor());
Expand Down Expand Up @@ -2060,6 +2064,10 @@ private static IEnumerable<FormatViewDefinition> ViewsOf_System_Management_Autom
.AddItemScriptBlock(@"""$($_.Progress.MaxWidth)""", label: "Progress.MaxWidth")
.AddItemScriptBlock(@"""$($_.Progress.View)""", label: "Progress.View")
.AddItemScriptBlock(@"""$($_.Progress.UseOSCIndicator)""", label: "Progress.UseOSCIndicator")
.AddItemScriptBlock(@"""$($_.FileInfo.Directory)$($_.FileInfo.Directory.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "FileInfo.Directory")
.AddItemScriptBlock(@"""$($_.FileInfo.SymbolicLink)$($_.FileInfo.SymbolicLink.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "FileInfo.SymbolicLink")
.AddItemScriptBlock(@"""$($_.FileInfo.Executable)$($_.FileInfo.Executable.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "FileInfo.Executable")
.AddItemScriptBlock(@"""$([string]::Join(',',$_.FileInfo.Extension.Keys))""", label: "FileInfo.Extension")
.AddItemScriptBlock(@"""$($_.Foreground.Black)$($_.Foreground.Black.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.Black")
.AddItemScriptBlock(@"""$($_.Foreground.White)$($_.Foreground.White.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.White")
.AddItemScriptBlock(@"""$($_.Foreground.DarkGray)$($_.Foreground.DarkGray.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Foreground.DarkGray")
Expand Down Expand Up @@ -2124,6 +2132,39 @@ private static IEnumerable<FormatViewDefinition> ViewsOf_System_Management_Autom
.EndList());
}

private static IEnumerable<FormatViewDefinition> ViewsOf_System_Management_Automation_PSStyleFileInfoFormat()
{
yield return new FormatViewDefinition("System.Management.Automation.PSStyle+FileInfoFormatting",
ListControl.Create()
.StartEntry()
.AddItemScriptBlock(@"""$($_.Directory)$($_.Directory.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Directory")
.AddItemScriptBlock(@"""$($_.SymbolicLink)$($_.SymbolicLink.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "SymbolicLink")
.AddItemScriptBlock(@"""$($_.Executable)$($_.Executable.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Executable")
.AddItemScriptBlock(@"
$sb = [System.Text.StringBuilder]::new()
$maxKeyLength = 0
foreach ($key in $_.Extension.Keys) {
if ($key.Length -gt $maxKeyLength) {
$maxKeyLength = $key.Length
}
}
foreach ($key in $_.Extension.Keys) {
$null = $sb.Append($key.PadRight($maxKeyLength))
$null = $sb.Append(' = ""')
$null = $sb.Append($_.Extension[$key])
$null = $sb.Append($_.Extension[$key].Replace(""`e"",'`e'))
$null = $sb.Append($PSStyle.Reset)
$null = $sb.Append('""')
$null = $sb.Append([Environment]::NewLine)
}
$sb.ToString()",
label: "Extension")
.EndEntry()
.EndList());
}

private static IEnumerable<FormatViewDefinition> ViewsOf_System_Management_Automation_PSStyleForegroundColor()
{
yield return new FormatViewDefinition("System.Management.Automation.PSStyle+ForegroundColor",
Expand Down
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;

namespace System.Management.Automation
{
#region OutputRendering
Expand Down Expand Up @@ -34,7 +36,7 @@ public enum ProgressView
/// <summary>Classic rendering of progress.</summary>
Classic = 1,
}

#region PSStyle
/// <summary>
/// Contains configuration for how PowerShell renders text.
Expand Down Expand Up @@ -333,6 +335,55 @@ public sealed class FormattingData
public string Debug { get; set; } = "\x1b[33;1m";
}

/// <summary>
/// Contains formatting styles for FileInfo objects.
/// </summary>
public sealed class FileInfoFormatting
{
/// <summary>
/// Gets or sets the style for directories.
/// </summary>
public string Directory { get; set; } = "\x1b[44;1m";

/// <summary>
/// Gets or sets the style for symbolic links.
/// </summary>
public string SymbolicLink { get; set; } = "\x1b[36;1m";

/// <summary>
/// Gets or sets the style for executables.
/// </summary>
public string Executable { get; set; } = "\x1b[32;1m";

/// <summary>
/// Gets the style for archive.
/// </summary>
public Dictionary<string, string> Extension { get; }

/// <summary>
/// Initializes a new instance of the <see cref="FileInfoFormatting"/> class.
/// </summary>
public FileInfoFormatting()
{
Extension = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

// archives
Extension.Add(".zip", "\x1b[31;1m");
Extension.Add(".tgz", "\x1b[31;1m");
Extension.Add(".gz", "\x1b[31;1m");
Extension.Add(".tar", "\x1b[31;1m");
Extension.Add(".nupkg", "\x1b[31;1m");
Extension.Add(".cab", "\x1b[31;1m");
Extension.Add(".7z", "\x1b[31;1m");

// powershell
Extension.Add(".ps1", "\x1b[33;1m");
Extension.Add(".psd1", "\x1b[33;1m");
Extension.Add(".psm1", "\x1b[33;1m");
Extension.Add(".ps1xml", "\x1b[33;1m");
}
}

/// <summary>
/// Gets or sets the rendering mode for output.
/// </summary>
Expand Down Expand Up @@ -444,6 +495,11 @@ public string FormatHyperlink(string text, Uri link)
/// </summary>
public BackgroundColor Background { get; }

/// <summary>
/// Gets FileInfo colors.
/// </summary>
public FileInfoFormatting FileInfo { get; }

private static readonly PSStyle s_psstyle = new PSStyle();

private PSStyle()
Expand All @@ -452,6 +508,7 @@ private PSStyle()
Progress = new ProgressConfiguration();
Foreground = new ForegroundColor();
Background = new BackgroundColor();
FileInfo = new FileInfoFormatting();
}

/// <summary>
Expand Down
Expand Up @@ -1410,7 +1410,7 @@ private static void InitPathExtCache(string pathExt)
lock (s_lockObject)
{
s_cachedPathExtCollection = pathExt != null
? pathExt.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries)
? pathExt.ToLower().Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries)
: Array.Empty<string>();
s_cachedPathExtCollectionWithPs1 = new string[s_cachedPathExtCollection.Length + 1];
s_cachedPathExtCollectionWithPs1[0] = StringLiterals.PowerShellScriptFileExtension;
Expand Down
Expand Up @@ -140,6 +140,9 @@ static ExperimentalFeature()
new ExperimentalFeature(
name: "PSLoadAssemblyFromNativeCode",
description: "Expose an API to allow assembly loading from native code"),
new ExperimentalFeature(
name: "PSAnsiRenderingFileInfo",
description: "Enable coloring for FileInfo objects"),
};

EngineExperimentalFeatures = new ReadOnlyCollection<ExperimentalFeature>(engineFeatures);
Expand Down
42 changes: 37 additions & 5 deletions src/System.Management.Automation/namespaces/FileSystemProvider.cs
Expand Up @@ -2058,11 +2058,43 @@ string ToModeString(FileSystemInfo fileSystemInfo)
/// <returns>Name if a file or directory, Name -> Target if symlink.</returns>
public static string NameString(PSObject instance)
{
return instance?.BaseObject is FileSystemInfo fileInfo
? InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(fileInfo)
? $"{fileInfo.Name} -> {InternalSymbolicLinkLinkCodeMethods.GetTarget(instance)}"
: fileInfo.Name
: string.Empty;
if (ExperimentalFeature.IsEnabled("PSAnsiRendering") && ExperimentalFeature.IsEnabled("PSAnsiRenderingFileInfo"))
{
if (instance?.BaseObject is FileSystemInfo fileInfo)
{
if (InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(fileInfo))
{
return $"{PSStyle.Instance.FileInfo.SymbolicLink}{fileInfo.Name}{PSStyle.Instance.Reset} -> {InternalSymbolicLinkLinkCodeMethods.GetTarget(instance)}";
}
else if (fileInfo.Attributes.HasFlag(FileAttributes.Directory))
{
return $"{PSStyle.Instance.FileInfo.Directory}{fileInfo.Name}{PSStyle.Instance.Reset}";
}
else if (PSStyle.Instance.FileInfo.Extension.ContainsKey(fileInfo.Extension))
{
return $"{PSStyle.Instance.FileInfo.Extension[fileInfo.Extension]}{fileInfo.Name}{PSStyle.Instance.Reset}";
}
else if ((Platform.IsWindows && CommandDiscovery.PathExtensions.Contains(fileInfo.Extension.ToLower())) ||
(!Platform.IsWindows && Platform.NonWindowsIsExecutable(fileInfo.FullName)))
{
return $"{PSStyle.Instance.FileInfo.Executable}{fileInfo.Name}{PSStyle.Instance.Reset}";
}
else
{
return fileInfo.Name;
}
}

return string.Empty;
}
else
{
return instance?.BaseObject is FileSystemInfo fileInfo
? InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(fileInfo)
? $"{fileInfo.Name} -> {InternalSymbolicLinkLinkCodeMethods.GetTarget(instance)}"
: fileInfo.Name
: string.Empty;
}
}

/// <summary>
Expand Down
Expand Up @@ -205,3 +205,62 @@ Describe "Get-Item environment provider on Windows with accidental case-variant
}
}
}

Describe 'Formatting for FileInfo objects' -Tags 'CI' {
BeforeAll {
$PSDefaultParameterValues.Add('It:Skip', (-not $EnabledExperimentalFeatures.Contains('PSAnsiRenderingFileInfo')))
$extensionTests = [System.Collections.Generic.List[HashTable]]::new()
foreach ($extension in @('.zip', '.tgz', '.tar', '.gz', '.nupkg', '.cab', '.7z', '.ps1', '.psd1', '.psm1', '.ps1xml')) {
$extensionTests.Add(@{extension = $extension})
}
}

AfterAll {
$PSDefaultParameterValues.Remove('It:Skip')
}

It 'File type <extension> should have correct color' -TestCases $extensionTests {
param($extension)

$testFile = Join-Path -Path $TestDrive -ChildPath "test$extension"
$file = New-Item -ItemType File -Path $testFile
$file.NameString | Should -BeExactly "$($PSStyle.FileInfo.Extension[$extension] + $file.Name + $PSStyle.Reset)"
}

It 'Directory should have correct color' {
$dirPath = Join-Path -Path $TestDrive -ChildPath 'myDir'
$dir = New-Item -ItemType Directory -Path $dirPath
$dir.NameString | Should -BeExactly "$($PSStyle.FileInfo.Directory + $dir.Name + $PSStyle.Reset)"
}

It 'Executable should have correct color' {
if ($IsWindows) {
$exePath = Join-Path -Path $TestDrive -ChildPath 'myExe.exe'
$exe = New-Item -ItemType File -Path $exePath
}
else {
$exePath = Join-Path -Path $TestDrive -ChildPath 'myExe'
$null = New-Item -ItemType File -Path $exePath
chmod +x $exePath
$exe = Get-Item -Path $exePath
}

$exe.NameString | Should -BeExactly "$($PSStyle.FileInfo.Executable + $exe.Name + $PSStyle.Reset)"
}
}

Describe 'Formatting for FileInfo requiring admin' -Tags 'CI','RequireAdminOnWindows' {
BeforeAll {
$PSDefaultParameterValues.Add('It:Skip', (-not $EnabledExperimentalFeatures.Contains('PSAnsiRenderingFileInfo')))
}

AfterAll {
$PSDefaultParameterValues.Remove('It:Skip')
}

It 'Symlink should have correct color' {
$linkPath = Join-Path -Path $TestDrive -ChildPath 'link'
$link = New-Item -ItemType SymbolicLink -Name 'link' -Value $TestDrive -Path $TestDrive
$link.NameString | Should -BeExactly "$($PSStyle.FileInfo.SymbolicLink + $link.Name + $PSStyle.Reset) -> $TestDrive"
}
}

0 comments on commit bfb4a81

Please sign in to comment.