Desktop/Private/Rendering/Get-ItemStyle.ps1

# src/Private/Rendering/Get-ItemStyle.ps1

<#
.SYNOPSIS
    Computes the ANSI style for a file or directory.
 
.DESCRIPTION
    The Get-ItemStyle cmdlet determines the color and styling for an item based on its type
    (File or Directory) and its states (Hidden, System, Symlink, etc.).
     
    It performs a multi-pass resolution:
    1. Selects the base style (File vs Directory).
    2. Overlays styles for active states (e.g., Dim for Hidden).
    3. Resolves Foreground/Background overrides based on the profile's StylePriority.
     
    Returns a PSCustomObject containing the resolved ANSI escape sequence.
 
.PARAMETER Item
    The ShowTree.TreeItem to style.
 
.PARAMETER Colorize
    If true, returns ANSI escape sequences. If false, returns empty strings.
 
.PARAMETER StyleProfile
    The style profile object containing the color definitions.
#>

function Get-ItemStyle {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object] $Item,

        [Parameter(Mandatory)]
        [bool] $Colorize,

        [object] $StyleProfile = $null
    )

    if (-not $PSBoundParameters.ContainsKey('Debug') -and $PSCmdlet)
    {
        $DebugPreference = $PSCmdlet.GetVariableValue('DebugPreference')
    }
    if (-not $PSBoundParameters.ContainsKey('Verbose') -and $PSCmdlet)
    {
        $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference')
    }

    if ($null -eq $StyleProfile) { $StyleProfile = Get-ActiveShowTreeStyleProfile }

    $esc = $StyleProfile.Esc

    $isContainer = $Item.IsContainer
    $attrs       = $Item.Native.FileAttributes

    #
    # Determine base style
    #
    if ($isContainer) {
        $styleName = "Directory"
        $base      = $StyleProfile.Base.Directory
    }
    else {
        $styleName = "File"
        $base      = $StyleProfile.Base.File
    }

    #
    # No color mode
    #
    if (-not $Colorize) {
        return [PSCustomObject]@{
            Name = $styleName
            Ansi = ""
        }
    }

    #
    # Parse base style codes
    #
    $codesList = (New-Object -TypeName System.Collections.Generic.List[object])
    foreach ($c in ($base -split ';')) {
        if ($c -ne '') { [void]$codesList.Add($c) }
    }

    # Extract foreground codes.
    # Includes:
    # 30-37 standard foreground colors
    # 90-97 bright foreground colors
    # 39 terminal default foreground
    #
    # 39 must be treated as a foreground code so it does not remain in the
    # non-foreground code list and reset later OverrideForeground values.
    $fgList = (New-Object -TypeName System.Collections.Generic.List[object])
    $remainingCodes = (New-Object -TypeName System.Collections.Generic.List[object])
    foreach ($c in $codesList) {
        if ($c -match '^(3[0-7]|9[0-7]|39)$') { [void]$fgList.Add($c) }
        else { [void]$remainingCodes.Add($c) }
    }
    $fg = $fgList.ToArray()
    $codes = $remainingCodes

    #
    # Apply States
    #
    # 1. Start with explicit States from the item
    # 2. Add States derived from Native FileAttributes for Windows/provider support
    #
    $allStates = (New-Object -TypeName System.Collections.Generic.List[string])
    if ($Item.States) {
        foreach ($s in $Item.States) { [void]$allStates.Add($s) }
    }

    # Derive states from Native FileAttributes.
    if ($attrs) {
        foreach ($flag in Get-FileAttributes $attrs) {
            $flagName = $flag.ToString()
            if (-not $allStates.Contains($flagName)) { [void]$allStates.Add($flagName) }
        }
    }

    # Apply broad/low-priority styles first and specific/high-priority
    # styles last. Later Foreground/Background values intentionally win.
    $stateStylePriority = @($StyleProfile.StylePriority)

    $knownStates = (New-Object -TypeName System.Collections.Generic.List[string])
    foreach ($priorityState in $stateStylePriority) {
        if ($allStates.Contains($priorityState)) {
            [void]$knownStates.Add($priorityState)
        }
    }

    $customStates = (New-Object -TypeName System.Collections.Generic.List[string])
    foreach ($stateName in $allStates) {
        if ($stateStylePriority -notcontains $stateName) {
            [void]$customStates.Add($stateName)
        }
    }

    $orderedStates = (New-Object -TypeName System.Collections.Generic.List[string])
    foreach($____item in $knownStates){[void]$orderedStates.Add($____item)}
    foreach($____item in $customStates){[void]$orderedStates.Add($____item)}

    # Lookup styles from States.
    foreach ($stateName in $orderedStates) {
        $overlay = $null

        if ($StyleProfile.States -and $StyleProfile.States.ContainsKey($stateName)) {
            $overlay = $StyleProfile.States[$stateName]
        }

        if ($overlay) {
            # Add state style SGR fragments.
            $ansiStyle = $overlay.AnsiStyle
            if ($ansiStyle) {
                foreach ($a in ($ansiStyle -split ';')) {
                    if ($a -ne '' -and $codes -notcontains $a) { [void]$codes.Add($a) }
                }
            }

            # Foreground override.
            if ($overlay.Foreground) {
                if ($overlay.Foreground -is [string]) {
                    $fg = $overlay.Foreground
                }
                elseif ($overlay.Foreground.ContainsKey($styleName)) {
                    $fg = $overlay.Foreground[$styleName]
                }
            }

            # Background override.
            if ($overlay.Background) {
                [void]$codes.Add($overlay.Background)
            }
        }
    }

    #
    # Build final ANSI sequence
    #
    $final = (New-Object -TypeName System.Collections.Generic.List[object])
    if ($fg) {
        if ($fg -is [array]) { foreach ($f in $fg) { [void]$final.Add($f) } }
        else { [void]$final.Add($fg) }
    }
    foreach ($c in $codes) { [void]$final.Add($c) }

    $ansi = "${esc}[$($final -join ';')m"

    [PSCustomObject]@{
        Name = $styleName
        Ansi = $ansi
    }
}