Threads/Private/PowerShellRich.Status.ps1

# Adapted from D:\mygit\PowerShellRich\Public\Status.ps1
# to provide lightweight status spinners inside PSUnplugged.

$script:PSUnpluggedSpinners = @{
    dots         = @{
        interval = 80
        frames   = @("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
    }
    dots2        = @{
        interval = 80
        frames   = @("⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷")
    }
    line         = @{
        interval = 130
        frames   = @("-", "\", "|", "/")
    }
    arc          = @{
        interval = 100
        frames   = @("◜", "◠", "◝", "◞", "◡", "◟")
    }
    moon         = @{
        interval = 80
        frames   = @("🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘")
    }
    bouncingBall = @{
        interval = 80
        frames   = @("( ● )", "( ● )", "( ● )", "( ● )", "( ●)", "( ● )", "( ● )", "( ● )")
    }
}

function Test-PSUnpluggedSpinnerEnabled {
    [CmdletBinding()]
    param()

    if ($env:PSUNPLUGGED_NO_SPINNER -in @('1', 'true', 'yes')) {
        return $false
    }

    if (-not [Environment]::UserInteractive) {
        return $false
    }

    try {
        if ([Console]::IsOutputRedirected -or [Console]::IsErrorRedirected) {
            return $false
        }
    }
    catch {
        return $false
    }

    return $Host.Name -notmatch 'ServerRemoteHost|Visual Studio Code Host'
}

function New-PSUnpluggedSpinner {
    [CmdletBinding()]
    param(
        [string]$Name = 'dots',
        [string]$Text = '',
        [string]$Style = 'bold cyan',
        [double]$Speed = 1.0
    )

    if (-not $script:PSUnpluggedSpinners.ContainsKey($Name)) {
        throw "No spinner called '$Name'."
    }

    $spinnerDef = $script:PSUnpluggedSpinners[$Name]
    return @{
        Name      = $Name
        Frames    = $spinnerDef.frames
        Interval  = $spinnerDef.interval
        Text      = $Text
        Style     = $Style
        Speed     = $Speed
        StartTime = [DateTime]::Now
    }
}

function Start-PSUnpluggedStatus {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$Status,
        [string]$SpinnerName = 'dots',
        [string]$SpinnerStyle = 'bold cyan',
        [Parameter(Mandatory)][scriptblock]$ScriptBlock
    )

    $spinner = New-PSUnpluggedSpinner -Name $SpinnerName -Text $Status -Style $SpinnerStyle
    $runspace = [runspacefactory]::CreateRunspace()
    $runspace.Open()

    $powershell = [powershell]::Create().AddScript({
            param($spinner, $status)

            [Console]::OutputEncoding = [System.Text.Encoding]::UTF8

            function Get-SimpleAnsi {
                param([string]$Style)

                $esc = [char]27
                $result = ''
                if ($Style -match 'bold') { $result += "$esc[1m" }
                if ($Style -match 'cyan') { $result += "$esc[36m" }
                if ($Style -match 'green') { $result += "$esc[32m" }
                if ($Style -match 'magenta') { $result += "$esc[35m" }
                if ($Style -match 'red') { $result += "$esc[31m" }
                if ($Style -match 'yellow') { $result += "$esc[33m" }
                if ($Style -match 'blue') { $result += "$esc[34m" }
                return $result
            }

            $frames = $spinner.Frames
            $styleCode = Get-SimpleAnsi -Style $spinner.Style
            $reset = "$([char]27)[0m"
            $index = 0

            [Console]::Write("$([char]27)[?25l")

            try {
                while ($true) {
                    $frame = $frames[$index % $frames.Count]
                    [Console]::Write("`r$([char]27)[K$styleCode$frame$reset $status")
                    $index++
                    [System.Threading.Thread]::Sleep([int]($spinner.Interval / $spinner.Speed))
                }
            }
            catch {
            }
            finally {
                [Console]::Write("`r$([char]27)[K$([char]27)[?25h")
            }
        }).AddArgument($spinner).AddArgument($Status)

    $powershell.Runspace = $runspace
    $null = $powershell.BeginInvoke()

    try {
        return (& $ScriptBlock)
    }
    finally {
        try { $powershell.Stop() } catch { }
        $runspace.Close()
        $powershell.Dispose()
        $runspace.Dispose()
        Write-Host -NoNewline "`r$([char]27)[K$([char]27)[?25h"
    }
}

function Invoke-PSUnpluggedWithSpinner {
    [CmdletBinding()]
    param(
        [AllowNull()][AllowEmptyString()][string]$Status,
        [string]$SpinnerName = 'dots',
        [string]$SpinnerStyle = 'bold cyan',
        [Parameter(Mandatory)][scriptblock]$ScriptBlock
    )

    if ([string]::IsNullOrWhiteSpace($Status)) {
        return (& $ScriptBlock)
    }

    if (-not (Test-PSUnpluggedSpinnerEnabled)) {
        return (& $ScriptBlock)
    }

    return (Start-PSUnpluggedStatus -Status $Status -SpinnerName $SpinnerName -SpinnerStyle $SpinnerStyle -ScriptBlock $ScriptBlock)
}