rustamp | shortcuts
One thing I don't like is the different shortcuts for each UI engine. Ughh!
It should be relative simple to change that with the architecture we designed.
Let us centralize that with input.rs:
use crate::engine::Command;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub enum UiAction {
CommandMode,
Open,
Add,
Quit,
}
#[derive(Debug, Clone)]
pub enum Action {
Engine(Command),
Ui(UiAction),
}
pub fn decode_key(ch: char) -> Option<Action> {
Some(match ch {
':' => Action::Ui(UiAction::CommandMode),
'q' | 'Q' => Action::Ui(UiAction::Quit),
'o' | 'O' => Action::Ui(UiAction::Open),
'a' | 'A' => Action::Ui(UiAction::Add),
'p' | 'P' => Action::Engine(Command::PlaySelected),
'i' | 'I' => Action::Engine(Command::Pause),
's' | 'S' => Action::Engine(Command::Stop),
't' | 'T' => Action::Engine(Command::TogglePlayPause),
'l' | 'L' => Action::Engine(Command::LoadSelected),
'd' | 'D' => Action::Engine(Command::RemoveSelected),
'j' => Action::Engine(Command::SelectDown),
'k' => Action::Engine(Command::SelectUp),
'g' => Action::Engine(Command::SelectTop),
'G' => Action::Engine(Command::SelectBottom),
'J' => Action::Engine(Command::MoveSelectedDown),
'K' => Action::Engine(Command::MoveSelectedUp),
'n' | 'N' => Action::Engine(Command::NextTrack),
'b' | 'B' => Action::Engine(Command::PrevTrack),
_ => return None,
})
}
pub fn parse_command_line(line: &str) -> Result<Vec<Command>, String> {
let line = line.trim().strip_prefix(':').unwrap_or(line).trim();
if line.is_empty() {
return Ok(vec![]);
}
let tokens = shell_split(line)?;
if tokens.is_empty() {
return Ok(vec![]);
}
let head = tokens[0].to_lowercase();
let cmd = match head.as_str() {
"open" | "o" => {
let p = tokens.get(1).ok_or_else(|| "open requires a path".to_string())?;
Command::FilePicked(Some(PathBuf::from(p)))
}
"add" | "a" => {
if tokens.len() < 2 {
return Err("add requires one or more paths".into());
}
let ps = tokens.iter().skip(1).map(|p| PathBuf::from(p)).collect();
return Ok(vec![Command::FilesPicked(ps)]);
}
"play" | "p" => Command::PlaySelected,
"pause" | "i" => Command::Pause,
"stop" | "s" => Command::Stop,
"toggle" | "t" => Command::TogglePlayPause,
"next" | "n" => Command::NextTrack,
"prev" | "b" => Command::PrevTrack,
"load" | "l" => Command::LoadSelected,
"rm" | "del" | "d" => Command::RemoveSelected,
_ => {
let joined = tokens.join(" ");
return Ok(vec![Command::FilePicked(Some(PathBuf::from(joined)))]);
}
};
Ok(vec![cmd])
}
fn shell_split(s: &str) -> Result<Vec<String>, String> {
let mut out: Vec<String> = vec![];
let mut cur = String::new();
let mut chars = s.chars().peekable();
let mut quote: Option<char> = None;
while let Some(ch) = chars.next() {
match (quote, ch) {
(_, '\\') => {
if let Some(next) = chars.next() {
cur.push(next);
}
}
(None, '\'' | '"') => quote = Some(ch),
(Some(q), c) if c == q => quote = None,
(None, c) if c.is_whitespace() => {
if !cur.is_empty() {
out.push(std::mem::take(&mut cur));
}
while matches!(chars.peek(), Some(p) if p.is_whitespace()) {
chars.next();
}
}
(_, c) => cur.push(c),
}
}
if quote.is_some() {
return Err("unterminated quote in command".into());
}
if !cur.is_empty() {
out.push(cur);
}
Ok(out)
}
pub fn help_line() -> &'static str {
"keys: o open a add : cmd p play i pause s stop n next b prev j/k move d del q quit"
}
Notice the take one shortcuts? It's quite similar approach to VIM, ain't no coincidence broddha. I'm up for simplicity, always, and VIM approach, I, for one, believe is the best approach.
rustamp-tui
Now, at the RatatUI part, we need to update our handle_norma_key:
fn handle_normal_key(engine: &mut Engine, state: &mut UiState, code: KeyCode, mods: KeyModifiers) -> bool {
if mods.contains(KeyModifiers::CONTROL) || mods.contains(KeyModifiers::ALT) {
return false;
}
let KeyCode::Char(ch) = code else { return false; };
let Some(action) = input::decode_key(ch) else { return false; };
match action {
Action::Ui(UiAction::Quit) => return true,
Action::Ui(UiAction::CommandMode) => {
state.mode = Mode::Command;
state.cmdline.clear();
}
Action::Ui(UiAction::Open) => {
state.mode = Mode::Command;
state.cmdline = "open ".into();
}
Action::Ui(UiAction::Add) => {
state.mode = Mode::Command;
state.cmdline = "add ".into();
}
Action::Engine(cmd) => step(engine, cmd),
}
false
}
and update our parser:
match input::parse_command_line(&state.cmdline) {
Ok(cmds) => for cmd in cmds { step(engine, cmd); },
Err(e) => engine.machine.error = Some(e),
}
and delete our now deprecated function parse_cmdline.
rustamp-iced
We only need to update our keyboard handler at app:
match input::decode_key(ch) {
Some(Action::Ui(UiAction::Open)) => return Task::done(Message::OpenFile),
Some(Action::Ui(UiAction::Add)) => return Task::done(Message::AddToPlaylist),
Some(Action::Ui(UiAction::CommandMode)) => Task::none(),
Some(Action::Ui(UiAction::Quit)) => Task::none(),
Some(Action::Engine(cmd)) => {
self.engine.apply(cmd);
Task::none()
}
None => Task::none(),
}