Skip to content

Instantly share code, notes, and snippets.

@hapylestat
Last active November 8, 2022 08:11
Show Gist options
  • Save hapylestat/58fa0e9b543f4eefd9588ce90c4b1fbc to your computer and use it in GitHub Desktop.
Save hapylestat/58fa0e9b543f4eefd9588ce90c4b1fbc to your computer and use it in GitHub Desktop.
#!/bin/bash
# shellcheck disable=SC2155,SC2015,SC2183
unselectedColor="\033[38;05;188m"
selectedColor="\033[38;05;232;48;05;188m"
resetColor="\033[m"
moveCursor() {
tput cup "$2" "$1"
}
moveCursorEX() {
echo -ne "\033[$(($2+1));$1f"
}
redrawMenuItems() {
local -n _menuItems=$1
local startPos=$2
local pos=$3
local oldPos=$4
# +1 comes from leading new line in the menu
local menuLen=$((${#_menuItems[@]} + 2))
local menuOldPosY=$((startPos - (menuLen - oldPos)))
local menuNewPosY=$((startPos - (menuLen - pos)))
moveCursorEX "0" "${menuOldPosY}"
echo -ne "\t${unselectedColor}${oldPos}. ${_menuItems[${oldPos}]}${resetColor}"
moveCursorEX "0" "${menuNewPosY}"
echo -ne "\t${selectedColor}${pos}. ${_menuItems[${pos}]}${resetColor}"
moveCursorEX "0" "${startPos}"
}
drawMenu() {
local -n _menuItems=$1
local menuPosition=$2
local menuTitle="$3"
local menuwidth=${#menuTitle}
local menuLen=$((${#_menuItems[@]}))
echo -ne "\t${unselectedColor}"; printf '%*s' "${menuwidth}" | tr ' ' "="; echo -e "${resetColor}"
echo -ne "\t${unselectedColor}"; echo -e " $menuTitle ${resetColor}"
echo -ne "\t${unselectedColor}"; printf '%*s' "${menuwidth}" | tr ' ' "="; echo -e "${resetColor}"
echo ""
for i in $(seq 0 ${menuLen}); do
echo -ne "\t"
if [[ $i -eq ${menuPosition} ]]; then
echo -e "${selectedColor}$i. ${_menuItems[${i}]}${resetColor}"
else
if [[ -n "${_menuItems[${i}]}" ]]; then
echo -e "${unselectedColor}$i. ${_menuItems[${i}]}${resetColor}"
fi
fi
done
# leading new line
echo
}
getCurrentPos(){
local _col; local _row
# shellcheck disable=SC2162
IFS=';' read -sdR -p $'\E[6n' _row _col
echo "${_col} ${_row#*[}"
}
readKey(){
read -rsN 1 _key
printf %d "'${_key}" # %x for hex
}
menu(){
local menuItems=($1)
local menuTitle="$2"
local keyCode=(0)
local pos=0
local oldPos=0
drawMenu "menuItems" "${pos}" "${menuTitle}"
local startPosStr=$(getCurrentPos);
local startPos="${startPosStr#* }"
while [[ ${keyCode[0]} -ne 10 ]]; do
local keyCode=("$(readKey)") # byte 1
if [[ ${keyCode[0]} -eq 27 ]]; then # escape character
local keyCode+=("$(readKey)") # byte 2
if [[ ${keyCode[-1]} -ne 27 ]]; then # checking if user pressed actual
local keyCode+=("$(readKey)") # byte 3
if [[ "51 50 48 52 53 54" =~ (^|[[:space:]])"${keyCode[2]}"($|[[:space:]]) ]]; then
while [[ ${keyCode[-1]} -ne 126 ]]; do
local keyCode+=("$(readKey)")
done
fi
if [[ "49" =~ (^|[[:space:]])"${keyCode[2]}"($|[[:space:]]) ]]; then
local keyCode+=("$(readKey)") # byte 4
[[ ${keyCode[-1]} -ne 126 ]] && local keyCode+=("$(readKey)") # byte 5
[[ ${keyCode[-1]} -eq 59 ]] && local keyCode+=("$(readKey)") # byte 5 check
[[ ${keyCode[-1]} -ne 126 ]] && local keyCode+=("$(readKey)")
fi
fi
fi
local oldPos=${pos}
case "${keyCode[*]}" in
"27 91 65") local pos=$((pos - 1));; # up
"27 91 66") local pos=$((pos + 1));; # down
"27 91 53 126") local pos=$((pos - 2));; # pgup
"27 91 54 126") local pos=$((pos + 2));; # pgdn
"27 91 72") local pos=0;; # home
"27 91 70") local pos=$((${#menuItems[*]} - 1));; # end
esac
[[ ${pos} -lt 0 ]] && local pos=0
[[ ${pos} -ge ${#menuItems[*]} ]] && local pos=$((${#menuItems[*]} - 1))
redrawMenuItems "menuItems" "${startPos}" "${pos}" "${oldPos}"
done
return "${pos}"
}
menu "Menu_Item_1 Menu_Item_2 Menu_Item_3 Menu_item_4 Menu_item_5" "Title Here"
case "$?" in
0) echo "selected 0";;
1) echo "selected 1";;
2) echo "selected 2";;
3) echo "selected 3";;
4) echo "selected 4";;
esac
function moveCursor{ param($position)
$host.UI.RawUI.CursorPosition = $position
}
function RedrawMenuItems{
param ([array]$menuItems, $oldMenuPos=0, $menuPosition=0, $currPos)
# +1 comes from leading new line in the menu
$menuLen = $menuItems.Count + 1
$fcolor = $host.UI.RawUI.ForegroundColor
$bcolor = $host.UI.RawUI.BackgroundColor
$menuOldPos = New-Object System.Management.Automation.Host.Coordinates(0, ($currPos.Y - ($menuLen - $oldMenuPos)))
$menuNewPos = New-Object System.Management.Automation.Host.Coordinates(0, ($currPos.Y - ($menuLen - $menuPosition)))
moveCursor $menuOldPos
Write-Host "`t" -NoNewLine
Write-Host "$oldMenuPos. $($menuItems[$oldMenuPos])" -fore $fcolor -back $bcolor -NoNewLine
moveCursor $menuNewPos
Write-Host "`t" -NoNewLine
Write-Host "$menuPosition. $($menuItems[$menuPosition])" -fore $bcolor -back $fcolor -NoNewLine
moveCursor $currPos
}
function DrawMenu { param ([array]$menuItems, $menuPosition, $menuTitel)
$fcolor = $host.UI.RawUI.ForegroundColor
$bcolor = $host.UI.RawUI.BackgroundColor
$menuwidth = $menuTitel.length + 4
Write-Host "`t" -NoNewLine; Write-Host ("=" * $menuwidth) -fore $fcolor -back $bcolor
Write-Host "`t" -NoNewLine; Write-Host " $menuTitel " -fore $fcolor -back $bcolor
Write-Host "`t" -NoNewLine; Write-Host ("=" * $menuwidth) -fore $fcolor -back $bcolor
Write-Host ""
for ($i = 0; $i -le $menuItems.length;$i++) {
Write-Host "`t" -NoNewLine
if ($i -eq $menuPosition) {
Write-Host "$i. $($menuItems[$i])" -fore $bcolor -back $fcolor -NoNewline
Write-Host "" -fore $fcolor -back $bcolor
} else {
if ($($menuItems[$i])) {
Write-Host "$i. $($menuItems[$i])" -fore $fcolor -back $bcolor
}
}
}
# leading new line
Write-Host ""
}
function Menu { param ([array]$menuItems, $menuTitel = "MENU")
$vkeycode = 0
$pos = 0
$oldPos = 0
DrawMenu $menuItems $pos $menuTitel
$currPos=$host.UI.RawUI.CursorPosition
While ($vkeycode -ne 13) {
$press = $host.ui.rawui.readkey("NoEcho,IncludeKeyDown")
$vkeycode = $press.virtualkeycode # https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
Write-host "$($press.character)" -NoNewLine
$oldPos=$pos;
Switch ($vkeycode) {
33 {$pos = if ($pos - 2 -gt 0) {$pos - 2} Else {0}} #PgUP
34 {$pos = if ($pos + 2 -lt @($menuItems).length) {$pos + 2} Else {@($menuItems).length}} #PgDn
35 {$pos = @($menuItems).length} # End
36 {$pos = 0} # Up
38 {$pos--} # Arrow UP
40 {$pos++} # Arrow DOWN
}
if ($pos -lt 0) {$pos = 0}
if ($pos -ge $menuItems.length) {$pos = $menuItems.length -1}
RedrawMenuItems $menuItems $oldPos $pos $currPos
}
Write-Output $pos
}
$bad = "a", "b", "c"
$selection = Menu $bad "Title"
Switch ($selection){
0 { Write-Output "a" }
1 { Write-Output "b" }
2 { Write-Output "c" }
}
@Deas-h
Copy link

Deas-h commented Oct 28, 2022

Hello, I love your PowerShell menu and want to use it in one of my scripts. During implementation i noticed, that PgUp/PgDn/End does not work as expected. I was able to fix this. Here is my modified code. I also added a variable for the page size for PgUp/PgDn and enabled rollover to be able to jump from the last entry to the first and vice versa.

Function Menu { param ([array]$menuItems, $menuTitel = "MENU")

$vkeycode = 0
$pos = 0
$oldPos = 0
$pageSize = 5
DrawMenu $menuItems $pos $menuTitel
$currPos=$host.UI.RawUI.CursorPosition
While ($vkeycode -ne 13) {
	$press = $host.ui.rawui.readkey("NoEcho,IncludeKeyDown")
	$vkeycode = $press.virtualkeycode  # https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
	Write-host "$($press.character)" -NoNewLine
	$oldPos=$pos

	Switch ($vkeycode) {
		33 {$pos = if ($pos - $pageSize -ge 0) {$pos - 5} Else {$menuItems.length -1}} #PgUP
		34 {$pos = if ($pos + $pageSize -le $menuItems.length -1) {$pos + $pageSize} Else {0}} #PgDn
		35 {$pos = $menuItems.length -1} # End
		36 {$pos = 0} # Pos1/Home
		38 {$pos--} # Arrow UP
		40 {$pos++} # Arrow DOWN
	}
	if ($pos -lt 0) {$pos = $menuItems.length -1} # Rollover to last entry
	if ($pos -ge $menuItems.length) {$pos = 0} # Rollover to first entry
	RedrawMenuItems $menuItems $oldPos $pos $currPos
}
Write-Output $pos

}

I also have one problem - my menu has about 50 entries and it would be great if your script could make columns. I found a script that is able to do this, but it redraws the menu on every move whichis very annoying.

https://community.spiceworks.com/scripts/show/4785-create-menu-2-0-arrow-key-driven-powershell-menu-for-scripts

Do you think it would be possible for you to add columns to your script? That would be great!

Brgds Deas

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment