Private/ColorAndIcon.ps1
|
function script:Get-Translate { $translate = [System.Collections.Generic.Dictionary[string, string]]::new([System.StringComparer]::OrdinalIgnoreCase) $translate.Add("BLK", "bd") $translate.Add("CAPABILITY", "ca") $translate.Add("CHR", "cd") $translate.Add("DIR", "di") $translate.Add("DOOR", "do") $translate.Add("EXEC", "ex") $translate.Add("FIFO", "pi") $translate.Add("FILE", "fi") $translate.Add("HIDDEN", "hi") $translate.Add("LINK", "ln") $translate.Add("MISSING", "mi") $translate.Add("MULTIHARDLINK", "mh") $translate.Add("NORMAL", "no") $translate.Add("ORPHAN", "or") $translate.Add("OTHER_WRITABLE", "ow") $translate.Add("RESET", "rs") $translate.Add("SETGID", "sg") $translate.Add("SETUID", "su") $translate.Add("SOCK", "so") $translate.Add("STICKY", "st") $translate.Add("STICKY_OTHER_WRITABLE", "tw") return $translate } $script:TranslateCache = Get-Translate function script:ConvertFrom-SourceData { param( [string]$SourceFile ) if (-not [System.IO.File]::Exists($SourceFile)) { return $null } $filters = [System.Collections.Generic.HashSet[string]]::new(2048) $null = $filters.Add("TERM") $null = $filters.Add("COLOR") $null = $filters.Add("*") $lines = [System.IO.File]::ReadAllLines($SourceFile) if ($lines.Count -eq 0) { return $null } $result = [System.Text.StringBuilder]::new(16384) foreach ($line in $lines) { # remove comments and trim whitespace $cleanLine = $line.Split('#')[0].Trim() if (-not $cleanLine) { continue } # use regex to split by the last occurrence of whitespace, to allow keys with spaces (like "Saved Games") $parts = [regex]::Split($cleanLine, '\s+(?=\S+$)') if ($parts.Count -lt 2) { continue } $key = $parts[0] $val = $parts[1] if ($val -eq "*" -or $val -eq "" -or $filters.Contains($key)) { continue } $finalKey = $null if ($script:TranslateCache.TryGetValue($key, [ref]$finalKey)) { # Found a translation, use it } elseif ($key.StartsWith('*.')) { $finalKey = $key.ToLower() } elseif ($key.StartsWith('.')) { $finalKey = "*" + $key.ToLower() } else { $finalKey = $key } if ($filters.Contains($finalKey)) { continue } $null = $filters.Add($key) $null = $filters.Add($finalKey) if ($result.Length -gt 0) { $null = $result.Append(':') } $null = $result.Append($finalKey).Append('=').Append($val) } return $result.ToString() } function script:Get-CacheData { param( [string]$SourceFile, [string]$CacheFile ) if ([System.IO.File]::Exists($CacheFile)) { $cached = [System.IO.File]::ReadAllText($CacheFile, [System.Text.Encoding]::UTF8) if ($cached) { return $cached } } $result = (ConvertFrom-SourceData $SourceFile) if ($result) { [System.IO.File]::WriteAllText($CacheFile, $result, [System.Text.Encoding]::UTF8) } return $result } $script:DefaultColors = @{ "fi" = "0" # Default file no color "di" = "38;5;30" # Directory default blue-green "ln" = "38;5;81;1" # Link default cyan bold "or" = "48;5;196;38;5;232;1" # Orphan default red background with black bold "ex" = "38;5;208;1" # Executable file default orange bold "hi" = "38;5;90" # Hidden file default purple-gray "pi" = "38;5;126" # FIFO default yellow-green "so" = "38;5;197" # Socket default pink } $script:DefaultIcons = @{ "fi" = "" # File default file icon "di" = "" # Directory default folder icon "ln" = "" # Link default link icon "or" = "" # Orphan default broken link icon "ex" = "" # Executable file default program icon "hi" = "" # Hidden file default hidden icon "pi" = "" # FIFO default pipe icon "so" = "" # Socket default socket icon } $script:COLORS_SOURCE = Join-Path $PSScriptRoot "../Data/LS_COLORS" $script:COLORS_CACHE = Join-Path $HOME ".LS_COLORS_CACHE" $script:ICONS_SOURCE = Join-Path $PSScriptRoot "../Data/LS_ICONS" $script:ICONS_CACHE = Join-Path $HOME ".LS_ICONS_CACHE" function script:Get-Colors { return Get-CacheData $script:COLORS_SOURCE $script:COLORS_CACHE } function script:Get-Icons { return Get-CacheData $script:ICONS_SOURCE $script:ICONS_CACHE } $script:ColorsMemCache = [PSCustomObject]@{ Hash = [System.Collections.Generic.Dictionary[string, string]]::new() IsInit = $false } $script:IconsMemCache = [PSCustomObject]@{ Hash = [System.Collections.Generic.Dictionary[string, string]]::new() IsInit = $false } function script:ConvertTo-MemCache { param($EnvVar) $hash = [System.Collections.Generic.Dictionary[string, string]]::new([System.StringComparer]::OrdinalIgnoreCase) # Store exact match and suffix match (.py, di) foreach ($item in ($EnvVar -split ':')) { $kv = $item -split '=' if ($kv.Count -ne 2) { continue } $key = $kv[0] $val = $kv[1] if ($key -match '^(.*)\[0-9\]\{0,(\d+)\}$') { $prefix = $Matches[1].TrimStart('*') $maxLen = [int]$Matches[2] # To prevent generating too many entries, # we can limit the maxLen to a reasonable number, say 3. # This means we will generate entries for up to 999 suffixes. if ($maxLen -gt 3) { $maxLen = 3 } $hash[$prefix] = $val $maxN = [math]::Pow(10, $maxLen) - 1 foreach ($i in 0..$maxN) { $n = "$i" foreach ($j in $n.Length..$maxLen) { $fmt = $n.PadLeft($j, '0') $hash[$prefix + $fmt] = $val } } } elseif ($key.StartsWith('*')) { $hash[$key.TrimStart('*')] = $val } else { $hash[$key] = $val } } return $hash } function script:Initialize-ColorsMemCache { if (-not $script:ColorsMemCache.IsInit) { if (-not $env:LS_COLORS) { $env:LS_COLORS = Get-Colors } $script:ColorsMemCache.Hash = ConvertTo-MemCache $env:LS_COLORS $script:ColorsMemCache.IsInit = $true } } function script:Initialize-IconsMemCache { if (-not $script:IconsMemCache.IsInit) { if (-not $env:LS_ICONS) { $env:LS_ICONS = Get-Icons } $script:IconsMemCache.Hash = ConvertTo-MemCache $env:LS_ICONS $script:IconsMemCache.IsInit = $true } } Initialize-ColorsMemCache Initialize-IconsMemCache function script:Lookup { param($DefaultHash, $Hash, $Name, $Ext, $Attr) if ($null -ne $Name -and $Hash.ContainsKey($Name)) { return $Hash[$Name] } if ($null -ne $Ext -and $Hash.ContainsKey($Ext)) { return $Hash[$Ext] } if ($null -eq $Attr) { return $null } if ($Hash.ContainsKey($Attr)) { return $Hash[$Attr] } return $DefaultHash[$Attr] } function script:Get-Color { param($Name, $Ext, $Attr) return Lookup $script:DefaultColors $script:ColorsMemCache.Hash $Name $Ext $Attr } function script:Get-Icon { param($Name, $Ext, $Attr) return Lookup $script:DefaultIcons $script:IconsMemCache.Hash $Name $Ext $Attr } function script:Get-DefaultColor { param($Attr) return $script:DefaultColors[$Attr] } function script:Get-DefaultIcon { param($Attr) return $script:DefaultIcons[$Attr] } function script:Get-ColorAndIcon { param( [System.IO.FileSystemInfo]$Item, [int]$Depth = 0 # Prevent infinite loop caused by circular links ) # Get basic attrs $name = $Item.Name $ext = $Item.Extension.ToLower() $attrs = $Item.Attributes $fa = [System.IO.FileAttributes] $isLink = $attrs.HasFlag($fa::ReparsePoint) $attr = if ($isLink) { if ($item.PSObject.Methods['ResolveLinkTarget']) { try { $target = $item.ResolveLinkTarget($true); $name = $target.Name $ext = $target.Extension.ToLower() "ln" } catch { "or" } } else { "ln" } } elseif ($attrs.HasFlag($fa::Hidden)) { "hi" } elseif ($Item -is [System.IO.DirectoryInfo]) { "di" } elseif ($attrs.HasFlag($fa::SparseFile)) { "pi" } elseif ($ext -eq ".sock" -or $ext -eq ".socket") { "so" } elseif ($ext -match '\.(com|exe|bat|cmd|ps1|sh)$') { "ex" } else { "fi" } # Get initial color and icon $color = Get-Color $name $ext $attr $icon = Get-Icon $name $ext $attr # Final rendering if ($null -eq $color -or $color -eq "target") { $color = Get-DefaultColor $attr } if ($null -eq $icon -or $icon -eq "target") { $icon = Get-DefaultIcon $attr } if ($attrs.HasFlag($fa::System)) { $color += ";2" # Dim system files } if ($attrs.HasFlag($fa::ReadOnly)) { $color += ";4" # Underline read-only files } return $color, $icon } function script:EscapeColor { param($Color) return "$([char]27)[${Color}m" } # Red, Orange, Yellow, Green, Cyan, Blue, Purple, Gray, Silver, White 10 color cycle, index 0-9 $script:Colors = @(196, 208, 220, 40, 81, 75, 141, 242, 250, 253) | ForEach-Object { EscapeColor "38;5;$_" } function script:Color { param($Index) return $script:Colors[$Index] } function script:ColorReset { return EscapeColor 0 } function script:ColorRed { return $script:Colors[0] } function script:ColorOrange { return $script:Colors[1] } function script:ColorYellow { return $script:Colors[2] } function script:ColorGreen { return $script:Colors[3] } function script:ColorCyan { return $script:Colors[4] } function script:ColorBlue { return $script:Colors[5] } function script:ColorPurple { return $script:Colors[6] } function script:ColorGray { return $script:Colors[7] } function script:ColorSilver { return $script:Colors[8] } function script:ColorWhite { return $script:Colors[9] } function global:Format-CoolName { <# .SYNOPSIS Formats the input filesystem object as a string with color and icon. .PARAMETER Item A FileSystemInfo object representing a file or directory. .OUTPUT A string with ANSI color codes and an icon, visually representing the file or directory. #> param( [System.IO.FileSystemInfo]$Item ) $color, $icon = Get-ColorAndIcon -Item $Item return "$(EscapeColor $color)$(vPadRight $icon 3)$($Item.Name)$(ColorReset)" } function global:Format-CoolSize { <# .SYNOPSIS Formats bytes into a colorful, human-readable string (B, KB, MB, GB, TB, PB, EB). The digital part is 7 chars wide, total output is 10 chars wide. .PARAMETER Bytes The size in bytes to format. .PARAMETER ValueColor Optional color for the numeric part. .OUTPUT A string with ANSI color codes, where the numeric part is right-aligned to 7 characters, followed by a space and a 2-character unit, for a total width of 10 characters. .EXAMPLE Format-CoolSize -Bytes 123456789 -ValueColor (ColorRed) Output: " 117.74 MB" with "117.74" in red and "MB" in the color corresponding to its unit. #> param( [double]$Bytes, [string]$ValueColor = "" # Optional color for the numeric part ) $units = ' B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB' # Handle zero or negative values if ($Bytes -le 0) { return "$(ColorGray) 0 B$(ColorReset)" } $index = 0 $value = $Bytes # Calculate unit level while ($value -ge 1024 -and $index -lt ($units.Count - 1)) { $value /= 1024 $index++ } # Format numeric part (PadLeft 7) $formattedValue = ("{0:N2}" -f $value).PadLeft(7) $unit = $units[$index] # Get colors $unitColor = Color $index # Combine: Value(7) + Space(1) + Unit(2) = 10 chars return "${ValueColor}${formattedValue} ${unitColor}$unit$(ColorReset)" } |