Ask.psm1
### Built from file: src\preamble.ps1 <# Ask Powershell Prompting made Pretty Bringing back the good old days of valid input with a TUI To report issues and be part of the discussion visit: https://github.com/natebarkei/Ask #> ### Built from file: src\ansi\ansi_codes.ps1 # ANSI Codes $Script:ESC = [char]27 $Script:a_U_ON = "$Script:ESC[4m" $Script:a_U_OFF = "$Script:ESC[24m" $Script:a_B_ON = "$Script:ESC[1m" $Script:a_B_OFF = "$Script:ESC[21m" $Script:a_CLLN = "$Script:ESC[K" $script:a_FgBlack = "$Script:ESC[30m" $script:a_FgRed = "$Script:ESC[31m" $script:a_FgGreen = "$Script:ESC[32m" $script:a_FgYellow = "$Script:ESC[33m" $script:a_FgBlue = "$Script:ESC[34m" $script:a_FgMagenta = "$Script:ESC[35m" $script:a_FgCyan = "$Script:ESC[36m" $script:a_FgWhite = "$Script:ESC[37m" $script:a_FgBBlack = "$Script:ESC[90m" $script:a_FgBRed = "$Script:ESC[91m" $script:a_FgBGreen = "$Script:ESC[92m" $script:a_FgBYellow = "$Script:ESC[93m" $script:a_FgBBlue = "$Script:ESC[94m" $script:a_FgBMagenta = "$Script:ESC[95m" $script:a_FgBCyan = "$Script:ESC[96m" $script:a_FgBWhite = "$Script:ESC[97m" $Script:AnsiLookup = @{ Black = "$Script:ESC[30m" Red = "$Script:ESC[31m" Green = "$Script:ESC[32m" Yellow = "$Script:ESC[33m" Blue = "$Script:ESC[34m" Magenta = "$Script:ESC[35m" Cyan = "$Script:ESC[36m" White = "$Script:ESC[37m" BBlack = "$Script:ESC[90m" BRed = "$Script:ESC[91m" BGreen = "$Script:ESC[92m" BYellow = "$Script:ESC[93m" BBlue = "$Script:ESC[94m" BMagenta = "$Script:ESC[95m" BCyan = "$Script:ESC[96m" BWhite = "$Script:ESC[97m" crlf = "`r`n" r = "`r" normal = "$Script:ESC[m" cl = "$Script:ESC[2K" } ### Built from file: src\ansi\nbCursorControl.ps1 function nbCursorHide(){ $host.ui.Write("$Script:ESC[?25l") } function nbCursorShow() { $host.ui.Write("$Script:ESC[?25h") } ### Built from file: src\ansi\nbMoveTo.ps1 function nbMoveTo([int32]$x,[int32]$y){ $host.ui.Write("$Script:ESC[$($y+1);${x}H") #$host.ui.RawUI.CursorPosition = [System.Management.Automation.Host.Coordinates]::new($x,$y) } ### Built from file: src\ansi\nbScreenRecover.ps1 function nbScreenRecover(){ #$AtNow = $host.UI.RawUI.CursorPosition $host.ui.Write("$Script:ESC[?1049l") $host.UI.RawUI.CursorPosition=$Script:curInitialPos } ### Built from file: src\ansi\nbScreenSwitch.ps1 $Script:curInitialPos=$null function nbScreenSwitch(){ #need to adjust positioning based on buffer size of the console #is the cursor beyond the window size, ie have we scrolled down? if ($host.UI.RawUI.CursorPosition.Y -gt $host.UI.RawUI.WindowSize.Height) { $top = $host.ui.RawUI.WindowPosition.Y } else { $top = 0 } $r = [System.Management.Automation.Host.Rectangle]::new(0, $top, $host.ui.rawui.windowsize.width,$top+$host.ui.RawUI.WindowSize.height) # Capture the screen buffer and current cursor position $b=$host.ui.RawUI.GetBufferContents($r) $Script:curInitialPos = $host.ui.RawUI.CursorPosition # Switch into the secondary screen $host.ui.Write("${ESC}[?1049h") # Write the buffer we captured into the alternate screen. $cWrite = [System.Management.Automation.Host.Coordinates]::new(0,0) $host.ui.RawUI.SetBufferContents($cWrite,$b) # Move the cursor to the exact location it was in on the primary screen $host.ui.Write("$Script:ESC[$($Script:curInitialPos.Y+1);0H") } ### Built from file: src\ansi\nbShowVersion.ps1 function nbShowVersion { param([string]$Version=$Script:Version) $ConsoleWidth = $host.ui.RawUI.WindowSize.Width $outVersion = $Script:Version if($outVersion.Length -lt $ConsoleWidth) {$outVersion=$outVersion + $(' ' * ($ConsoleWidth- $outVersion.Length))} Write-Host $outVersion -BackgroundColor Blue -ForegroundColor White } ### Built from file: src\ansi\nbWriteAnsi.ps1 $script:noColorMode = $false function nbColorEnable() {$script:noColorMode=$true} function nbColorDisable() {$script:noColorMode=$false} function nbWriteAnsi { [CmdletBinding()] param( [string]$Text ) $text.Split( [char]'<',[char]'>') | foreach-Object {$i = 0} { if($i % 2 -eq 0) { $host.ui.Write($_) } else { #Figure out our internal codes and translate to ansi if($Script:AnsiLookup.ContainsKey($_)) { $Ansi = $Script:AnsiLookup.Item($_) } if(-not [string]::IsNullOrEmpty($ansi)) {$host.ui.write($Ansi)} } $i++ } } ### Built from file: src\iconography.ps1 ## Iconography $Script:curInitialPos=$null $script:Question = '?' $script:Check='√' $script:Bullet='•' $script:Selector='>' $script:Bad = 'X' $script:Prompt = '›' $script:UpArrow = '↑' $script:DnArrow = '↓' ## ## Overwrite Specific Iconography with Emoji's if our terminal supports it. ## if($env:WT_SESSION -or $env:TERM_PROGRAM -eq 'vscode') { $script:Check='✔' $script:Selector='❯' $script:Bad = '✘' } ### Built from file: src\strings.ps1 ## Core strings $Script:Version = "Ask, Copyright 2015-2022 by Nate Barkei." $Script:str_UnhandledError = 'An uncaught error happened in Invoke-Ask. The command that called it has been terminated because there is no way to validate if a proper input was entered.' $Script:str_ConfirmYESno = '(Y/n)' $script:str_ConfirmNOyes = '(N/y)' $Script:str_FilterPrompt = " Filter: " $Script:str_FilterPlaceholder = "<BBlack>Type to filter" ### Built from file: src\coreinput.ps1 function nbWaitSilentKeyPress([array]$AllowedKeys=$null) { $AllowExit = $false $KeyPressOptions= 7 # 1=AllowCtrlC, 4=IncludeKeyDown, 2=NoEcho while(-not $AllowExit) { [System.Management.Automation.Host.KeyInfo]$KeyPress=$host.ui.RawUI.ReadKey($KeyPressOptions) if($null -eq $AllowedKeys) { $AllowExit=$true } else { if($AllowedKeys -contains $KeyPress.VirtualKeyCode) { $AllowExit = $true} Write-Verbose "Key pressed was: $($keypress.VirtualKeyCode)" } } return $KeyPress } ### Built from file: src\functions\confirm.ps1 $script:f_confirm = { if([string]::IsNullOrWhiteSpace($Message)){ $Message='Are you sure?' } $YN=$script:str_ConfirmNOyes if($DefaultTrue){$YN=$Script:str_ConfirmYESno} $promptString = "<BBlue>$script:Question <BWhite>$Message <BBlack>$yn $script:Prompt " nbWriteAnsi $promptString $keyPress = nbWaitSilentKeyPress -AllowedKeys 27,89,78,13,81 switch($keyPress.VirtualKeyCode) { 27 { $exitValue=$false } # ESC 81 { $exitValue=$false } # q 13 { if($DefaultTrue) {$exitValue=$true} else {$exitValue=$false} } # Enter 89 { $exitValue=$true} # Y 78 { $exitValue=$false} # N } $ClosingString = "<BGreen>$script:Check <BWhite>$message <BBlack>$script:Bullet <BGreen>$exitValue" } ### Built from file: src\functions\select.ps1 $script:f_select = { if([string]::IsNullOrWhiteSpace($Message)){$Message='Select an option?'} $promptString = "<cl><BBlue>$script:Question <BWhite>$Message <BBlack>$script:Prompt " # If we have more items than the max height of window right now -1 (for the prompt) # then we have to get clever $ReservedSpace=1 if(-not [string]::IsNullOrWhiteSpace($Filter) -or $ShowFilter) { $ReservedSpace = 2 } # Space for Filter prompt # This is where we get tricky with filters $ApplyFilter = { if([string]::IsNullOrWhiteSpace($filter)) { $FilteredItems = $Items } else { $FilteredItems = @($Items| ? {$_ -like "*$filter*"}) } $ViewItems = $Items.Length if($ViewItems -gt $host.ui.RawUI.WindowSize.height-($ReservedSpace+1)) { $ViewItems = $host.UI.RawUI.WindowSize.height - $ReservedSpace } if($FullScreen) { $ViewItems = $host.UI.RawUI.WindowSize.height - $ReservedSpace } $VisibleTop = 0 if($Selected -gt $filteredItems.Length-1) {$selected=0} } $PrintFilter = { if($ReservedSpace -gt 1) { nbWriteAnsi "<crlf>$Script:a_CLLN<r><white>$Script:str_FilterPrompt" if([string]::IsNullOrWhiteSpace($filter)) { nbWriteAnsi $Script:str_FilterPlaceholder } else { nbWriteAnsi $Filter } } } $PrintCurrentPage = { #if($filterRefresh) { #$host.ui.write("ViewItems $($ViewItems) VisibleTop=$($visibleTop) FilteredItemsLength=$($FilteredItems.Length)") #} for($i = 0; $i -lt $ViewItems; $i++){ $currentIndex = $VisibleTop+$i if($currentIndex -gt $FilteredItems.Length-1) { nbWriteAnsi "<crlf>1<cl><r>" # Clear the line #if($filterRefresh) {$host.ui.write('-'); $host.ui.RawUI.ReadKey(7)} } else { nbWriteAnsi ("<crlf>$Script:a_CLLN<r> <BBlack>" + $FilteredItems[$currentIndex]) #if($filterRefresh) {$host.ui.write('+'); $host.ui.RawUI.ReadKey(7)} } } } $PrintCurrentSelected = { nbMoveTo -x 0 -y ($FirstListItemY + ($Selected - $VisibleTop)) if($filteredItems.count -eq 0) { nbwriteAnsi " <BRed>$script:Bad No items match filter<r>" return } nbWriteAnsi ' <BGreen>' $host.ui.write($script:Selector) nbwriteAnsi " <BWhite>" $host.ui.write("$($FilteredItems[$selected])`r") } # Initial draw before loop nbCursorHide . $ApplyFilter # This needs to be called before using $FilteredItems below nbWriteAnsi $promptString nbWriteAnsi "$($FilteredItems[$selected])" . $PrintFilter . $PrintCurrentPage # Now for some clever crap. Figure out where the cursor is and subtract ViewItems to get # the start location of the list $FirstListItemY = $host.UI.RawUI.CursorPosition.Y - $viewItems+1 if($FirstListItemY -lt 0) {$FirstListItemY = 1} $continueLoop = $true while ($continueLoop) { . $printCurrentSelected # Show scroll arrows if they are required if($VisibleTop -gt 0) {} # Show Up if($visibleTop+$ViewItems -lt $FilteredItems.Count) {} # Show Dn <# # Our initial timeout goes here while(-not [console]::KeyAvailable) { MoveTo -x 0 -y ($FirstListItemY + ($Selected - $VisibleTop)) } #> # Blocking Keypress $keyPress=nbWaitSilentKeyPress #-AllowedKeys 27,40,38,13,33,34,35,36 nbWriteAnsi " <BBlack> $($FilteredItems[$selected])" # Erases the current selector $FilterRefresh=$false $screenRefresh=$false switch ($keyPress.VirtualKeyCode) { 27 {$continueLoop = $false; $exitValue = -1; break} 40 {if($Selected -lt $FilteredItems.Count-1) { $selected ++}; break} #down 38 {if($Selected -gt 0) {$selected --}; break} #up 33 {$selected-=$viewItems; if($selected -lt 0){$selected=0}; break} #PageUp 34 {$selected+=$ViewItems; if($selected -gt $FilteredItems.count-1){$selected=$FilteredItems.Count-1}; break} #PageDN 35 {$selected = $FilteredItems.Count-1; break} # End 36 {$selected = 0; break} # Home 13 { if($FilteredItems.Length -eq 0) {break} # Don't allow bad filtering $continueLoop=$false; if($Index) { [int]$exitValue = $items.IndexOf($FilteredItems[$selected]) } else { $exitValue = $FilteredItems[$selected] } break } 8 {if($filter.Length -gt 0) {$filter= $filter.Substring(0,$filter.Length-1)} $FilterRefresh=$true break} #Backspace 46 {$filter=''; $FilterRefresh=$true; break}# Delete key clears filter default { # We must be adding or removing the filter text if($keypress.Character -ne 0) { $filter += $keyPress.Character; $FilterRefresh=$true } } } if($selected -gt ($VisibleTop + $ViewItems -1)) { # Shift viewport down if($selected -eq $FilteredItems.Length-1) { $visibletop = $selected-$viewitems+1 if($VisibleTop -lt 0) {$VisibleTop = 0} } else { $visibletop = $Selected } $screenRefresh=$true } if($selected -lt $VisibleTop) { # Shift viewport up $visibleTop = $selected - $viewItems+1 if($VisibleTop -lt 0) {$VisibleTop = 0} $screenRefresh=$true } if ($FilterRefresh) { . $ApplyFilter $screenRefresh=$true } if($screenRefresh) { nbmoveto -x 0 -y ($FirstListItemY-1) # -1 required because we start with a crlf . $printCurrentPage } nbmoveto -x 0 -Y ($FirstListItemY-$ReservedSpace) nbWriteAnsi $promptString nbWriteAnsi "$($FilteredItems[$selected])" . $PrintFilter } nbCursorShow $ClosingString = "<BGreen>$script:Check <BWhite>$message <BBlack>$script:Bullet <BGreen>$($FilteredItems[$selected])" } ### Built from file: src\Invoke-Ask.ps1 <# .SYNOPSIS Ask pretty questions on the command line. #> function Invoke-Ask { [Alias('Ask')] [CmdletBinding(DefaultParameterSetName='help')] param( [Parameter(Mandatory=$true, ParameterSetName='confirm')][Alias('c')][switch]$Confirm, #[Parameter(Mandatory=$true, ParameterSetName='getinput')][switch]$GetInput, #[Parameter(Mandatory=$true, ParameterSetName='secret')][switch]$Secret, [Parameter(Mandatory=$true, ParameterSetName='select')][switch]$Select, #[Parameter(Mandatory=$true, ParameterSetName='multiselect')][switch]$MultiSelect, [Parameter(ParameterSetName='version')][switch]$Version, [Parameter(ParameterSetName='help')][switch]$Help, [Parameter(ParameterSetName='confirm')][switch]$DefaultTrue, #[Parameter(ParameterSetName='confirm')][Int]$Timeout, #[Parameter(ParameterSetName='getinput')][Parameter(ParameterSetName='secret')][switch]$AllowEmpty, #[Parameter(ParameterSetName='getinput')][string]$Default, #[Parameter(ParameterSetName='secret')][string]$Confirmation, #[Parameter(ParameterSetName='secret')][string]$MismatchErrorMessage, [Parameter(ParameterSetName='select')][int]$Selected=0, #[Parameter(ParameterSetName='multiselect')][int[]]$SelectedItems, [Parameter(Mandatory=$true, ParameterSetName='select')] [array]$Items, [Parameter(ParameterSetName='select')][switch]$Index, [Parameter(ParameterSetName='select')][switch]$ShowFilter, [Parameter(ParameterSetName='select')][string]$Filter=$null, [Parameter(HelpMessage='Message for the Prompt')] [Parameter(ParameterSetName='confirm')][Parameter(ParameterSetName='getinput')] [Parameter(ParameterSetName='secret')][Parameter(ParameterSetName='select')] [Parameter(ParameterSetName='multiselect')] [string]$Message, [switch]$NoColor, [Parameter(ParameterSetName='select')] [switch]$FullScreen ) process { # IF we actually display this to the end-user then our logic has failed and they should know. $ClosingString = 'An internal error has occured inside of Invoke-Ask.' if ($NoColor) { nbColorDisable } else { nbColorEnable } if($PSCmdlet.ParameterSetName -eq 'help'){ nbShowVersion Get-Help -Name Invoke-Ask return } trap { # This is our generic something went very bad so revert out trap nbScreenRecover nbCursorShow Write-Warning $Script:str_UnhandledError Write-Warning $_ break TerminateAskCallingFunction } if($PSCmdlet.ParameterSetName -eq 'version') { nbShowVersion return } # Switch into the Alt Screen Buffer nbScreenSwitch ## ## CONFIRM ## if($Confirm) { . $script:f_confirm } ## ## SELECT ## if($Select) { . $script:f_select } # Restore the screen and write whatever we're supposed to and return our exitvalue nbScreenRecover nbWriteAnsi $ClosingString nbWriteAnsi '<normal><crlf>' return $exitValue } } |