Private/Invoke-AACPagedOutput.ps1
|
function Invoke-AACPagedOutput { <# .SYNOPSIS Runs a script block that draws Spectre.Console output, then shows that output one screen at a time: "press any key for the next page". .DESCRIPTION While the script block runs, the global Spectre console is swapped for one that writes into a buffer, with the same width, color support and Unicode support as the real terminal. The buffered text (ANSI color codes included) is then written a page at a time, where a page is the terminal's height minus one line for the prompt. The real console is always put back, even if the script block throws. At the prompt, any key shows the next page, and A shows everything that's left without stopping again. Paging only makes sense when someone is at the keyboard. The output is written straight through, unpaged, when -NoPaging is given, when input or output is redirected (pipelines, CI, scheduled tasks), or when it all fits on one screen. -ReadKey exists for tests: it replaces the real key press. #> [CmdletBinding()] param( [Parameter(Mandatory)] [scriptblock] $ScriptBlock, [switch] $NoPaging, [int] $PageSize = 0, [scriptblock] $ReadKey = { [Console]::ReadKey($true) } ) $isInteractive = -not [Console]::IsInputRedirected -and -not [Console]::IsOutputRedirected if ($NoPaging -or (-not $isInteractive -and $PageSize -le 0)) { & $ScriptBlock return } $realConsole = [Spectre.Console.AnsiConsole]::Console $buffer = [System.IO.StringWriter]::new() $settings = [Spectre.Console.AnsiConsoleSettings]::new() $settings.Out = [Spectre.Console.AnsiConsoleOutput]::new($buffer) $settings.Ansi = [Spectre.Console.AnsiSupport]::Yes $settings.ColorSystem = [Spectre.Console.ColorSystemSupport]([string]$realConsole.Profile.Capabilities.ColorSystem) $settings.Interactive = [Spectre.Console.InteractionSupport]::No $capture = [Spectre.Console.AnsiConsole]::Create($settings) $capture.Profile.Width = $realConsole.Profile.Width $capture.Profile.Capabilities.Unicode = $realConsole.Profile.Capabilities.Unicode $capture.Profile.Capabilities.Ansi = $realConsole.Profile.Capabilities.Ansi try { [Spectre.Console.AnsiConsole]::Console = $capture & $ScriptBlock } finally { [Spectre.Console.AnsiConsole]::Console = $realConsole } $lines = @($buffer.ToString() -split '\r?\n') # The last line is the empty remainder after the final newline. if ($lines.Count -gt 0 -and $lines[-1] -eq '') { $lines = @($lines | Select-Object -SkipLast 1) } if ($PageSize -le 0) { $height = try { [Console]::WindowHeight } catch { 0 } $PageSize = [Math]::Max(10, $height - 1) } $out = [Console]::Out if ($lines.Count -le $PageSize) { foreach ($line in $lines) { $out.WriteLine($line) } return } $pageCount = [Math]::Ceiling($lines.Count / $PageSize) $esc = [char]27 for ($page = 0; $page -lt $pageCount; $page++) { $start = $page * $PageSize $end = [Math]::Min($start + $PageSize, $lines.Count) - 1 foreach ($index in $start..$end) { $out.WriteLine($lines[$index]) } if ($page -eq $pageCount - 1) { break } $prompt = " Page $($page + 1) of $pageCount - press any key for the next page, A to show the rest " $out.Write("$esc[7m$prompt$esc[0m") $key = & $ReadKey # Erase the prompt line so it doesn't stay in the scrollback. $out.Write("`r$(' ' * $prompt.Length)`r") if ($key -and [string]$key.KeyChar -eq 'a') { foreach ($index in ($end + 1)..($lines.Count - 1)) { $out.WriteLine($lines[$index]) } break } } } |