Public/Runtime/Start-ElmProgram.ps1
|
function Start-ElmProgram { <# .SYNOPSIS Starts an Elm-architecture program in the terminal. .DESCRIPTION Calls InitFn to obtain the initial model, creates a terminal driver to read keyboard input, runs the MVU event loop until a Quit command is returned, then tears down the driver and returns the final model. The three required scriptblocks mirror the Elm Architecture: - InitFn : () -> { Model; Cmd } - UpdateFn : ($msg, $model) -> { Model; Cmd } - ViewFn : ($model) -> view-tree node .PARAMETER InitFn Scriptblock with no parameters that returns a PSCustomObject with Model and Cmd properties. Cmd may be $null. .PARAMETER UpdateFn Scriptblock accepting ($msg, $model) that returns a PSCustomObject with Model and Cmd properties. Return Cmd.Type = 'Quit' to exit the event loop. .PARAMETER ViewFn Scriptblock accepting ($model) that returns a view-tree node (Type 'Text' or 'Box') produced by New-ElmText, New-ElmBox, or New-ElmRow. .PARAMETER Width Terminal width in columns used for layout. Defaults to the current terminal width ([Console]::WindowWidth). If the terminal reports no width (e.g. no TTY), falls back to 80. Must not exceed the actual terminal width - if it does, a terminating error is thrown with instructions to resize or omit the parameter. .PARAMETER Height Terminal height in rows used for layout. Defaults to the current terminal height ([Console]::WindowHeight). If the terminal reports no height, falls back to 24. Must not exceed the actual terminal height. .OUTPUTS PSCustomObject - the final model at the time the event loop exited. .EXAMPLE $init = { [PSCustomObject]@{ Model = [PSCustomObject]@{ Count = 0 }; Cmd = $null } } $update = { param($msg, $model) $newCount = if ($msg -eq 'Inc') { $model.Count + 1 } else { $model.Count } [PSCustomObject]@{ Model = [PSCustomObject]@{ Count = $newCount }; Cmd = $null } } $view = { param($model) New-ElmText -Content "Count: $($model.Count)" } Start-ElmProgram -InitFn $init -UpdateFn $update -ViewFn $view .NOTES Requires a terminal that supports ANSI escape sequences. On Windows, ensure Enable-VirtualTerminalProcessing has been called before invoking this function. #> [CmdletBinding()] param( [Parameter(Mandatory)] [scriptblock]$InitFn, [Parameter(Mandatory)] [scriptblock]$UpdateFn, [Parameter(Mandatory)] [scriptblock]$ViewFn, [Parameter()] [int]$Width = 0, [Parameter()] [int]$Height = 0 ) # Ensure ANSI/VT processing is active (required on Windows PS5.1/conhost; no-op elsewhere) $null = Enable-VirtualTerminal # Resolve actual terminal dimensions, falling back if running without a TTY $termWidth = if ([Console]::WindowWidth -gt 0) { [Console]::WindowWidth } else { 80 } $termHeight = if ([Console]::WindowHeight -gt 0) { [Console]::WindowHeight } else { 24 } # Validate explicit sizes - must fit in the real terminal if ($PSBoundParameters.ContainsKey('Width') -and $Width -gt $termWidth) { $ex = [System.ArgumentOutOfRangeException]::new( 'Width', "Requested width ($Width) exceeds terminal width ($termWidth). " + 'Resize the terminal or omit -Width to fill the terminal automatically.' ) $err = [System.Management.Automation.ErrorRecord]::new( $ex, 'TerminalTooSmall', [System.Management.Automation.ErrorCategory]::InvalidArgument, $Width ) $PSCmdlet.ThrowTerminatingError($err) } if ($PSBoundParameters.ContainsKey('Height') -and $Height -gt $termHeight) { $ex = [System.ArgumentOutOfRangeException]::new( 'Height', "Requested height ($Height) exceeds terminal height ($termHeight). " + 'Resize the terminal or omit -Height to fill the terminal automatically.' ) $err = [System.Management.Automation.ErrorRecord]::new( $ex, 'TerminalTooSmall', [System.Management.Automation.ErrorCategory]::InvalidArgument, $Height ) $PSCmdlet.ThrowTerminatingError($err) } $resolvedWidth = if ($PSBoundParameters.ContainsKey('Width')) { $Width } else { $termWidth } $resolvedHeight = if ($PSBoundParameters.ContainsKey('Height')) { $Height } else { $termHeight } $driver = New-ElmTerminalDriver -AltScreen $initResult = & $InitFn $initialModel = $initResult.Model try { $finalModel = Invoke-ElmEventLoop ` -InitialModel $initialModel ` -UpdateFn $UpdateFn ` -ViewFn $ViewFn ` -InputQueue $driver.InputQueue ` -TerminalWidth $resolvedWidth ` -TerminalHeight $resolvedHeight } finally { & $driver.Stop } return $finalModel } |