added vim motions

This commit is contained in:
Uber Veng
2026-05-22 00:06:30 +07:00
parent 149246b000
commit 9600e49385
3 changed files with 74 additions and 9 deletions

2
Cargo.lock generated
View File

@@ -1123,7 +1123,7 @@ dependencies = [
[[package]]
name = "ostiary"
version = "1.0.1"
version = "1.0.2"
dependencies = [
"anyhow",
"crossterm",

View File

@@ -1,6 +1,6 @@
[package]
name = "ostiary"
version = "1.0.1"
version = "1.0.2"
edition = "2024"
[dependencies]

View File

@@ -297,6 +297,11 @@ impl App {
Some(executor::StructuredCommand::Confirm { .. })
);
let has_command = current_command.is_some();
// Vim motions are disabled only when free text input is active.
let is_text_input = matches!(
current_command,
Some(executor::StructuredCommand::Input { .. })
);
match key.code {
// ── Log scrolling (PageUp / PageDown always work) ─
@@ -394,7 +399,66 @@ impl App {
let _ = current_command.take();
let _ = reply_tx.send("n".to_string());
} else {
// Kill the script if still running.
if let Some(kx) = kill_tx.take() {
let _ = kx.send(());
}
self.popup = None;
}
}
// ── Vim motions (off during text input) ───────────
KeyCode::Char('k') if !is_text_input => {
if is_menu {
if *menu_selected_index > 0 {
*menu_selected_index -= 1;
}
} else if !has_command {
*log_scroll_pos = log_scroll_pos.saturating_sub(1);
*log_follow_bottom = false;
}
}
KeyCode::Char('j') if !is_text_input => {
if is_menu {
if let Some(executor::StructuredCommand::Menu {
options, ..
}) = current_command
{
if *menu_selected_index + 1 < options.len() {
*menu_selected_index += 1;
}
}
} else if !has_command {
*log_scroll_pos = log_scroll_pos.saturating_add(1);
*log_follow_bottom =
*log_scroll_pos + 1 >= log_buffer.len();
}
}
KeyCode::Char('l') if !is_text_input => {
// Forward / confirm — same logic as Enter.
if let Some(cmd) = current_command.take() {
let response = match &cmd {
executor::StructuredCommand::Confirm { .. } => {
"y".to_string()
}
executor::StructuredCommand::Menu {
options, ..
} => options
.get(*menu_selected_index)
.map(|o| o.id.clone())
.unwrap_or_default(),
_ => String::new(),
};
let _ = reply_tx.send(response);
input_buffer.clear();
*menu_selected_index = 0;
}
}
KeyCode::Char('h') if !is_text_input => {
// Back / cancel — same logic as Esc.
if is_confirm {
let _ = current_command.take();
let _ = reply_tx.send("n".to_string());
} else {
if let Some(kx) = kill_tx.take() {
let _ = kx.send(());
}
@@ -414,7 +478,7 @@ impl App {
Popup::UpdateConfirm { info } => {
match key.code {
KeyCode::Char('y') | KeyCode::Char('Y') => {
KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Char('l') => {
let info = info.clone();
self.popup = Some(Popup::Updating {
info: info.clone(),
@@ -453,7 +517,8 @@ impl App {
}
});
}
KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
KeyCode::Char('n') | KeyCode::Char('N')
| KeyCode::Esc | KeyCode::Char('h') => {
self.popup = None;
}
_ => {}
@@ -478,24 +543,24 @@ impl App {
// ── Main menu navigation ─────────────────────────────────────────
match key.code {
KeyCode::Char('q') => return Ok(true),
KeyCode::Up => {
KeyCode::Up | KeyCode::Char('k') => {
let len = self.current_items().len();
if len > 0 {
self.selected_index = (self.selected_index + len - 1) % len;
}
}
KeyCode::Down => {
KeyCode::Down | KeyCode::Char('j') => {
let len = self.current_items().len();
if len > 0 {
self.selected_index = (self.selected_index + 1) % len;
}
}
KeyCode::Enter => {
KeyCode::Enter | KeyCode::Char('l') => {
if let Some(item) = self.selected_item().cloned() {
self.activate_item(item).await?;
}
}
KeyCode::Esc => {
KeyCode::Esc | KeyCode::Char('h') => {
if !self.breadcrumbs.is_empty() {
self.breadcrumbs.pop();
self.selected_index = 0;