feat: command history navigation with up/down arrow keys

CommandLine now records every submitted command in a recall list (up to
50 entries).  Pressing ↑ in the command input walks back through the
list, ↓ moves forward, and returning to the newest position restores
the draft text the user had typed before navigating.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-04-07 00:04:12 +03:00
commit 80ca095121
4 changed files with 76 additions and 1 deletions

View file

@ -176,6 +176,10 @@ pub enum Message {
CommandInput(String),
CommandSubmit,
Command(String),
/// Recall previous command in history (↑ arrow key).
CommandHistoryPrev,
/// Recall next command in history (↓ arrow key).
CommandHistoryNext,
ToggleLayers,
LayerToggleVisible(usize),
LayerToggleLock(usize),

View file

@ -248,7 +248,21 @@ impl H7CAD {
Task::none()
}
Message::CommandInput(s) => { self.command_line.input = s; Task::none() }
Message::CommandInput(s) => {
// Any manual typing resets the history recall cursor.
self.command_line.input = s;
Task::none()
}
Message::CommandHistoryPrev => {
self.command_line.history_prev();
Task::none()
}
Message::CommandHistoryNext => {
self.command_line.history_next();
Task::none()
}
Message::CommandSubmit => {
let i = self.active_tab;

View file

@ -320,6 +320,12 @@ impl H7CAD {
{
Some(Message::DeleteSelected)
}
keyboard::Key::Named(keyboard::key::Named::ArrowUp) => {
Some(Message::CommandHistoryPrev)
}
keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
Some(Message::CommandHistoryNext)
}
keyboard::Key::Named(keyboard::key::Named::F3) => {
Some(Message::ToggleSnapEnabled)
}

View file

@ -16,6 +16,12 @@ const MAX_HISTORY: usize = 64;
pub struct CommandLine {
pub input: String,
pub history: Vec<HistoryEntry>,
/// Commands the user has typed (for ↑/↓ recall).
cmd_recall: Vec<String>,
/// Current position in `cmd_recall` while navigating (None = not navigating).
recall_cursor: Option<usize>,
/// Saved draft input before the user started navigating history.
recall_draft: String,
}
#[derive(Clone, Debug)]
@ -39,15 +45,60 @@ impl CommandLine {
cl.push_info("Type a command or use the ribbon. Open OBJ: INSERT tab.");
cl
}
pub fn submit(&mut self) -> Option<String> {
let cmd = self.input.trim().to_uppercase();
if cmd.is_empty() {
return None;
}
// Record in recall list (avoid duplicates at the top).
let raw = self.input.trim().to_string();
if self.cmd_recall.last().map(|s| s.as_str()) != Some(&raw) {
self.cmd_recall.push(raw);
if self.cmd_recall.len() > 50 {
self.cmd_recall.remove(0);
}
}
self.recall_cursor = None;
self.recall_draft.clear();
self.push_command(&self.input.clone());
self.input.clear();
Some(cmd)
}
/// Navigate to the previous command in recall history (↑).
pub fn history_prev(&mut self) {
if self.cmd_recall.is_empty() {
return;
}
let cursor = match self.recall_cursor {
None => {
self.recall_draft = self.input.clone();
self.cmd_recall.len() - 1
}
Some(c) if c > 0 => c - 1,
Some(c) => c,
};
self.recall_cursor = Some(cursor);
self.input = self.cmd_recall[cursor].clone();
}
/// Navigate to the next command in recall history (↓).
pub fn history_next(&mut self) {
match self.recall_cursor {
None => {}
Some(c) if c + 1 < self.cmd_recall.len() => {
let next = c + 1;
self.recall_cursor = Some(next);
self.input = self.cmd_recall[next].clone();
}
Some(_) => {
self.recall_cursor = None;
self.input = self.recall_draft.clone();
}
}
}
pub fn push_command(&mut self, cmd: &str) {
self.push(EntryKind::Command, format!("Command: {cmd}"));
}