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:
parent
0868a1719c
commit
80ca095121
4 changed files with 76 additions and 1 deletions
|
|
@ -176,6 +176,10 @@ pub enum Message {
|
||||||
CommandInput(String),
|
CommandInput(String),
|
||||||
CommandSubmit,
|
CommandSubmit,
|
||||||
Command(String),
|
Command(String),
|
||||||
|
/// Recall previous command in history (↑ arrow key).
|
||||||
|
CommandHistoryPrev,
|
||||||
|
/// Recall next command in history (↓ arrow key).
|
||||||
|
CommandHistoryNext,
|
||||||
ToggleLayers,
|
ToggleLayers,
|
||||||
LayerToggleVisible(usize),
|
LayerToggleVisible(usize),
|
||||||
LayerToggleLock(usize),
|
LayerToggleLock(usize),
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,21 @@ impl H7CAD {
|
||||||
Task::none()
|
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 => {
|
Message::CommandSubmit => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
|
|
|
||||||
|
|
@ -320,6 +320,12 @@ impl H7CAD {
|
||||||
{
|
{
|
||||||
Some(Message::DeleteSelected)
|
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) => {
|
keyboard::Key::Named(keyboard::key::Named::F3) => {
|
||||||
Some(Message::ToggleSnapEnabled)
|
Some(Message::ToggleSnapEnabled)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,12 @@ const MAX_HISTORY: usize = 64;
|
||||||
pub struct CommandLine {
|
pub struct CommandLine {
|
||||||
pub input: String,
|
pub input: String,
|
||||||
pub history: Vec<HistoryEntry>,
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -39,15 +45,60 @@ impl CommandLine {
|
||||||
cl.push_info("Type a command or use the ribbon. Open OBJ: INSERT tab.");
|
cl.push_info("Type a command or use the ribbon. Open OBJ: INSERT tab.");
|
||||||
cl
|
cl
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn submit(&mut self) -> Option<String> {
|
pub fn submit(&mut self) -> Option<String> {
|
||||||
let cmd = self.input.trim().to_uppercase();
|
let cmd = self.input.trim().to_uppercase();
|
||||||
if cmd.is_empty() {
|
if cmd.is_empty() {
|
||||||
return None;
|
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.push_command(&self.input.clone());
|
||||||
self.input.clear();
|
self.input.clear();
|
||||||
Some(cmd)
|
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) {
|
pub fn push_command(&mut self, cmd: &str) {
|
||||||
self.push(EntryKind::Command, format!("Command: {cmd}"));
|
self.push(EntryKind::Command, format!("Command: {cmd}"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue