Private/WriteMenu.ps1

<#
    Adapted from an early version of https://github.com/QuietusPlus/Write-Menu (MIT License)
    https://gist.github.com/QuietusPlus/59d8612ec13ea929704542eb0bd8d52c (CC-SA-4.0 License)
 
    Opted for the initial version as it was sufficient for this purpose.
    - Reworked logic for performance.
    - Removed redundancy.
    - Removed Clear-Host executions.
    - Comments and Cleanup.
#>

function WriteMenu {
    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "")]
    param (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [array]$Items,

        [Parameter()]
        [string]$Title = $null,

        [Parameter()]
        [int]$Page = 0
    )

    [System.Console]::CursorVisible = $false

    try {
        # Print title when applicable and determine number of lines dedicated to static content.
        if ($Title -ne $null) {
            [System.Console]::WriteLine("`n" + $Title + "`n")
            $AddedLines = 7
        }
        else {
            [System.Console]::WriteLine('')
            $AddedLines = 5
        }
        # Determine available lines for list items.
        $ListSize = ($host.UI.RawUI.WindowSize.Height - $AddedLines)

        # Console colours.
        $Foreground         = [System.Console]::ForegroundColor
        $Background         = [System.Console]::BackgroundColor
        # Invert for selection.
        $ForegroundSelected = $Background
        $BackgroundSelected = $Foreground

        # Number of entries in total.
        $ItemsTotal = $Items.Length

        # First entry of the current page.
        $FirstEntry = ($ListSize * $Page)

        # Total pages
        $PageTotal = [math]::Ceiling((($ItemsTotal - $ListSize) / $ListSize))

        # Handle variable size of the last page.
        if ($Page -eq $PageTotal) {
            $PageEntriesCount = ($ItemsTotal - ($ListSize * $PageTotal))
        }
        else {
            $PageEntriesCount = $ListSize
        }

        # Starting positions for the cursor
        $PositionCurrent  = 0
        $PositionSelected = 0
        $PositionTotal    = 0
        $PositionTop      = [System.Console]::CursorTop

        # Get entries for current page
        $PageEntries = @(foreach ($i in 0..$ListSize) {
            $Items[($FirstEntry + $i)]
        })

        do {
            # Initialize Loop
            $MenuLoop = $true

            # Write menu options
            [System.Console]::CursorTop = ($PositionTop - $PositionTotal)
            for ($PositionCurrent = 0; $PositionCurrent -le ($PageEntriesCount - 1); $PositionCurrent++) {
                [System.Console]::Write("`r")
                # Truncate entry
                $Entry = $PageEntries[$PositionCurrent]
                if ($Entry.Length -gt ($Host.UI.RawUI.WindowSize.Width - 2)) {
                    $Entry = $Entry.Substring(0,($Host.UI.RawUI.WindowSize.Width - 5)) + '...'
                }

                # If selected, invert colours
                if ($PositionCurrent -eq $PositionSelected) {
                    [System.Console]::BackgroundColor = $BackgroundSelected
                    [System.Console]::ForegroundColor = $ForegroundSelected
                    [System.Console]::Write(' ' + $Entry)
                    [System.Console]::BackgroundColor = $Background
                    [System.Console]::ForegroundColor = $Foreground
                } else {
                    [System.Console]::Write(' ' + $Entry)
                }
                [System.Console]::WriteLine('')
            }
            # Write pagination
            [System.Console]::WriteLine("`n $($LocalizedData.Page) $($Page + 1) / $($PageTotal + 1)")

            # Handle key input
            $InputKey = [System.Console]::ReadKey($true)
            if (($InputKey.Key -eq 'DownArrow') -and ($PositionSelected -lt ($PageEntriesCount - 1))) {
                $PositionSelected++
            }
            elseif (($InputKey.Key -eq 'UpArrow') -and ($PositionSelected -gt 0)) {
                $PositionSelected--
            }
            elseif ($InputKey.Key -eq 'Enter' -or $InputKey.Key -eq 'Escape') {
                $MenuLoop = $false
            }
            elseif ($InputKey.Key -eq 'LeftArrow') {
                if ($Page -ne 0) {
                    $Page--
                    $MenuLoop = $false
                }
            }
            elseif ($InputKey.Key -eq 'RightArrow') {
                if ($Page -ne $PageTotal) {
                    $Page++
                    $MenuLoop = $false
                }
            }

        } while ($MenuLoop)

        if ($InputKey.Key -eq 'Escape') {
            ClearMenu $PositionTop ($PageEntriesCount + $AddedLines)
            [System.Console]::CursorVisible = $true
        }
        elseif ($InputKey.Key -eq 'Enter') {
            ClearMenu $PositionTop ($PageEntriesCount + $AddedLines)
            $PageEntries[$PositionSelected]
            [System.Console]::CursorVisible = $true
        }
        elseif (($InputKey.Key -eq 'LeftArrow') -or ($InputKey.Key -eq 'RightArrow')) {
            ClearMenu $PositionTop ($PageEntriesCount + $AddedLines)
            WriteMenu -Items $Items -Page $Page -Title $Title
        }
    } catch {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    } finally {
        [System.Console]::CursorVisible = $true
    }
}