Private/Console/Live.psm1

using namespace System
using namespace System.Threading
using namespace System.Collections.Generic

using module .\Ansi.psm1
using module ..\Enums.psm1
using module ..\Abstracts.psm1
using module .\Rendering.psm1
using module .\Progress.psm1

class LiveDisplayContext {
  [LiveDisplay]$Owner

  LiveDisplayContext([LiveDisplay]$owner) {
    $this.Owner = $owner
  }

  [void] UpdateTarget([IRenderable]$target) {
    $this.Owner.Target = $target
  }

  [void] Refresh() {
  }
}

class LiveDisplaySession {
  [LiveDisplay]$Owner
  [LiveDisplayContext]$Context
  [LiveDisplayRegion]$Display
  [string[]]$LastLines

  LiveDisplaySession([LiveDisplay]$owner, [LiveDisplayContext]$context, [LiveDisplayRegion]$display) {
    $this.Owner = $owner
    $this.Context = $context
    $this.Display = $display
    $this.LastLines = [string[]]@()
  }

  [void] Tick([object]$state) {
    if ($null -eq $this.Owner.Target) { return }

    $options = [RenderOptions]::Create($this.Owner.Writer, $this.Owner.Writer.Capabilities)
    $segs = $this.Owner.Target.Render($options, $this.Owner.GetRenderWidth())
    $lines = [Segment]::SplitLines($segs, $this.Owner.GetRenderWidth())

    $renderedLines = [List[string]]::new()
    foreach ($line in $lines) {
      $sb = [Text.StringBuilder]::new()
      foreach ($seg in $line.Segments) {
        $hasColor = $this.Owner.Writer.Capabilities.Ansi -and $null -ne $seg.Style -and $seg.Style -ne [Style]::Plain
        if ($hasColor) {
          [void]$sb.Append("`e[" + [AnsiCodeBuilder]::GetAnsi($seg.Style, $this.Owner.Writer.Capabilities.ColorSystem) + "m")
        }
        [void]$sb.Append($seg.Text)
        if ($hasColor) {
          [void]$sb.Append("`e[0m")
        }
      }
      $renderedLines.Add($sb.ToString())
    }

    $this.LastLines = $renderedLines.ToArray()
    $this.Display.Render($this.LastLines)
  }
}

class LiveDisplay {
  [AnsiWriter]$Writer
  [IRenderable]$Target
  [int]$RefreshRateMs = 100

  LiveDisplay([AnsiWriter]$writer, [IRenderable]$target) {
    $this.Writer = $writer
    $this.Target = $target
  }

  [void] Start([Action[LiveDisplayContext]]$action) {
    $context = [LiveDisplayContext]::new($this)
    $display = [LiveDisplayRegion]::new($this.Writer)
    $session = [LiveDisplaySession]::new($this, $context, $display)
    $thread = [ProgressRefreshThread]::new(([TimerCallback] $session.Tick), $this.RefreshRateMs)

    try {
      $action.Invoke($context)
      $session.Tick($null)
    } finally {
      $thread.Dispose()
      $session.Tick($null)
      $display.Complete($session.LastLines)
    }
  }

  hidden [int] GetRenderWidth() {
    try {
      return [Math]::Max(40, [Console]::WindowWidth)
    } catch {
      return 80
    }
  }
}