Private/FunctionBuilderRenderer.ps1
$script:LogMessages = [System.Collections.Queue]::new() $script:LogMessageColors = @{ "INF" = "White" "WRN" = "Yellow" "ERR" = "Red" } $script:LogMessagesMaxCount = 8 $script:FunctionTopLeft = @{X = 0; Y = 0} $script:RendererBackground = @{ R = 35; G = 35; B = 35 } $script:FunctionVersion = 1 $script:InitialPrePrompt = $null $script:InitialPrompt = $null $script:NonInteractive = $false function Initialize-AifbRenderer { <# .SYNOPSIS Setup the function renderer at the current cursor position, this will be considered the top left of the function for each draw #> param ( [string] $InitialPrePrompt, [string] $InitialPrompt, [bool] $NonInteractive ) $script:FunctionTopLeft.X = $Host.UI.RawUI.CursorPosition.X $script:FunctionTopLeft.Y = $Host.UI.RawUI.CursorPosition.Y + 1 $script:LogMessages = [System.Collections.Queue]::new() $script:FunctionVersion = 0 $script:InitialPrePrompt = $InitialPrePrompt $script:InitialPrompt = $InitialPrompt $script:NonInteractive = $NonInteractive } function Write-AifbFunctionOutput { <# .SYNOPSIS This function writes a function to the terminal with optional syntax highlighting .DESCRIPTION Using some cursor manipulation and Write-Host this re-renders overtop of itself and clears the rest of the text on the terminal. Then the function text is drawn and the log data is written underneath it. #> param ( # The text of the function to render [string] $FunctionText, # Prompt info [string] $Prompt, # Extents to highlight as issues [array] $HighlightExtents, # Lines to highlight as issues [array] $HighlightLines, # Whether to syntax highlight the function [switch] $SyntaxHighlight, # The background color for the code block [hashtable] $BackgroundRgb = $script:RendererBackground, # Don't output the log viewer [switch] $NoLogMessages ) if($script:NonInteractive) { return } $FunctionText = $FunctionText.Trim() + "`n`n<#`nAIFunctionBuilder Iteration $([int]$script:FunctionVersion++)`n$Prompt`n#>" $script:FunctionLines = @() # Write it all to the terminal and don't overwrite on every render in verbose mode, this makes debugging easier if($VerbosePreference -ne "SilentlyContinue") { Write-Verbose "Function text:`n$FunctionText" return } # Draw from the top left of the terminal window [Console]::CursorVisible = $false [Console]::SetCursorPosition(0, 0) if($script:InitialPrePrompt) { Write-Host -ForegroundColor Cyan -NoNewline "$($script:InitialPrePrompt): " Write-Host -NoNewline $script:InitialPrompt } else { Write-Host -NoNewline $script:InitialPrompt } [Console]::WriteLine(" " * ($Host.UI.RawUI.WindowSize.Width - $Host.UI.RawUI.CursorPosition.X)) [Console]::WriteLine(" " * ($Host.UI.RawUI.WindowSize.Width)) Write-Codeblock -Text $FunctionText -ShowLineNumbers -HighlightExtents $HighlightExtents -HighlightLines $HighlightLines -SyntaxHighlight:$SyntaxHighlight # Blank out the rest of the terminal $endOfFunctionPosition = $Host.UI.RawUI.CursorPosition $clearingBuffer = "" 1..($Host.UI.RawUI.WindowSize.Height - $Host.UI.RawUI.CursorPosition.Y) | Foreach-Object { $clearingBuffer += (" " * $Host.UI.RawUI.WindowSize.Width) } [Console]::Write($clearingBuffer) [Console]::SetCursorPosition($endOfFunctionPosition.X, $endOfFunctionPosition.Y) Write-Host "" # Write the log messages under the function if(!$NoLogMessages) { Write-AifbLogMessages } [Console]::CursorVisible = $true } function Add-AifbLogMessage { <# .SYNOPSIS Add a log message to the function builder log. #> param ( # The message to add [string] $Message, # The level to log it at [ValidateSet("INF", "WRN", "ERR")] [string] $Level = "INF", # Whether to skip rendering the latest log to the terminal [switch] $NoRender ) Write-Verbose "$Level $Message" $logItem = @{ Date = (Get-Date).ToString("HH:mm:ss") Message = $Message Level = $Level } $script:LogMessages.Enqueue($logItem) if($script:LogMessages.Count -gt $script:LogMessagesMaxCount) { $script:LogMessages.Dequeue() | Out-Null } } function Write-AifbLogMessages { <# .SYNOPSIS Write out the current list of log messages to the terminal. #> if($VerbosePreference) { return } $consoleWidth = $Host.UI.RawUI.WindowSize.Width $script:LogMessages | Foreach-Object { $logPrefix = "$($_.Date) $($_.Level.PadRight(4))" $line = $_.Message -replace "`n", ". " -replace "`r", "" $messageWidth = $consoleWidth - $logPrefix.Length - 1 if($line.Length -gt $messageWidth) { $lines = ($line | Select-String "(.{1,$messageWidth})+").Matches.Groups[1].Captures.Value } else { $lines = @($line) } $lineNumber = 0 foreach($line in $lines) { if($lineNumber -eq 0) { $message = $logPrefix + $line Write-Host -NoNewline -ForegroundColor $script:LogMessageColors[$_.Level] ($message + (" " * ($consoleWidth - $message.Length))) } else { $message = (" " * $logPrefix.Length) + $line Write-Host -NoNewline -ForegroundColor $script:LogMessageColors[$_.Level] ($message + (" " * ($consoleWidth - $message.Length))) } $lineNumber++ } } Write-Host "`n" } |