From 80ca095121d6c2eeb997f795aaf62acd25435ef5 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 7 Apr 2026 00:04:12 +0300 Subject: [PATCH] feat: command history navigation with up/down arrow keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/app/mod.rs | 4 ++++ src/app/update.rs | 16 ++++++++++++- src/app/view.rs | 6 +++++ src/ui/command_line.rs | 51 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index f750969c1..d7a3039f9 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -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), diff --git a/src/app/update.rs b/src/app/update.rs index b4207b30b..cc05c1a8a 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -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; diff --git a/src/app/view.rs b/src/app/view.rs index 8f73fa726..e2ef42a15 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -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) } diff --git a/src/ui/command_line.rs b/src/ui/command_line.rs index 258ddfb21..0b1634672 100644 --- a/src/ui/command_line.rs +++ b/src/ui/command_line.rs @@ -16,6 +16,12 @@ const MAX_HISTORY: usize = 64; pub struct CommandLine { pub input: String, pub history: Vec, + /// Commands the user has typed (for ↑/↓ recall). + cmd_recall: Vec, + /// Current position in `cmd_recall` while navigating (None = not navigating). + recall_cursor: Option, + /// 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 { 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}")); }