Those of you familiar with bash's select builtin know that the automated layout of choices is a nice feature. Last week while prototyping some SQL I became frustrated with select, mostly because you must type in a number and then press the Enter key. Remembering the TUI interfaces of the 1980's, I set about writing hkselect, a hotkey driven replacement for select. Here are the main features:
- Automated layout of choices.
- Assign item hotkey with &.
- Automatic hotkey assignment if & is missing.
- Bind escaped keys to callback functions.
- Transparently caches layout information and composed labels.
Full project is here
Below is an example bash program with a functioning hotkey TUI that includes callbacks for F1, F2, and F3:
# Comments from hkselect() implementation in bash++ | |
function hkselect | |
################################################ | |
# Approximate replacement for bash's builtin `select'. | |
# hkselect() does not require the user to press enter key | |
# to finalize choice. | |
# Usage: hkselect [command_flags] arg1 ... | |
# | |
# User choices are supplied as args to the function, and | |
# the hotkey may be indicated by placing an `&' in | |
# the preceding character, e.g. | |
# press&kplease | |
# where `k' is the hotkey. If a hotkey is not indicated, | |
# then one will be assigned automatically. | |
# | |
# Command flags: | |
# -b 'keystr|funcname|arg1 arg2 ...' Bind escape-based key (F1-F12, Arrows, ...) to a function | |
# -B global_arr_name Array of -b arguments | |
# -p left_pad Left margin padding count | |
# -r 'rebuke string' String to print when user pressed invalid key | |
# -s 'subtitle string' Subtitle to print below choices | |
# -w max_width Limit the width of choices layout | |
# | |
# Global variables: | |
# HKSELKEY - key the user pressed when making a successful choice | |
# REPLY - zero based index of the arg whose hotkey was pressed | |
# | |
# RETURN: upon return the value of HKSELKEY will contain the key which | |
# the user pressed, and REPLY will contain the 0-based index of the | |
# matching argument passed when hkselect() was called |
#!/bin/bash | |
############################################################## | |
# Script to demonstrate use of hkselect() function in bash++ | |
# | |
# John Robertson <john@rrci.com> | |
# Initial release: Tue Mar 30 11:02:44 EDT 2021 | |
# | |
# Halt on error, no globbing, no unbound variables | |
set -efu | |
# import bash++ facilities | |
source ../bash++ | |
# Flag so we know when a bound key was pressed | |
is_boundKey=0 | |
function FnKey_cb | |
############################################################### | |
# This function is bound to function keys | |
{ | |
1>&2 echo -en "\nI can see you've pressed $1\nPress return to continue " | |
read | |
# Note that a bound key callback cause hkselect() to return. | |
is_boundKey=1 | |
} | |
function cleanup | |
################################################ | |
# Cleanup on exit | |
{ | |
# Restore cursor | |
tput cvvis | |
# Reset any text attributes | |
tput sgr0 | |
} | |
################################### | |
### Execution starts here ######### | |
################################### | |
# cleanup on exit | |
trap cleanup 0 | |
# Data might come from a dynamic source, like a file or pipe or ... | |
declare -a DATA_CHOICES=([0]=one [1]=two [2]=three) | |
# Strings we'll use repeatedly | |
PAD=" " | |
REBUKE="${PAD}$(tput bold)Please make a selection, or press a function key.$(tput sgr0)" | |
SUBTITLE="${PAD}You will write awesome programs with $(tput bold)bash++$(tput sgr0)" | |
# Loop processing user keystrokes | |
while true; do | |
# Clear the terminal | |
tput clear | |
# Print banner | |
1>&2 echo -e "\n${PAD}$(tput rev)Welcome to the hkselect example program$(tput sgr0)" | |
1>&2 echo -e "${PAD}$(tput bold)Press a function key!$(tput sgr0)" | |
# Wait for user to press a valid key | |
hkselect \ | |
-r "$REBUKE"\ | |
-s "$SUBTITLE"\ | |
-p 4 \ | |
-b $(tput kf1)'|FnKey_cb|F1' \ | |
-b $(tput kf2)'|FnKey_cb|F2' \ | |
-b $(tput kf3)'|FnKey_cb|F3' \ | |
"${DATA_CHOICES[@]}"\ | |
'&Shout hurray!' 'E&xit' | |
# Handle known hotkeys first | |
case $HKSELKEY in | |
s|S) | |
1>&2 echo -en 'Hurray!\nPress return to continue ' | |
read discard | |
;; | |
x|X) break;; | |
$'\e') # User pressed escape | |
echo | |
break | |
;; | |
*) # Could be an automatically assigned hotkey | |
if (( !is_boundKey )); then | |
# If we get to here, then REPLY contains the zero based index of the item chosen by the user | |
1>&2 echo -en "Thank you for selecting '${DATA_CHOICES[$REPLY]}'.\nPress return to continue " | |
read | |
fi | |
;; | |
esac | |
is_boundKey=0 | |
done | |
Top comments (0)