Enable binding multiple commands to a key.
This enables useful combinations of commands like `Space -> queue; move down 1` using ';' as command separator. ';' can be escaped using ';;'.
This commit is contained in:
410
src/command.rs
410
src/command.rs
@@ -196,6 +196,8 @@ impl fmt::Display for Command {
|
|||||||
Command::Logout => "logout".to_string(),
|
Command::Logout => "logout".to_string(),
|
||||||
Command::ShowRecommendations(mode) => format!("similar {}", mode),
|
Command::ShowRecommendations(mode) => format!("similar {}", mode),
|
||||||
};
|
};
|
||||||
|
// escape the command separator
|
||||||
|
let repr = repr.replace(";", ";;");
|
||||||
write!(f, "{}", repr)
|
write!(f, "{}", repr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,206 +235,236 @@ fn handle_aliases(input: &str) -> &str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(input: &str) -> Option<Command> {
|
pub fn parse(input: &str) -> Option<Vec<Command>> {
|
||||||
let components: Vec<_> = input.trim().split(' ').collect();
|
let mut command_inputs = vec!["".to_string()];
|
||||||
|
let mut command_idx = 0;
|
||||||
let command = handle_aliases(components[0]);
|
enum ParseState {
|
||||||
let args = components[1..].to_vec();
|
Normal,
|
||||||
|
SeparatorEncountered,
|
||||||
match command {
|
}
|
||||||
"quit" => Some(Command::Quit),
|
let mut parse_state = ParseState::Normal;
|
||||||
"playpause" => Some(Command::TogglePlay),
|
for c in input.chars() {
|
||||||
"stop" => Some(Command::Stop),
|
let is_separator = c == ';';
|
||||||
"previous" => Some(Command::Previous),
|
match parse_state {
|
||||||
"next" => Some(Command::Next),
|
ParseState::Normal if is_separator => parse_state = ParseState::SeparatorEncountered,
|
||||||
"clear" => Some(Command::Clear),
|
ParseState::Normal => command_inputs[command_idx].push(c),
|
||||||
"playnext" => Some(Command::PlayNext),
|
// ";" is escaped using ";;", so if the previous char already was a ';' push a ';'.
|
||||||
"queue" => Some(Command::Queue),
|
ParseState::SeparatorEncountered if is_separator => {
|
||||||
"play" => Some(Command::Play),
|
command_inputs[command_idx].push(c);
|
||||||
"update" => Some(Command::UpdateLibrary),
|
parse_state = ParseState::Normal;
|
||||||
"delete" => Some(Command::Delete),
|
}
|
||||||
"back" => Some(Command::Back),
|
ParseState::SeparatorEncountered => {
|
||||||
"open" => args
|
command_idx += 1;
|
||||||
.get(0)
|
command_inputs.push(c.to_string());
|
||||||
.and_then(|target| match *target {
|
parse_state = ParseState::Normal;
|
||||||
"selected" => Some(TargetMode::Selected),
|
}
|
||||||
"current" => Some(TargetMode::Current),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.map(Command::Open),
|
|
||||||
"jump" => Some(Command::Jump(JumpMode::Query(args.join(" ")))),
|
|
||||||
"search" => Some(Command::Search(args.join(" "))),
|
|
||||||
"shift" => {
|
|
||||||
let amount = args.get(1).and_then(|amount| amount.parse().ok());
|
|
||||||
|
|
||||||
args.get(0)
|
|
||||||
.and_then(|direction| match *direction {
|
|
||||||
"up" => Some(ShiftMode::Up),
|
|
||||||
"down" => Some(ShiftMode::Down),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.map(|mode| Command::Shift(mode, amount))
|
|
||||||
}
|
}
|
||||||
"move" => {
|
}
|
||||||
let cmd: Option<Command> = {
|
|
||||||
args.get(0).and_then(|extreme| match *extreme {
|
let mut commands = vec![];
|
||||||
"top" => Some(Command::Move(MoveMode::Up, MoveAmount::Extreme)),
|
for command_input in command_inputs {
|
||||||
"bottom" => Some(Command::Move(MoveMode::Down, MoveAmount::Extreme)),
|
let components: Vec<_> = command_input.trim().split(' ').collect();
|
||||||
"leftmost" => Some(Command::Move(MoveMode::Left, MoveAmount::Extreme)),
|
|
||||||
"rightmost" => Some(Command::Move(MoveMode::Right, MoveAmount::Extreme)),
|
let command = handle_aliases(components[0]);
|
||||||
"playing" => Some(Command::Move(MoveMode::Playing, MoveAmount::default())),
|
let args = components[1..].to_vec();
|
||||||
|
|
||||||
|
let command = match command {
|
||||||
|
"quit" => Some(Command::Quit),
|
||||||
|
"playpause" => Some(Command::TogglePlay),
|
||||||
|
"stop" => Some(Command::Stop),
|
||||||
|
"previous" => Some(Command::Previous),
|
||||||
|
"next" => Some(Command::Next),
|
||||||
|
"clear" => Some(Command::Clear),
|
||||||
|
"playnext" => Some(Command::PlayNext),
|
||||||
|
"queue" => Some(Command::Queue),
|
||||||
|
"play" => Some(Command::Play),
|
||||||
|
"update" => Some(Command::UpdateLibrary),
|
||||||
|
"delete" => Some(Command::Delete),
|
||||||
|
"back" => Some(Command::Back),
|
||||||
|
"open" => args
|
||||||
|
.get(0)
|
||||||
|
.and_then(|target| match *target {
|
||||||
|
"selected" => Some(TargetMode::Selected),
|
||||||
|
"current" => Some(TargetMode::Current),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
};
|
.map(Command::Open),
|
||||||
|
"jump" => Some(Command::Jump(JumpMode::Query(args.join(" ")))),
|
||||||
cmd.or({
|
"search" => Some(Command::Search(args.join(" "))),
|
||||||
let amount = args
|
"shift" => {
|
||||||
.get(1)
|
let amount = args.get(1).and_then(|amount| amount.parse().ok());
|
||||||
.and_then(|amount| amount.parse().ok())
|
|
||||||
.map(MoveAmount::Integer)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
args.get(0)
|
args.get(0)
|
||||||
.and_then(|direction| match *direction {
|
.and_then(|direction| match *direction {
|
||||||
"up" => Some(MoveMode::Up),
|
"up" => Some(ShiftMode::Up),
|
||||||
"down" => Some(MoveMode::Down),
|
"down" => Some(ShiftMode::Down),
|
||||||
"left" => Some(MoveMode::Left),
|
|
||||||
"right" => Some(MoveMode::Right),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.map(|mode| Command::Move(mode, amount))
|
.map(|mode| Command::Shift(mode, amount))
|
||||||
})
|
|
||||||
}
|
|
||||||
"goto" => args
|
|
||||||
.get(0)
|
|
||||||
.and_then(|mode| match *mode {
|
|
||||||
"album" => Some(GotoMode::Album),
|
|
||||||
"artist" => Some(GotoMode::Artist),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.map(Command::Goto),
|
|
||||||
"share" => args
|
|
||||||
.get(0)
|
|
||||||
.and_then(|target| match *target {
|
|
||||||
"selected" => Some(TargetMode::Selected),
|
|
||||||
"current" => Some(TargetMode::Current),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.map(Command::Share),
|
|
||||||
"shuffle" => {
|
|
||||||
let shuffle = args.get(0).and_then(|mode| match *mode {
|
|
||||||
"on" => Some(true),
|
|
||||||
"off" => Some(false),
|
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(Command::Shuffle(shuffle))
|
|
||||||
}
|
|
||||||
"repeat" => {
|
|
||||||
let mode = args.get(0).and_then(|mode| match *mode {
|
|
||||||
"list" | "playlist" | "queue" => Some(RepeatSetting::RepeatPlaylist),
|
|
||||||
"track" | "once" => Some(RepeatSetting::RepeatTrack),
|
|
||||||
"none" | "off" => Some(RepeatSetting::None),
|
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(Command::Repeat(mode))
|
|
||||||
}
|
|
||||||
"seek" => args.get(0).and_then(|arg| match arg.chars().next() {
|
|
||||||
Some(x) if x == '-' || x == '+' => arg
|
|
||||||
.chars()
|
|
||||||
.skip(1)
|
|
||||||
.collect::<String>()
|
|
||||||
.parse::<i32>()
|
|
||||||
.ok()
|
|
||||||
.map(|amount| {
|
|
||||||
Command::Seek(SeekDirection::Relative(
|
|
||||||
amount
|
|
||||||
* match x {
|
|
||||||
'-' => -1,
|
|
||||||
_ => 1,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}),
|
|
||||||
_ => arg
|
|
||||||
.chars()
|
|
||||||
.collect::<String>()
|
|
||||||
.parse()
|
|
||||||
.ok()
|
|
||||||
.map(|amount| Command::Seek(SeekDirection::Absolute(amount))),
|
|
||||||
}),
|
|
||||||
"focus" => args
|
|
||||||
.get(0)
|
|
||||||
.map(|target| Command::Focus((*target).to_string())),
|
|
||||||
"save" => args
|
|
||||||
.get(0)
|
|
||||||
.map(|target| match *target {
|
|
||||||
"queue" => Command::SaveQueue,
|
|
||||||
_ => Command::Save,
|
|
||||||
})
|
|
||||||
.or(Some(Command::Save)),
|
|
||||||
"volup" => Some(Command::VolumeUp(
|
|
||||||
args.get(0).and_then(|v| v.parse::<u16>().ok()).unwrap_or(1),
|
|
||||||
)),
|
|
||||||
"voldown" => Some(Command::VolumeDown(
|
|
||||||
args.get(0).and_then(|v| v.parse::<u16>().ok()).unwrap_or(1),
|
|
||||||
)),
|
|
||||||
"help" => Some(Command::Help),
|
|
||||||
"reload" => Some(Command::ReloadConfig),
|
|
||||||
"insert" => {
|
|
||||||
if args.is_empty() {
|
|
||||||
Some(Command::Insert(None))
|
|
||||||
} else {
|
|
||||||
args.get(0)
|
|
||||||
.map(|url| Command::Insert(Some((*url).to_string())))
|
|
||||||
}
|
}
|
||||||
}
|
"move" => {
|
||||||
"newplaylist" => {
|
let cmd: Option<Command> = {
|
||||||
if !args.is_empty() {
|
args.get(0).and_then(|extreme| match *extreme {
|
||||||
Some(Command::NewPlaylist(args.join(" ")))
|
"top" => Some(Command::Move(MoveMode::Up, MoveAmount::Extreme)),
|
||||||
} else {
|
"bottom" => Some(Command::Move(MoveMode::Down, MoveAmount::Extreme)),
|
||||||
None
|
"leftmost" => Some(Command::Move(MoveMode::Left, MoveAmount::Extreme)),
|
||||||
}
|
"rightmost" => Some(Command::Move(MoveMode::Right, MoveAmount::Extreme)),
|
||||||
}
|
"playing" => Some(Command::Move(MoveMode::Playing, MoveAmount::default())),
|
||||||
"sort" => {
|
_ => None,
|
||||||
if !args.is_empty() {
|
|
||||||
let sort_key = args.get(0).and_then(|key| match *key {
|
|
||||||
"title" => Some(SortKey::Title),
|
|
||||||
"duration" => Some(SortKey::Duration),
|
|
||||||
"album" => Some(SortKey::Album),
|
|
||||||
"added" => Some(SortKey::Added),
|
|
||||||
"artist" => Some(SortKey::Artist),
|
|
||||||
_ => None,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let sort_direction = args
|
|
||||||
.get(1)
|
|
||||||
.map(|direction| match *direction {
|
|
||||||
"a" => SortDirection::Ascending,
|
|
||||||
"asc" => SortDirection::Ascending,
|
|
||||||
"ascending" => SortDirection::Ascending,
|
|
||||||
"d" => SortDirection::Descending,
|
|
||||||
"desc" => SortDirection::Descending,
|
|
||||||
"descending" => SortDirection::Descending,
|
|
||||||
_ => SortDirection::Ascending,
|
|
||||||
})
|
})
|
||||||
.unwrap_or(SortDirection::Ascending);
|
};
|
||||||
|
|
||||||
Some(Command::Sort(sort_key, sort_direction))
|
cmd.or({
|
||||||
} else {
|
let amount = args
|
||||||
None
|
.get(1)
|
||||||
|
.and_then(|amount| amount.parse().ok())
|
||||||
|
.map(MoveAmount::Integer)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
args.get(0)
|
||||||
|
.and_then(|direction| match *direction {
|
||||||
|
"up" => Some(MoveMode::Up),
|
||||||
|
"down" => Some(MoveMode::Down),
|
||||||
|
"left" => Some(MoveMode::Left),
|
||||||
|
"right" => Some(MoveMode::Right),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.map(|mode| Command::Move(mode, amount))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
"goto" => args
|
||||||
"logout" => Some(Command::Logout),
|
.get(0)
|
||||||
"similar" => args
|
.and_then(|mode| match *mode {
|
||||||
.get(0)
|
"album" => Some(GotoMode::Album),
|
||||||
.and_then(|target| match *target {
|
"artist" => Some(GotoMode::Artist),
|
||||||
"selected" => Some(TargetMode::Selected),
|
_ => None,
|
||||||
"current" => Some(TargetMode::Current),
|
})
|
||||||
_ => None,
|
.map(Command::Goto),
|
||||||
})
|
"share" => args
|
||||||
.map(Command::ShowRecommendations),
|
.get(0)
|
||||||
"noop" => Some(Command::Noop),
|
.and_then(|target| match *target {
|
||||||
_ => None,
|
"selected" => Some(TargetMode::Selected),
|
||||||
|
"current" => Some(TargetMode::Current),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.map(Command::Share),
|
||||||
|
"shuffle" => {
|
||||||
|
let shuffle = args.get(0).and_then(|mode| match *mode {
|
||||||
|
"on" => Some(true),
|
||||||
|
"off" => Some(false),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(Command::Shuffle(shuffle))
|
||||||
|
}
|
||||||
|
"repeat" => {
|
||||||
|
let mode = args.get(0).and_then(|mode| match *mode {
|
||||||
|
"list" | "playlist" | "queue" => Some(RepeatSetting::RepeatPlaylist),
|
||||||
|
"track" | "once" => Some(RepeatSetting::RepeatTrack),
|
||||||
|
"none" | "off" => Some(RepeatSetting::None),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(Command::Repeat(mode))
|
||||||
|
}
|
||||||
|
"seek" => args.get(0).and_then(|arg| match arg.chars().next() {
|
||||||
|
Some(x) if x == '-' || x == '+' => arg
|
||||||
|
.chars()
|
||||||
|
.skip(1)
|
||||||
|
.collect::<String>()
|
||||||
|
.parse::<i32>()
|
||||||
|
.ok()
|
||||||
|
.map(|amount| {
|
||||||
|
Command::Seek(SeekDirection::Relative(
|
||||||
|
amount
|
||||||
|
* match x {
|
||||||
|
'-' => -1,
|
||||||
|
_ => 1,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
_ => arg
|
||||||
|
.chars()
|
||||||
|
.collect::<String>()
|
||||||
|
.parse()
|
||||||
|
.ok()
|
||||||
|
.map(|amount| Command::Seek(SeekDirection::Absolute(amount))),
|
||||||
|
}),
|
||||||
|
"focus" => args
|
||||||
|
.get(0)
|
||||||
|
.map(|target| Command::Focus((*target).to_string())),
|
||||||
|
"save" => args
|
||||||
|
.get(0)
|
||||||
|
.map(|target| match *target {
|
||||||
|
"queue" => Command::SaveQueue,
|
||||||
|
_ => Command::Save,
|
||||||
|
})
|
||||||
|
.or(Some(Command::Save)),
|
||||||
|
"volup" => Some(Command::VolumeUp(
|
||||||
|
args.get(0).and_then(|v| v.parse::<u16>().ok()).unwrap_or(1),
|
||||||
|
)),
|
||||||
|
"voldown" => Some(Command::VolumeDown(
|
||||||
|
args.get(0).and_then(|v| v.parse::<u16>().ok()).unwrap_or(1),
|
||||||
|
)),
|
||||||
|
"help" => Some(Command::Help),
|
||||||
|
"reload" => Some(Command::ReloadConfig),
|
||||||
|
"insert" => {
|
||||||
|
if args.is_empty() {
|
||||||
|
Some(Command::Insert(None))
|
||||||
|
} else {
|
||||||
|
args.get(0)
|
||||||
|
.map(|url| Command::Insert(Some((*url).to_string())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"newplaylist" => {
|
||||||
|
if !args.is_empty() {
|
||||||
|
Some(Command::NewPlaylist(args.join(" ")))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"sort" => {
|
||||||
|
if !args.is_empty() {
|
||||||
|
let sort_key = args.get(0).and_then(|key| match *key {
|
||||||
|
"title" => Some(SortKey::Title),
|
||||||
|
"duration" => Some(SortKey::Duration),
|
||||||
|
"album" => Some(SortKey::Album),
|
||||||
|
"added" => Some(SortKey::Added),
|
||||||
|
"artist" => Some(SortKey::Artist),
|
||||||
|
_ => None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let sort_direction = args
|
||||||
|
.get(1)
|
||||||
|
.map(|direction| match *direction {
|
||||||
|
"a" => SortDirection::Ascending,
|
||||||
|
"asc" => SortDirection::Ascending,
|
||||||
|
"ascending" => SortDirection::Ascending,
|
||||||
|
"d" => SortDirection::Descending,
|
||||||
|
"desc" => SortDirection::Descending,
|
||||||
|
"descending" => SortDirection::Descending,
|
||||||
|
_ => SortDirection::Ascending,
|
||||||
|
})
|
||||||
|
.unwrap_or(SortDirection::Ascending);
|
||||||
|
|
||||||
|
Some(Command::Sort(sort_key, sort_direction))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"logout" => Some(Command::Logout),
|
||||||
|
"similar" => args
|
||||||
|
.get(0)
|
||||||
|
.and_then(|target| match *target {
|
||||||
|
"selected" => Some(TargetMode::Selected),
|
||||||
|
"current" => Some(TargetMode::Current),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.map(Command::ShowRecommendations),
|
||||||
|
"noop" => Some(Command::Noop),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
commands.push(command?);
|
||||||
}
|
}
|
||||||
|
Some(commands)
|
||||||
}
|
}
|
||||||
|
|||||||
167
src/commands.rs
167
src/commands.rs
@@ -33,7 +33,7 @@ pub enum CommandResult {
|
|||||||
|
|
||||||
pub struct CommandManager {
|
pub struct CommandManager {
|
||||||
aliases: HashMap<String, String>,
|
aliases: HashMap<String, String>,
|
||||||
bindings: RefCell<HashMap<String, Command>>,
|
bindings: RefCell<HashMap<String, Vec<Command>>>,
|
||||||
spotify: Spotify,
|
spotify: Spotify,
|
||||||
queue: Arc<Queue>,
|
queue: Arc<Queue>,
|
||||||
library: Arc<Library>,
|
library: Arc<Library>,
|
||||||
@@ -61,7 +61,7 @@ impl CommandManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bindings(config: Arc<Config>) -> HashMap<String, Command> {
|
pub fn get_bindings(config: Arc<Config>) -> HashMap<String, Vec<Command>> {
|
||||||
let config = config.values();
|
let config = config.values();
|
||||||
let mut kb = if config.default_keybindings.unwrap_or(true) {
|
let mut kb = if config.default_keybindings.unwrap_or(true) {
|
||||||
Self::default_keybindings()
|
Self::default_keybindings()
|
||||||
@@ -70,12 +70,12 @@ impl CommandManager {
|
|||||||
};
|
};
|
||||||
let custom_bindings: Option<HashMap<String, String>> = config.keybindings.clone();
|
let custom_bindings: Option<HashMap<String, String>> = config.keybindings.clone();
|
||||||
|
|
||||||
for (key, command) in custom_bindings.unwrap_or_default() {
|
for (key, commands) in custom_bindings.unwrap_or_default() {
|
||||||
if let Some(command) = parse(&command) {
|
if let Some(commands) = parse(&commands) {
|
||||||
info!("Custom keybinding: {} -> {:?}", key, command);
|
info!("Custom keybinding: {} -> {:?}", key, commands);
|
||||||
kb.insert(key, command);
|
kb.insert(key, commands);
|
||||||
} else {
|
} else {
|
||||||
error!("Invalid command for key {}: {}", key, command);
|
error!("Invalid command(s) for key {}: {}", key, commands);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,11 +309,13 @@ impl CommandManager {
|
|||||||
&self,
|
&self,
|
||||||
cursive: &mut Cursive,
|
cursive: &mut Cursive,
|
||||||
event: E,
|
event: E,
|
||||||
command: Command,
|
commands: Vec<Command>,
|
||||||
) {
|
) {
|
||||||
cursive.add_global_callback(event, move |s| {
|
cursive.add_global_callback(event, move |s| {
|
||||||
if let Some(data) = s.user_data::<UserData>().cloned() {
|
if let Some(data) = s.user_data::<UserData>().cloned() {
|
||||||
data.cmd.handle(s, command.clone());
|
for command in commands.clone().into_iter() {
|
||||||
|
data.cmd.handle(s, command);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -340,133 +342,160 @@ impl CommandManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_keybindings() -> HashMap<String, Command> {
|
fn default_keybindings() -> HashMap<String, Vec<Command>> {
|
||||||
let mut kb = HashMap::new();
|
let mut kb = HashMap::new();
|
||||||
|
|
||||||
kb.insert("q".into(), Command::Quit);
|
kb.insert("q".into(), vec![Command::Quit]);
|
||||||
kb.insert("Shift+p".into(), Command::TogglePlay);
|
kb.insert("Shift+p".into(), vec![Command::TogglePlay]);
|
||||||
kb.insert("Shift+u".into(), Command::UpdateLibrary);
|
kb.insert("Shift+u".into(), vec![Command::UpdateLibrary]);
|
||||||
kb.insert("Shift+s".into(), Command::Stop);
|
kb.insert("Shift+s".into(), vec![Command::Stop]);
|
||||||
kb.insert("<".into(), Command::Previous);
|
kb.insert("<".into(), vec![Command::Previous]);
|
||||||
kb.insert(">".into(), Command::Next);
|
kb.insert(">".into(), vec![Command::Next]);
|
||||||
kb.insert("c".into(), Command::Clear);
|
kb.insert("c".into(), vec![Command::Clear]);
|
||||||
kb.insert("Space".into(), Command::Queue);
|
kb.insert(
|
||||||
kb.insert(".".into(), Command::PlayNext);
|
"Space".into(),
|
||||||
kb.insert("Enter".into(), Command::Play);
|
vec![
|
||||||
kb.insert("n".into(), Command::Jump(JumpMode::Next));
|
Command::Queue,
|
||||||
kb.insert("Shift+n".into(), Command::Jump(JumpMode::Previous));
|
Command::Move(MoveMode::Down, Default::default()),
|
||||||
kb.insert("s".into(), Command::Save);
|
],
|
||||||
kb.insert("Ctrl+s".into(), Command::SaveQueue);
|
);
|
||||||
kb.insert("d".into(), Command::Delete);
|
kb.insert(
|
||||||
kb.insert("f".into(), Command::Seek(SeekDirection::Relative(1000)));
|
".".into(),
|
||||||
kb.insert("b".into(), Command::Seek(SeekDirection::Relative(-1000)));
|
vec![
|
||||||
|
Command::PlayNext,
|
||||||
|
Command::Move(MoveMode::Down, Default::default()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
kb.insert("Enter".into(), vec![Command::Play]);
|
||||||
|
kb.insert("n".into(), vec![Command::Jump(JumpMode::Next)]);
|
||||||
|
kb.insert("Shift+n".into(), vec![Command::Jump(JumpMode::Previous)]);
|
||||||
|
kb.insert("s".into(), vec![Command::Save]);
|
||||||
|
kb.insert("Ctrl+s".into(), vec![Command::SaveQueue]);
|
||||||
|
kb.insert("d".into(), vec![Command::Delete]);
|
||||||
|
kb.insert(
|
||||||
|
"f".into(),
|
||||||
|
vec![Command::Seek(SeekDirection::Relative(1000))],
|
||||||
|
);
|
||||||
|
kb.insert(
|
||||||
|
"b".into(),
|
||||||
|
vec![Command::Seek(SeekDirection::Relative(-1000))],
|
||||||
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"Shift+f".into(),
|
"Shift+f".into(),
|
||||||
Command::Seek(SeekDirection::Relative(10000)),
|
vec![Command::Seek(SeekDirection::Relative(10000))],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"Shift+b".into(),
|
"Shift+b".into(),
|
||||||
Command::Seek(SeekDirection::Relative(-10000)),
|
vec![Command::Seek(SeekDirection::Relative(-10000))],
|
||||||
);
|
);
|
||||||
kb.insert("+".into(), Command::VolumeUp(1));
|
kb.insert("+".into(), vec![Command::VolumeUp(1)]);
|
||||||
kb.insert("]".into(), Command::VolumeUp(5));
|
kb.insert("]".into(), vec![Command::VolumeUp(5)]);
|
||||||
kb.insert("-".into(), Command::VolumeDown(1));
|
kb.insert("-".into(), vec![Command::VolumeDown(1)]);
|
||||||
kb.insert("[".into(), Command::VolumeDown(5));
|
kb.insert("[".into(), vec![Command::VolumeDown(5)]);
|
||||||
|
|
||||||
kb.insert("r".into(), Command::Repeat(None));
|
kb.insert("r".into(), vec![Command::Repeat(None)]);
|
||||||
kb.insert("z".into(), Command::Shuffle(None));
|
kb.insert("z".into(), vec![Command::Shuffle(None)]);
|
||||||
kb.insert("x".into(), Command::Share(TargetMode::Selected));
|
kb.insert("x".into(), vec![Command::Share(TargetMode::Selected)]);
|
||||||
kb.insert("Shift+x".into(), Command::Share(TargetMode::Current));
|
kb.insert("Shift+x".into(), vec![Command::Share(TargetMode::Current)]);
|
||||||
|
|
||||||
kb.insert("F1".into(), Command::Focus("queue".into()));
|
kb.insert("F1".into(), vec![Command::Focus("queue".into())]);
|
||||||
kb.insert("F2".into(), Command::Focus("search".into()));
|
kb.insert("F2".into(), vec![Command::Focus("search".into())]);
|
||||||
kb.insert("F3".into(), Command::Focus("library".into()));
|
kb.insert("F3".into(), vec![Command::Focus("library".into())]);
|
||||||
#[cfg(feature = "cover")]
|
#[cfg(feature = "cover")]
|
||||||
kb.insert("F8".into(), Command::Focus("cover".into()));
|
kb.insert("F8".into(), vec![Command::Focus("cover".into())]);
|
||||||
kb.insert("?".into(), Command::Help);
|
kb.insert("?".into(), vec![Command::Help]);
|
||||||
kb.insert("Backspace".into(), Command::Back);
|
kb.insert("Backspace".into(), vec![Command::Back]);
|
||||||
|
|
||||||
kb.insert("o".into(), Command::Open(TargetMode::Selected));
|
kb.insert("o".into(), vec![Command::Open(TargetMode::Selected)]);
|
||||||
kb.insert("Shift+o".into(), Command::Open(TargetMode::Current));
|
kb.insert("Shift+o".into(), vec![Command::Open(TargetMode::Current)]);
|
||||||
kb.insert("a".into(), Command::Goto(GotoMode::Album));
|
kb.insert("a".into(), vec![Command::Goto(GotoMode::Album)]);
|
||||||
kb.insert("A".into(), Command::Goto(GotoMode::Artist));
|
kb.insert("A".into(), vec![Command::Goto(GotoMode::Artist)]);
|
||||||
|
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"m".into(),
|
"m".into(),
|
||||||
Command::ShowRecommendations(TargetMode::Selected),
|
vec![Command::ShowRecommendations(TargetMode::Selected)],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"M".into(),
|
"M".into(),
|
||||||
Command::ShowRecommendations(TargetMode::Current),
|
vec![Command::ShowRecommendations(TargetMode::Current)],
|
||||||
);
|
);
|
||||||
|
|
||||||
kb.insert("Up".into(), Command::Move(MoveMode::Up, Default::default()));
|
kb.insert(
|
||||||
|
"Up".into(),
|
||||||
|
vec![Command::Move(MoveMode::Up, Default::default())],
|
||||||
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"p".into(),
|
"p".into(),
|
||||||
Command::Move(MoveMode::Playing, Default::default()),
|
vec![Command::Move(MoveMode::Playing, Default::default())],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"Down".into(),
|
"Down".into(),
|
||||||
Command::Move(MoveMode::Down, Default::default()),
|
vec![Command::Move(MoveMode::Down, Default::default())],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"Left".into(),
|
"Left".into(),
|
||||||
Command::Move(MoveMode::Left, Default::default()),
|
vec![Command::Move(MoveMode::Left, Default::default())],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"Right".into(),
|
"Right".into(),
|
||||||
Command::Move(MoveMode::Right, Default::default()),
|
vec![Command::Move(MoveMode::Right, Default::default())],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"PageUp".into(),
|
"PageUp".into(),
|
||||||
Command::Move(MoveMode::Up, MoveAmount::Integer(5)),
|
vec![Command::Move(MoveMode::Up, MoveAmount::Integer(5))],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"PageDown".into(),
|
"PageDown".into(),
|
||||||
Command::Move(MoveMode::Down, MoveAmount::Integer(5)),
|
vec![Command::Move(MoveMode::Down, MoveAmount::Integer(5))],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"Home".into(),
|
"Home".into(),
|
||||||
Command::Move(MoveMode::Up, MoveAmount::Extreme),
|
vec![Command::Move(MoveMode::Up, MoveAmount::Extreme)],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"End".into(),
|
"End".into(),
|
||||||
Command::Move(MoveMode::Down, MoveAmount::Extreme),
|
vec![Command::Move(MoveMode::Down, MoveAmount::Extreme)],
|
||||||
|
);
|
||||||
|
kb.insert(
|
||||||
|
"k".into(),
|
||||||
|
vec![Command::Move(MoveMode::Up, Default::default())],
|
||||||
);
|
);
|
||||||
kb.insert("k".into(), Command::Move(MoveMode::Up, Default::default()));
|
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"j".into(),
|
"j".into(),
|
||||||
Command::Move(MoveMode::Down, Default::default()),
|
vec![Command::Move(MoveMode::Down, Default::default())],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"h".into(),
|
"h".into(),
|
||||||
Command::Move(MoveMode::Left, Default::default()),
|
vec![Command::Move(MoveMode::Left, Default::default())],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"l".into(),
|
"l".into(),
|
||||||
Command::Move(MoveMode::Right, Default::default()),
|
vec![Command::Move(MoveMode::Right, Default::default())],
|
||||||
);
|
);
|
||||||
|
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"Ctrl+p".into(),
|
"Ctrl+p".into(),
|
||||||
Command::Move(MoveMode::Up, Default::default()),
|
vec![Command::Move(MoveMode::Up, Default::default())],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"Ctrl+n".into(),
|
"Ctrl+n".into(),
|
||||||
Command::Move(MoveMode::Down, Default::default()),
|
vec![Command::Move(MoveMode::Down, Default::default())],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"Ctrl+a".into(),
|
"Ctrl+a".into(),
|
||||||
Command::Move(MoveMode::Left, Default::default()),
|
vec![Command::Move(MoveMode::Left, Default::default())],
|
||||||
);
|
);
|
||||||
kb.insert(
|
kb.insert(
|
||||||
"Ctrl+e".into(),
|
"Ctrl+e".into(),
|
||||||
Command::Move(MoveMode::Right, Default::default()),
|
vec![Command::Move(MoveMode::Right, Default::default())],
|
||||||
);
|
);
|
||||||
|
|
||||||
kb.insert("Shift+Up".into(), Command::Shift(ShiftMode::Up, None));
|
kb.insert("Shift+Up".into(), vec![Command::Shift(ShiftMode::Up, None)]);
|
||||||
kb.insert("Shift+Down".into(), Command::Shift(ShiftMode::Down, None));
|
kb.insert(
|
||||||
kb.insert("Ctrl+v".into(), Command::Insert(None));
|
"Shift+Down".into(),
|
||||||
|
vec![Command::Shift(ShiftMode::Down, None)],
|
||||||
|
);
|
||||||
|
kb.insert("Ctrl+v".into(), vec![Command::Insert(None)]);
|
||||||
|
|
||||||
kb
|
kb
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,13 +288,15 @@ async fn main() -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let parsed = command::parse(cmd_without_prefix);
|
let parsed = command::parse(cmd_without_prefix);
|
||||||
if let Some(parsed) = parsed {
|
if let Some(commands) = parsed {
|
||||||
if let Some(data) = s.user_data::<UserData>().cloned() {
|
if let Some(data) = s.user_data::<UserData>().cloned() {
|
||||||
data.cmd.handle(s, parsed)
|
for cmd in commands {
|
||||||
|
data.cmd.handle(s, cmd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut main = s.find_name::<ui::layout::Layout>("main").unwrap();
|
let mut main = s.find_name::<ui::layout::Layout>("main").unwrap();
|
||||||
let err_msg = format!("Unknown command: \"{}\"", cmd_without_prefix);
|
let err_msg = format!("Failed to parse command(s): \"{}\"", cmd_without_prefix);
|
||||||
main.set_result(Err(err_msg));
|
main.set_result(Err(err_msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub struct HelpView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HelpView {
|
impl HelpView {
|
||||||
pub fn new(bindings: HashMap<String, Command>) -> HelpView {
|
pub fn new(bindings: HashMap<String, Vec<Command>>) -> HelpView {
|
||||||
let mut text = StyledString::styled("Keybindings\n\n", Effect::Bold);
|
let mut text = StyledString::styled("Keybindings\n\n", Effect::Bold);
|
||||||
|
|
||||||
let note = format!(
|
let note = format!(
|
||||||
@@ -30,8 +30,16 @@ impl HelpView {
|
|||||||
keys.sort();
|
keys.sort();
|
||||||
|
|
||||||
for key in keys {
|
for key in keys {
|
||||||
let command = &bindings[key];
|
let commands = &bindings[key];
|
||||||
let binding = format!("{} -> {}\n", key, command);
|
let binding = format!(
|
||||||
|
"{} -> {}\n",
|
||||||
|
key,
|
||||||
|
commands
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("; ")
|
||||||
|
);
|
||||||
text.append(binding);
|
text.append(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user