src/Services/AnsiService.ps1
|
# AnsiService — Helpers VT100/ANSI para el Renderer (ADR-005) # Truecolor escape codes. Sin Write-Host -Foreground. Sin [Console]::ForegroundColor. class AnsiService { static [string] $Esc = [char]27 + '[' static [string] $Reset = [char]27 + '[0m' # Foreground truecolor: \x1b[38;2;r;g;bm static [string] Fg([int]$r, [int]$g, [int]$b) { return "$([AnsiService]::Esc)38;2;${r};${g};${b}m" } # Background truecolor: \x1b[48;2;r;g;bm static [string] Bg([int]$r, [int]$g, [int]$b) { return "$([AnsiService]::Esc)48;2;${r};${g};${b}m" } # Hex (#rrggbb o rrggbb) → foreground static [string] FgHex([string]$hex) { $rgb = [AnsiService]::HexToRgb($hex) return [AnsiService]::Fg($rgb[0], $rgb[1], $rgb[2]) } static [string] BgHex([string]$hex) { $rgb = [AnsiService]::HexToRgb($hex) return [AnsiService]::Bg($rgb[0], $rgb[1], $rgb[2]) } # Cursor positioning: \x1b[<row>;<col>H (1-indexed) static [string] MoveTo([int]$row, [int]$col) { return "$([AnsiService]::Esc)${row};${col}H" } # Clear screen + cursor home static [string] ClearScreen() { return "$([AnsiService]::Esc)2J$([AnsiService]::Esc)H" } # Hide / show cursor static [string] HideCursor() { return "$([AnsiService]::Esc)?25l" } static [string] ShowCursor() { return "$([AnsiService]::Esc)?25h" } # Alternate screen buffer (entrar/salir sin ensuciar el scrollback) static [string] EnterAltBuffer() { return "$([AnsiService]::Esc)?1049h" } static [string] LeaveAltBuffer() { return "$([AnsiService]::Esc)?1049l" } # Synchronized output (DEC SYNC, modo 2026). Le dice a la terminal: "procesá # todo lo que viene pero no lo pintes hasta que veas End". Evita render # progresivo línea-por-línea que el ojo percibe como parpadeo cuando el frame # nuevo se solapa visualmente con el anterior. # Soportado por Windows Terminal, iTerm2, Kitty, Alacritty, modernos pwsh. # Si la terminal no lo entiende, ignora silenciosamente — defensivo siempre. static [string] BeginSync() { return "$([AnsiService]::Esc)?2026h" } static [string] EndSync() { return "$([AnsiService]::Esc)?2026l" } # Atributos static [string] Bold() { return "$([AnsiService]::Esc)1m" } static [string] Dim() { return "$([AnsiService]::Esc)2m" } static [string] Italic() { return "$([AnsiService]::Esc)3m" } static [string] Underline() { return "$([AnsiService]::Esc)4m" } # "#6ee7ff" → @(110, 231, 255). Acepta con o sin '#'. static [int[]] HexToRgb([string]$hex) { $h = $hex.TrimStart('#') if ($h.Length -ne 6) { throw "Hex inválido: '$hex' (esperado #rrggbb)" } return @( [Convert]::ToInt32($h.Substring(0, 2), 16), [Convert]::ToInt32($h.Substring(2, 2), 16), [Convert]::ToInt32($h.Substring(4, 2), 16) ) } # ¿Soporta truecolor el host actual? Heuristica simple: COLORTERM=truecolor o WT_SESSION presente. # Nota: dentro de class methods PS no expone $PSVersionTable / $env: directamente. static [bool] SupportsTrueColor() { $colorTerm = [Environment]::GetEnvironmentVariable('COLORTERM') if ($colorTerm -eq 'truecolor' -or $colorTerm -eq '24bit') { return $true } if ([Environment]::GetEnvironmentVariable('WT_SESSION')) { return $true } # Windows Terminal $psv = $global:PSVersionTable if ($psv -and $psv.PSVersion.Major -ge 7) { return $true } # pwsh 7+ moderno return $false } } |