Private/Rendering/ConvertTo-AnsiPatch.ps1
|
function ConvertTo-AnsiPatch { <# .SYNOPSIS Converts a list of view-diff patches into an incremental ANSI escape sequence string. .DESCRIPTION Processes each patch from Compare-ElmViewTree: - Replace: emits a cursor-position sequence (ESC[{Y+1};{X+1}H) followed by the styled content from Apply-ElmStyle. - Clear: emits a cursor-position sequence followed by spaces spanning Width for each row in Height, erasing the vacated region. - FullRedraw: skipped. The caller is expected to detect FullRedraw patches and invoke ConvertTo-AnsiOutput instead. .PARAMETER Patches An array of patch PSCustomObjects produced by Compare-ElmViewTree. Each object has a Type property ('Replace', 'Clear', or 'FullRedraw'). .OUTPUTS [string] - A single ANSI escape sequence string ready for Console output. Returns an empty string when the patch list is empty or contains only FullRedraw. .EXAMPLE $patches = Compare-ElmViewTree -OldTree $prev -NewTree $new $ansi = ConvertTo-AnsiPatch -Patches $patches [Console]::Out.Write($ansi) .NOTES Does not emit hide/show cursor or clear-screen sequences. The caller (event loop) manages cursor visibility around full renders. #> [CmdletBinding()] param( [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]]$Patches ) $esc = [char]27 $sb = [System.Text.StringBuilder]::new() foreach ($patch in $Patches) { switch ($patch.Type) { 'Replace' { $row = $patch.Y + 1 $col = $patch.X + 1 $patchWidth = if ($patch.PSObject.Properties['Width'] -and $null -ne $patch.Width) { $patch.Width } else { $patch.Content.Length } $oldWidth = if ($patch.PSObject.Properties['OldWidth'] -and $null -ne $patch.OldWidth) { $patch.OldWidth } else { $patchWidth } $clearWidth = [Math]::Max($patchWidth, $oldWidth) $content = Apply-ElmStyle -Content $patch.Content -Style $patch.Style -Width $patchWidth # Compute visual width of styled output (SGR codes are zero-width). # Append trailing spaces to fill clearWidth, overwriting stale chars from shorter-to-longer transitions. $visualLen = $patch.Content.Length if ($null -ne $patch.Style) { $visualLen += [int]$patch.Style.PaddingLeft + [int]$patch.Style.PaddingRight } $trailer = ' ' * [Math]::Max(0, $clearWidth - $visualLen) [void]$sb.Append($esc + "[$row;${col}H" + $content + $trailer) } 'Clear' { $row = $patch.Y + 1 $col = $patch.X + 1 $spaces = ' ' * $patch.Width for ($r = 0; $r -lt $patch.Height; $r++) { $currentRow = $row + $r [void]$sb.Append("$esc[$currentRow;${col}H$spaces") } } 'FullRedraw' { # Intentionally skipped - caller handles full-redraw via ConvertTo-AnsiOutput } } } return $sb.ToString() } |