From a3372e8a6cc58dd829055f0050805dabf9b40f18 Mon Sep 17 00:00:00 2001 From: Shaun Lawrie Date: Sun, 18 Aug 2024 14:52:40 +1200 Subject: [PATCH] Ugh custom formatting is painful --- .../PwshSpectreConsole.format.ps1xml | 26 ++----------------- PwshSpectreConsole/PwshSpectreConsole.psm1 | 12 +++++++++ .../Spectre.Console.AnsiConsole.format.ps1 | 24 +---------------- .../private/Write-AnsiConsole.ps1 | 18 +++++++++++-- .../public/demo/Start-SpectreDemo.ps1 | 2 +- .../public/rendering/Out-SpectreHost.ps1 | 10 ++++--- 6 files changed, 39 insertions(+), 53 deletions(-) diff --git a/PwshSpectreConsole/PwshSpectreConsole.format.ps1xml b/PwshSpectreConsole/PwshSpectreConsole.format.ps1xml index debfb48a..98a54ea1 100644 --- a/PwshSpectreConsole/PwshSpectreConsole.format.ps1xml +++ b/PwshSpectreConsole/PwshSpectreConsole.format.ps1xml @@ -12,30 +12,8 @@ - # Work out if the current object is being piped to another command, there isn't access to the pipeline in the format view script block so it's using a janky regex workaround - try { - $line = $MyInvocation.Line - $start = $MyInvocation.OffsetInLine - $lineAfterOffset = $line.SubString($start, ($line.Length - $start)) - $targetIsInPipeline = $lineAfterOffset | Select-String "^[^;]+?\|" - $pipelineSegment = $lineAfterOffset | Select-String "^[^;]+?(;|$)" | Select-Object -ExpandProperty Matches -First 1 | Select-Object -ExpandProperty Value - $targetIsPipedToSpectreFunction = $pipelineSegment -match ".*\|.*(Write|Format|Out)-Spectre.*" - Write-Debug "Line: $line" - Write-Debug "Start: $start" - Write-Debug "Line after offset: $lineAfterOffset" - Write-Debug "Target is in pipeline: $targetIsInPipeline" - Write-Debug "Pipeline segment: $pipelineSegment" - Write-Debug "Target is piped to Spectre function: $targetIsPipedToSpectreFunction" - } catch { - Write-Debug "Failed to discover pipeline state for Spectre.Console.Rendering.Renderable: $_" - } - - if ($targetIsInPipeline -and -not $targetIsPipedToSpectreFunction) { - $_ - } else { - $_ | Out-SpectreHost - } - + $_ | Out-SpectreHost -CustomItemFormatter + diff --git a/PwshSpectreConsole/PwshSpectreConsole.psm1 b/PwshSpectreConsole/PwshSpectreConsole.psm1 index 3b12cbb6..c756664c 100644 --- a/PwshSpectreConsole/PwshSpectreConsole.psm1 +++ b/PwshSpectreConsole/PwshSpectreConsole.psm1 @@ -5,6 +5,18 @@ $script:DefaultValueColor = [Spectre.Console.Color]::Grey $script:DefaultTableHeaderColor = [Spectre.Console.Color]::Default $script:DefaultTableTextColor = [Spectre.Console.Color]::Default +# For widgets that can be streamed to the console as raw text, prompts/progress widgets do not use this. +# This allows the terminal to process them as text so they can be dumped like: +# PS> $widget = "Hello, World!" | Format-SpectrePanel -Title "My Panel" -Color Blue -Expand +# PS> $widget # uses the default powershell console writer +# PS> $widget > file.txt # redirects as string data to file +# PS> $widget | Out-SpectreHost # uses a dedicated console writer that doesn't pad the object like the default formatter +$script:SpectreConsoleWriter = [System.IO.StringWriter]::new() +$script:SpectreConsoleOutput = [Spectre.Console.AnsiConsoleOutput]::new($script:SpectreConsoleWriter) +$script:SpectreConsoleSettings = [Spectre.Console.AnsiConsoleSettings]::new() +$script:SpectreConsoleSettings.Out = $script:SpectreConsoleOutput +$script:SpectreConsole = [Spectre.Console.AnsiConsole]::Create($script:SpectreConsoleSettings) + foreach ($directory in @('private', 'public')) { Get-ChildItem -Path "$PSScriptRoot\$directory\*.ps1" -Recurse | ForEach-Object { . $_.FullName diff --git a/PwshSpectreConsole/formatting/Spectre.Console.AnsiConsole.format.ps1 b/PwshSpectreConsole/formatting/Spectre.Console.AnsiConsole.format.ps1 index 5804b3af..36fcc0c8 100644 --- a/PwshSpectreConsole/formatting/Spectre.Console.AnsiConsole.format.ps1 +++ b/PwshSpectreConsole/formatting/Spectre.Console.AnsiConsole.format.ps1 @@ -1,26 +1,4 @@ # TODO - Ask @startautomating how this can be done better Write-FormatView -TypeName "Spectre.Console.Rendering.Renderable" -Action { - # Work out if the current object is being piped to another command, there isn't access to the pipeline in the format view script block so it's using a janky regex workaround - try { - $line = $MyInvocation.Line - $start = $MyInvocation.OffsetInLine - $lineAfterOffset = $line.SubString($start, ($line.Length - $start)) - $targetIsInPipeline = $lineAfterOffset | Select-String "^[^;]+?\|" - $pipelineSegment = $lineAfterOffset | Select-String "^[^;]+?(;|$)" | Select-Object -ExpandProperty Matches -First 1 | Select-Object -ExpandProperty Value - $targetIsPipedToSpectreFunction = $pipelineSegment -match ".*\|.*(Write|Format|Out)-Spectre.*" - Write-Debug "Line: $line" - Write-Debug "Start: $start" - Write-Debug "Line after offset: $lineAfterOffset" - Write-Debug "Target is in pipeline: $targetIsInPipeline" - Write-Debug "Pipeline segment: $pipelineSegment" - Write-Debug "Target is piped to Spectre function: $targetIsPipedToSpectreFunction" - } catch { - Write-Debug "Failed to discover pipeline state for Spectre.Console.Rendering.Renderable: $_" - } - - if ($targetIsInPipeline -and -not $targetIsPipedToSpectreFunction) { - $_ - } else { - $_ | Out-SpectreHost - } + $_ | Out-SpectreHost -CustomItemFormatter } \ No newline at end of file diff --git a/PwshSpectreConsole/private/Write-AnsiConsole.ps1 b/PwshSpectreConsole/private/Write-AnsiConsole.ps1 index ad4554e8..0c328984 100644 --- a/PwshSpectreConsole/private/Write-AnsiConsole.ps1 +++ b/PwshSpectreConsole/private/Write-AnsiConsole.ps1 @@ -18,7 +18,21 @@ function Write-AnsiConsole { param( [Parameter(Mandatory)] [RenderableTransformationAttribute()] - [object] $RenderableObject + [object] $RenderableObject, + [switch] $CustomItemFormatter ) - [Spectre.Console.AnsiConsole]::Render($RenderableObject) + + if ($CustomItemFormatter) { + # ps1xml CustomItem formatters mangle the output because it uses the last character of the buffer width for itself + $script:SpectreConsole.Profile.Width = $Host.UI.RawUI.BufferSize.Width - 1 + } else { + $script:SpectreConsole.Profile.Width = $Host.UI.RawUI.BufferSize.Width + } + + $script:SpectreConsole.Write($RenderableObject) + $output = $script:SpectreConsoleWriter.ToString() + + $output.ToString().TrimEnd() + + $null = $script:SpectreConsoleWriter.GetStringBuilder().Clear() } \ No newline at end of file diff --git a/PwshSpectreConsole/public/demo/Start-SpectreDemo.ps1 b/PwshSpectreConsole/public/demo/Start-SpectreDemo.ps1 index 906360fa..c793c990 100644 --- a/PwshSpectreConsole/public/demo/Start-SpectreDemo.ps1 +++ b/PwshSpectreConsole/public/demo/Start-SpectreDemo.ps1 @@ -217,7 +217,7 @@ $( Clear-Host $example = @' -Get-Module PwshSpectreConsole | Select-Object PrivateData | Format-SpectreJson -Expand +Get-Module PwshSpectreConsole | Select-Object PrivateData | Format-SpectreJson '@ $example | Write-SpectreExample -Title "Json Data" -Description "Spectre Console can format JSON with syntax highlighting thanks to https://github.com/trackd" Invoke-Expression $example diff --git a/PwshSpectreConsole/public/rendering/Out-SpectreHost.ps1 b/PwshSpectreConsole/public/rendering/Out-SpectreHost.ps1 index d645f009..9fcbaab6 100644 --- a/PwshSpectreConsole/public/rendering/Out-SpectreHost.ps1 +++ b/PwshSpectreConsole/public/rendering/Out-SpectreHost.ps1 @@ -12,6 +12,9 @@ function Out-SpectreHost { .PARAMETER Data The data to write to the console. + .PARAMETER CustomItemFormatter + The default host customitem formatter has some restrictions, it needs to be one char less wide than when outputting to the standard console or it will wrap. + .EXAMPLE $table = New-SpectreTable -Data $data $table | Out-SpectreHost @@ -20,7 +23,8 @@ function Out-SpectreHost { param ( [Parameter(ValueFromPipeline, Mandatory)] [RenderableTransformationAttribute()] - [object] $Data + [object] $Data, + [switch] $CustomItemFormatter ) begin {} @@ -28,10 +32,10 @@ function Out-SpectreHost { process { if ($Data -is [array]) { foreach ($dataItem in $Data) { - Write-AnsiConsole -RenderableObject $dataItem + Write-AnsiConsole -RenderableObject $dataItem -CustomItemFormatter:$CustomItemFormatter } } else { - Write-AnsiConsole -RenderableObject $Data + Write-AnsiConsole -RenderableObject $Data -CustomItemFormatter:$CustomItemFormatter } }