fix(menu): let the basic menu select entries numbered 10 and above

The basic menu listed entries as [10], [11] and so on, but only read a
single digit, so those entries could never be selected.

Keep reading digits while another digit could still select a different
entry, and boot once it can't or when enter is pressed. With ten
entries or fewer, a single digit boots the entry right away as before.
This commit is contained in:
alex authored and Alex Zenla committed 2026-09-26 20:52:15 -07:00
1 parent 9e595bf8d5
commit df0bda04c0
1 file changed
+71 -30
+71 -30
View File
@@ -14,11 +14,14 @@ pub struct BasicMenu;
/// Represents the operation that can be performed by the boot menu. /// Represents the operation that can be performed by the boot menu.
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
enum MenuOperation { enum MenuOperation {
/// The user selected a numbered entry. /// The user typed a digit of an entry number.
Number(usize), Digit(usize),
/// The user selected the escape key to exit the boot menu. /// The user selected the escape key to exit the boot menu.
Exit, Exit,
/// The user selected the enter key to display the entries again. /// The user selected the enter key to boot the typed entry number,
/// or to display the entries again if no number was typed.
Enter,
/// The user selected some other key to display the entries again.
Continue, Continue,
/// Timeout occurred. /// Timeout occurred.
Timeout, Timeout,
@@ -42,11 +45,15 @@ fn read(input: &mut Input, timeout: Option<Duration>) -> Result<MenuOperation> {
} }
// Convert the key to a char. // Convert the key to a char.
let c: char = c.into(); let c: char = c.into();
// Serial consoles may send a line feed instead of a carriage return.
if matches!(c, '\r' | '\n') {
return Ok(MenuOperation::Enter);
}
// Find the key pressed in the entry number table or continue. // Find the key pressed in the entry number table or continue.
Ok(ENTRY_NUMBER_TABLE Ok(ENTRY_NUMBER_TABLE
.iter() .iter()
.position(|&x| x == c) .position(|&x| x == c)
.map(MenuOperation::Number) .map(MenuOperation::Digit)
.unwrap_or(MenuOperation::Continue)) .unwrap_or(MenuOperation::Continue))
} }
@@ -80,35 +87,65 @@ fn select_with_input<'a>(
// The time to wait for a key, or None once a key stops the countdown. // The time to wait for a key, or None once a key stops the countdown.
let mut countdown = Some(timeout); let mut countdown = Some(timeout);
// The entry number typed so far, while more digits could still select another entry.
let mut typed: Option<usize> = None;
// Whether to print the entries before reading the next key.
let mut display = true;
loop { loop {
// Print all the entries with the number used to select them. if display {
info!("Boot Menu:"); // Print all the entries with the number used to select them.
for (index, entry) in entries.iter().enumerate() { info!("Boot Menu:");
let title = entry.context().stamp(&entry.declaration().title); for (index, entry) in entries.iter().enumerate() {
info!(" [{}] {}", index, title); let title = entry.context().stamp(&entry.declaration().title);
info!(" [{}] {}", index, title);
}
info!("Select a boot entry using the number keys.");
if entries.len() > ENTRY_NUMBER_TABLE.len() {
info!("Press enter after the number if the entry does not boot right away.");
}
info!("Press Escape to exit and enter to display the entries again.");
display = false;
} }
// Read from input until a valid operation is selected. let operation = read(input, countdown)?;
let operation = loop {
info!("Select a boot entry using the number keys.");
info!("Press Escape to exit and enter to display the entries again.");
let operation = read(input, countdown)?; // Any key stops the countdown, so the menu waits for the user from now on.
if operation != MenuOperation::Timeout {
// Any key stops the countdown, so the menu waits for the user from now on. countdown = None;
if operation != MenuOperation::Timeout { }
countdown = None;
}
if operation != MenuOperation::Nop {
break operation;
}
};
match operation { match operation {
// Entry was selected by number. If the number is invalid, we continue. // A digit of the entry number was typed.
MenuOperation::Number(index) => { MenuOperation::Digit(digit) => {
let Some(entry) = entries.get(index) else { let number = typed
.take()
.unwrap_or(0)
.saturating_mul(10)
.saturating_add(digit);
// If another digit could select a different entry, wait for it or for enter.
if number != 0 && number.saturating_mul(10) < entries.len() {
typed = Some(number);
continue;
}
// Otherwise, boot the entry. If the number is invalid, we continue.
let Some(entry) = entries.get(number) else {
info!("invalid entry number");
continue;
};
return Ok(entry);
}
// Enter boots the typed entry number, or displays the entries again.
MenuOperation::Enter => {
let Some(number) = typed.take() else {
display = true;
continue;
};
let Some(entry) = entries.get(number) else {
info!("invalid entry number"); info!("invalid entry number");
continue; continue;
}; };
@@ -121,10 +158,14 @@ fn select_with_input<'a>(
return default_entry(entries); return default_entry(entries);
} }
// If the operation is to continue or nop, we can just run the loop again. // Any other key clears the typed number and displays the entries again.
MenuOperation::Continue | MenuOperation::Nop => { MenuOperation::Continue => {
continue; typed = None;
display = true;
} }
// If the operation is nop, there is nothing to do.
MenuOperation::Nop => {}
} }
} }
} }