From 1671db14c15cc04ed93d020fe547f97c72f204ac Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Mon, 20 May 2019 22:15:12 +0200 Subject: [PATCH 1/6] Move to enum-based commands --- src/command.rs | 64 ++++++++++++++++ src/commands.rs | 126 +++++++++++++++--------------- src/config.rs | 3 +- src/main.rs | 4 + src/traits.rs | 18 +---- src/ui/album.rs | 10 +-- src/ui/artist.rs | 10 +-- src/ui/layout.rs | 37 ++++----- src/ui/library.rs | 10 +-- src/ui/listview.rs | 182 +++++++++++++++++++++----------------------- src/ui/playlist.rs | 10 +-- src/ui/playlists.rs | 21 +++-- src/ui/queue.rs | 83 ++++++++++---------- src/ui/search.rs | 40 +++++----- src/ui/tabview.rs | 42 +++++----- 15 files changed, 343 insertions(+), 317 deletions(-) create mode 100644 src/command.rs diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..aa507cf --- /dev/null +++ b/src/command.rs @@ -0,0 +1,64 @@ +#[derive(Clone, Serialize, Deserialize, Debug)] +pub enum PlaylistCommands { + Update, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub enum SeekInterval { + Forward, + Backwards, + Custom(usize), +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub enum TargetMode { + Current, + Selected, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub enum MoveMode { + Up, + Down, + Left, + Right, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub enum ShiftMode { + Up, + Down, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub enum GotoMode { + Album, + Artist, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub enum Command { + Quit, + TogglePlay, + Playlists(PlaylistCommands), + Stop, + Previous, + Next, + Clear, + Queue, + Play, + Save, + SaveQueue, + Delete, + Focus(String), + Seek(SeekInterval), + Repeat, + Shuffle, + Share(TargetMode), + Back, + Open, + Goto(GotoMode), + Move(MoveMode, Option), + Shift(ShiftMode, Option), + Search(String), +} diff --git a/src/commands.rs b/src/commands.rs index b0662b4..3b92d49 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -6,6 +6,16 @@ use cursive::event::{Event, Key}; use cursive::views::ViewRef; use cursive::Cursive; +use command::Command::{ + Back, Clear, Delete, Focus, Goto, Move, Next, Open, Play, Playlists, Previous, Quit, Repeat, + Save, SaveQueue, Seek, Share, Shift, Shuffle, Stop, TogglePlay, +}; +use command::GotoMode::{Album, Artist}; +use command::MoveMode::{Down, Left, Right, Up}; +use command::PlaylistCommands::Update; +use command::SeekInterval::{Backwards, Forward}; +use command::TargetMode::{Current, Selected}; +use command::{Command, ShiftMode}; use library::Library; use queue::{Queue, RepeatSetting}; use spotify::Spotify; @@ -227,15 +237,10 @@ impl CommandManager { } } - fn handle_callbacks( - &self, - s: &mut Cursive, - cmd: &str, - args: &[String], - ) -> Result, String> { + fn handle_callbacks(&self, s: &mut Cursive, cmd: &Command) -> Result, String> { let local = { let mut main: ViewRef = s.find_id("main").unwrap(); - main.on_command(s, cmd, args)? + main.on_command(s, cmd)? }; if let CommandResult::Consumed(output) = local { @@ -246,24 +251,18 @@ impl CommandManager { }); Ok(None) - } else if let Some(callback) = self.callbacks.get(cmd) { + } + /* handle default commands + else if let Some(callback) = self.callbacks.get(cmd) { callback.as_ref().map(|cb| cb(s, args)).unwrap_or(Ok(None)) - } else { + } */ + else { Err("Unknown command.".to_string()) } } - pub fn handle(&self, s: &mut Cursive, cmd: String) { - let components: Vec = cmd - .trim() - .split(' ') - .map(std::string::ToString::to_string) - .collect(); - - let cmd = self.handle_aliases(&components[0]); - let args = components[1..].to_vec(); - - let result = self.handle_callbacks(s, &cmd, &args); + pub fn handle(&self, s: &mut Cursive, cmd: Command) { + let result = self.handle_callbacks(s, &cmd); s.call_on_id("main", |v: &mut Layout| { v.set_result(result); @@ -272,22 +271,21 @@ impl CommandManager { s.on_event(Event::Refresh); } - pub fn register_keybinding, S: Into>( + pub fn register_keybinding>( this: Arc, cursive: &mut Cursive, event: E, - command: S, + command: Command, ) { - let cmd = command.into(); cursive.add_global_callback(event, move |s| { - this.handle(s, cmd.clone()); + this.handle(s, command.clone()); }); } pub fn register_keybindings( this: Arc, cursive: &mut Cursive, - keybindings: Option>, + keybindings: Option>, ) { let mut kb = Self::default_keybindings(); kb.extend(keybindings.unwrap_or_default()); @@ -301,51 +299,51 @@ impl CommandManager { } } - fn default_keybindings() -> HashMap { + fn default_keybindings() -> HashMap { let mut kb = HashMap::new(); - kb.insert("q".into(), "quit".into()); - kb.insert("P".into(), "toggleplay".into()); - kb.insert("R".into(), "playlists update".into()); - kb.insert("S".into(), "stop".into()); - kb.insert("<".into(), "previous".into()); - kb.insert(">".into(), "next".into()); - kb.insert("c".into(), "clear".into()); - kb.insert(" ".into(), "queue".into()); - kb.insert("Enter".into(), "play".into()); - kb.insert("s".into(), "save".into()); - kb.insert("Ctrl+s".into(), "save queue".into()); - kb.insert("d".into(), "delete".into()); - kb.insert("/".into(), "focus search".into()); - kb.insert(".".into(), "seek +500".into()); - kb.insert(",".into(), "seek -500".into()); - kb.insert("r".into(), "repeat".into()); - kb.insert("z".into(), "shuffle".into()); - kb.insert("x".into(), "share current".into()); - kb.insert("Shift+x".into(), "share selected".into()); + kb.insert("q".into(), Quit); + kb.insert("P".into(), TogglePlay); + kb.insert("R".into(), Playlists(Update)); + kb.insert("S".into(), Stop); + kb.insert("<".into(), Previous); + kb.insert(">".into(), Next); + kb.insert("c".into(), Clear); + kb.insert(" ".into(), Command::Queue); + kb.insert("Enter".into(), Play); + kb.insert("s".into(), Save); + kb.insert("Ctrl+s".into(), SaveQueue); + kb.insert("d".into(), Delete); + kb.insert("/".into(), Focus("search".into())); + kb.insert(".".into(), Seek(Forward)); + kb.insert(",".into(), Seek(Backwards)); + kb.insert("r".into(), Repeat); + kb.insert("z".into(), Shuffle); + kb.insert("x".into(), Share(Current)); + kb.insert("Shift+x".into(), Share(Selected)); - kb.insert("F1".into(), "focus queue".into()); - kb.insert("F2".into(), "focus search".into()); - kb.insert("F3".into(), "focus library".into()); - kb.insert("Backspace".into(), "back".into()); + kb.insert("F1".into(), Focus("queue".into())); + kb.insert("F2".into(), Focus("search".into())); + kb.insert("F3".into(), Focus("library".into())); + kb.insert("Backspace".into(), Back); - kb.insert("o".into(), "open".into()); - kb.insert("a".into(), "goto album".into()); - kb.insert("A".into(), "goto artist".into()); + kb.insert("o".into(), Open); + kb.insert("a".into(), Goto(Album)); + kb.insert("A".into(), Goto(Artist)); - kb.insert("Up".into(), "move up".into()); - kb.insert("Down".into(), "move down".into()); - kb.insert("Left".into(), "move left".into()); - kb.insert("Right".into(), "move right".into()); - kb.insert("PageUp".into(), "move up 5".into()); - kb.insert("PageDown".into(), "move down 5".into()); - kb.insert("k".into(), "move up".into()); - kb.insert("j".into(), "move down".into()); - kb.insert("h".into(), "move left".into()); - kb.insert("l".into(), "move right".into()); + kb.insert("Up".into(), Move(Up, None)); + kb.insert("Down".into(), Move(Down, None)); + kb.insert("Left".into(), Move(Left, None)); + kb.insert("Right".into(), Move(Right, None)); + kb.insert("PageUp".into(), Move(Up, Some(5))); + kb.insert("PageDown".into(), Move(Down, Some(5))); + kb.insert("k".into(), Move(Up, None)); + kb.insert("j".into(), Move(Down, None)); + kb.insert("h".into(), Move(Left, None)); + kb.insert("l".into(), Move(Right, None)); - kb.insert("Shift+Up".into(), "shift up".into()); - kb.insert("Shift+Down".into(), "shift down".into()); + kb.insert("Shift+Up".into(), Shift(ShiftMode::Up, None)); + kb.insert("Shift+Down".into(), Shift(ShiftMode::Down, None)); kb } diff --git a/src/config.rs b/src/config.rs index 977316b..4318853 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,13 +3,14 @@ use std::fs; use std::path::{Path, PathBuf}; use std::sync::RwLock; +use command::Command; use directories::ProjectDirs; pub const CLIENT_ID: &str = "d420a117a32841c2b3474932e49fb54b"; #[derive(Serialize, Deserialize, Debug, Default)] pub struct Config { - pub keybindings: Option>, + pub keybindings: Option>, pub theme: Option, pub use_nerdfont: Option, } diff --git a/src/main.rs b/src/main.rs index c78ea94..90cadb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,6 +46,7 @@ use librespot::core::authentication::Credentials; mod album; mod artist; mod authentication; +mod command; mod commands; mod config; mod events; @@ -232,6 +233,8 @@ fn main() { }); }); + /* + TODO: Write parser for commands { let ev = event_manager.clone(); let cmd_manager = cmd_manager.clone(); @@ -244,6 +247,7 @@ fn main() { ev.trigger(); }); } + */ cursive.add_fullscreen_layer(layout.with_id("main")); diff --git a/src/traits.rs b/src/traits.rs index aae2c70..7fde625 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -6,6 +6,7 @@ use cursive::Cursive; use album::Album; use artist::Artist; +use command::Command; use commands::CommandResult; use library::Library; use queue::Queue; @@ -34,25 +35,14 @@ pub trait ViewExt: View { "".into() } - fn on_command( - &mut self, - _s: &mut Cursive, - _cmd: &str, - _args: &[String], - ) -> Result { + fn on_command(&mut self, _s: &mut Cursive, _cmd: &Command) -> Result { Ok(CommandResult::Ignored) } } impl ViewExt for IdView { - fn on_command( - &mut self, - s: &mut Cursive, - cmd: &str, - args: &[String], - ) -> Result { - self.with_view_mut(move |v| v.on_command(s, cmd, args)) - .unwrap() + fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { + self.with_view_mut(move |v| v.on_command(s, cmd)).unwrap() } } diff --git a/src/ui/album.rs b/src/ui/album.rs index dbb4047..4f2a194 100644 --- a/src/ui/album.rs +++ b/src/ui/album.rs @@ -5,6 +5,7 @@ use cursive::Cursive; use album::Album; use artist::Artist; +use command::Command; use commands::CommandResult; use library::Library; use queue::Queue; @@ -69,12 +70,7 @@ impl ViewExt for AlbumView { format!("{} ({})", self.album.title, self.album.year) } - fn on_command( - &mut self, - s: &mut Cursive, - cmd: &str, - args: &[String], - ) -> Result { - self.tabs.on_command(s, cmd, args) + fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { + self.tabs.on_command(s, cmd) } } diff --git a/src/ui/artist.rs b/src/ui/artist.rs index e7e8038..6b6716e 100644 --- a/src/ui/artist.rs +++ b/src/ui/artist.rs @@ -5,6 +5,7 @@ use cursive::view::ViewWrapper; use cursive::Cursive; use artist::Artist; +use command::Command; use commands::CommandResult; use library::Library; use queue::Queue; @@ -110,12 +111,7 @@ impl ViewExt for ArtistView { self.artist.name.clone() } - fn on_command( - &mut self, - s: &mut Cursive, - cmd: &str, - args: &[String], - ) -> Result { - self.tabs.on_command(s, cmd, args) + fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { + self.tabs.on_command(s, cmd) } } diff --git a/src/ui/layout.rs b/src/ui/layout.rs index 849d534..5b6be84 100644 --- a/src/ui/layout.rs +++ b/src/ui/layout.rs @@ -12,6 +12,8 @@ use cursive::views::EditView; use cursive::{Cursive, Printer}; use unicode_width::UnicodeWidthStr; +use command::Command; +use command::Command::Focus; use commands::CommandResult; use events; use traits::{IntoBoxedViewExt, ViewExt}; @@ -283,29 +285,28 @@ impl View for Layout { } impl ViewExt for Layout { - fn on_command( - &mut self, - s: &mut Cursive, - cmd: &str, - args: &[String], - ) -> Result { - if cmd == "focus" { - if let Some(view) = args.get(0) { + fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { + match cmd { + Command::Focus(view) => { if self.views.keys().any(|k| k == view) { self.set_view(view.clone()); let screen = self.views.get_mut(view).unwrap(); - screen.view.on_command(s, cmd, args)?; + screen.view.on_command(s, cmd)?; + } + + Ok(CommandResult::Consumed(None)) + } + Command::Back => { + self.pop_view(); + Ok(CommandResult::Consumed(None)) + } + _ => { + if let Some(screen) = self.get_current_screen_mut() { + screen.view.on_command(s, cmd) + } else { + Ok(CommandResult::Ignored) } } - - Ok(CommandResult::Consumed(None)) - } else if cmd == "back" { - self.pop_view(); - Ok(CommandResult::Consumed(None)) - } else if let Some(screen) = self.get_current_screen_mut() { - screen.view.on_command(s, cmd, args) - } else { - Ok(CommandResult::Ignored) } } } diff --git a/src/ui/library.rs b/src/ui/library.rs index c3ab05c..3163d6d 100644 --- a/src/ui/library.rs +++ b/src/ui/library.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use cursive::view::ViewWrapper; use cursive::Cursive; +use command::Command; use commands::CommandResult; use library::Library; use queue::Queue; @@ -48,12 +49,7 @@ impl ViewWrapper for LibraryView { } impl ViewExt for LibraryView { - fn on_command( - &mut self, - s: &mut Cursive, - cmd: &str, - args: &[String], - ) -> Result { - self.tabs.on_command(s, cmd, args) + fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { + self.tabs.on_command(s, cmd) } } diff --git a/src/ui/listview.rs b/src/ui/listview.rs index f60c221..89defc9 100644 --- a/src/ui/listview.rs +++ b/src/ui/listview.rs @@ -10,6 +10,7 @@ use cursive::{Cursive, Printer, Rect, Vec2}; use unicode_width::UnicodeWidthStr; use clipboard::{ClipboardContext, ClipboardProvider}; +use command::{Command, GotoMode, MoveMode, TargetMode}; use commands::CommandResult; use library::Library; use queue::Queue; @@ -277,123 +278,116 @@ impl View for ListView { } impl ViewExt for ListView { - fn on_command( - &mut self, - _s: &mut Cursive, - cmd: &str, - args: &[String], - ) -> Result { - if cmd == "play" { - self.queue.clear(); + fn on_command(&mut self, _s: &mut Cursive, cmd: &Command) -> Result { + match cmd { + Command::Play => { + self.queue.clear(); - if !self.attempt_play_all_tracks() { + if !self.attempt_play_all_tracks() { + let mut content = self.content.write().unwrap(); + if let Some(item) = content.get_mut(self.selected) { + item.play(self.queue.clone()); + } + } + + return Ok(CommandResult::Consumed(None)); + } + Command::Queue => { let mut content = self.content.write().unwrap(); if let Some(item) = content.get_mut(self.selected) { - item.play(self.queue.clone()); + item.queue(self.queue.clone()); + } + + return Ok(CommandResult::Consumed(None)); + } + Command::Save => { + let mut item = { + let content = self.content.read().unwrap(); + content.get(self.selected).cloned() + }; + + if let Some(item) = item.as_mut() { + item.toggle_saved(self.library.clone()); } } - return Ok(CommandResult::Consumed(None)); - } - - if cmd == "queue" { - let mut content = self.content.write().unwrap(); - if let Some(item) = content.get_mut(self.selected) { - item.queue(self.queue.clone()); - } - return Ok(CommandResult::Consumed(None)); - } - - if cmd == "save" { - let mut item = { - let content = self.content.read().unwrap(); - content.get(self.selected).cloned() - }; - - if let Some(item) = item.as_mut() { - item.toggle_saved(self.library.clone()); - } - } - - if cmd == "share" { - let source = args.get(0); - let url = - source.and_then(|source| match source.as_str() { - "selected" => self.content.read().ok().and_then(|content| { + Command::Share(mode) => { + let url = match mode { + TargetMode::Selected => self.content.read().ok().and_then(|content| { content.get(self.selected).and_then(ListItem::share_url) }), - "current" => self.queue.get_current().and_then(|t| t.share_url()), - _ => None, - }); + TargetMode::Current => self.queue.get_current().and_then(|t| t.share_url()), + }; - if let Some(url) = url { - ClipboardProvider::new() - .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url)) - .ok(); - }; + if let Some(url) = url { + ClipboardProvider::new() + .and_then(|mut ctx: ClipboardContext| ctx.set_contents(url)) + .ok(); + } - return Ok(CommandResult::Consumed(None)); - } - - if cmd == "move" { - if let Some(dir) = args.get(0) { - let amount: usize = args - .get(1) - .unwrap_or(&"1".to_string()) - .parse() - .map_err(|e| format!("{:?}", e))?; + return Ok(CommandResult::Consumed(None)); + } + Command::Move(mode, amount) => { + let amount = match amount { + Some(amount) => *amount, + _ => 1, + }; let len = self.content.read().unwrap().len(); - if dir == "up" && self.selected > 0 { - self.move_focus(-(amount as i32)); - return Ok(CommandResult::Consumed(None)); - } - - if dir == "down" { - if self.selected < len.saturating_sub(1) { + match mode { + MoveMode::Up if self.selected > 0 => { + self.move_focus(-(amount as i32)); + return Ok(CommandResult::Consumed(None)); + } + MoveMode::Down if self.selected < len.saturating_sub(1) => { self.move_focus(amount as i32); return Ok(CommandResult::Consumed(None)); - } else if self.selected == len.saturating_sub(1) && self.can_paginate() { + } + MoveMode::Down + if self.selected == len.saturating_sub(1) && self.can_paginate() => + { self.pagination.call(&self.content); } + _ => {} } } - } - - if cmd == "open" { - let mut content = self.content.write().unwrap(); - if let Some(item) = content.get_mut(self.selected) { - let queue = self.queue.clone(); - let library = self.library.clone(); - if let Some(view) = item.open(queue, library) { - return Ok(CommandResult::View(view)); - } - } - } - - if cmd == "goto" { - let mut content = self.content.write().unwrap(); - if let Some(item) = content.get_mut(self.selected) { - let queue = self.queue.clone(); - let library = self.library.clone(); - let arg = args.get(0).cloned().unwrap_or_default(); - - if arg == "album" { - if let Some(album) = item.album(queue.clone()) { - let view = AlbumView::new(queue, library, &album).as_boxed_view_ext(); - return Ok(CommandResult::View(view)); - } - } - - if arg == "artist" { - if let Some(artist) = item.artist() { - let view = ArtistView::new(queue, library, &artist).as_boxed_view_ext(); + Command::Open => { + let mut content = self.content.write().unwrap(); + if let Some(item) = content.get_mut(self.selected) { + let queue = self.queue.clone(); + let library = self.library.clone(); + if let Some(view) = item.open(queue, library) { return Ok(CommandResult::View(view)); } } } - } + Command::Goto(mode) => { + let mut content = self.content.write().unwrap(); + if let Some(item) = content.get_mut(self.selected) { + let queue = self.queue.clone(); + let library = self.library.clone(); + + match mode { + GotoMode::Album => { + if let Some(album) = item.album(queue.clone()) { + let view = + AlbumView::new(queue, library, &album).as_boxed_view_ext(); + return Ok(CommandResult::View(view)); + } + } + GotoMode::Artist => { + if let Some(artist) = item.artist() { + let view = + ArtistView::new(queue, library, &artist).as_boxed_view_ext(); + return Ok(CommandResult::View(view)); + } + } + } + } + } + _ => {} + }; Ok(CommandResult::Ignored) } diff --git a/src/ui/playlist.rs b/src/ui/playlist.rs index 70b6667..f3da363 100644 --- a/src/ui/playlist.rs +++ b/src/ui/playlist.rs @@ -3,6 +3,7 @@ use std::sync::{Arc, RwLock}; use cursive::view::ViewWrapper; use cursive::Cursive; +use command::Command; use commands::CommandResult; use library::Library; use playlist::Playlist; @@ -43,12 +44,7 @@ impl ViewExt for PlaylistView { self.playlist.name.clone() } - fn on_command( - &mut self, - s: &mut Cursive, - cmd: &str, - args: &[String], - ) -> Result { - self.list.on_command(s, cmd, args) + fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { + self.list.on_command(s, cmd) } } diff --git a/src/ui/playlists.rs b/src/ui/playlists.rs index 2912b01..57071a2 100644 --- a/src/ui/playlists.rs +++ b/src/ui/playlists.rs @@ -4,6 +4,7 @@ use cursive::view::ViewWrapper; use cursive::views::Dialog; use cursive::Cursive; +use command::Command; use commands::CommandResult; use library::Library; use playlist::Playlist; @@ -52,19 +53,17 @@ impl ViewWrapper for PlaylistsView { } impl ViewExt for PlaylistsView { - fn on_command( - &mut self, - s: &mut Cursive, - cmd: &str, - args: &[String], - ) -> Result { - if cmd == "delete" { - if let Some(dialog) = self.delete_dialog() { - s.add_layer(dialog); + fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { + match cmd { + Command::Delete => { + if let Some(dialog) = self.delete_dialog() { + s.add_layer(dialog); + } + return Ok(CommandResult::Consumed(None)); } - return Ok(CommandResult::Consumed(None)); + _ => {} } - self.list.on_command(s, cmd, args) + self.list.on_command(s, cmd) } } diff --git a/src/ui/queue.rs b/src/ui/queue.rs index 78b6873..17e872a 100644 --- a/src/ui/queue.rs +++ b/src/ui/queue.rs @@ -6,6 +6,7 @@ use cursive::Cursive; use std::cmp::min; use std::sync::Arc; +use command::{Command, ShiftMode}; use commands::CommandResult; use library::Library; use queue::Queue; @@ -88,55 +89,51 @@ impl ViewWrapper for QueueView { } impl ViewExt for QueueView { - fn on_command( - &mut self, - s: &mut Cursive, - cmd: &str, - args: &[String], - ) -> Result { - if cmd == "play" { - self.queue.play(self.list.get_selected_index(), true); - return Ok(CommandResult::Consumed(None)); - } + fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { + match cmd { + Command::Play => { + self.queue.play(self.list.get_selected_index(), true); + return Ok(CommandResult::Consumed(None)); + } + Command::Queue => { + return Ok(CommandResult::Ignored); + } + Command::Delete => { + self.queue.remove(self.list.get_selected_index()); + return Ok(CommandResult::Consumed(None)); + } + Command::Shift(mode, amount) => { + let amount = match amount { + Some(amount) => *amount, + _ => 1, + }; - if cmd == "queue" { - return Ok(CommandResult::Ignored); - } - - if cmd == "delete" { - self.queue.remove(self.list.get_selected_index()); - return Ok(CommandResult::Consumed(None)); - } - - if cmd == "shift" { - if let Some(dir) = args.get(0) { - let amount: usize = args - .get(1) - .unwrap_or(&"1".to_string()) - .parse() - .map_err(|e| format!("{:?}", e))?; let selected = self.list.get_selected_index(); let len = self.queue.len(); - if dir == "up" && selected > 0 { - self.queue.shift(selected, selected.saturating_sub(amount)); - self.list.move_focus(-(amount as i32)); - return Ok(CommandResult::Consumed(None)); - } else if dir == "down" && selected < len.saturating_sub(1) { - self.queue - .shift(selected, min(selected + amount as usize, len - 1)); - self.list.move_focus(amount as i32); - return Ok(CommandResult::Consumed(None)); + + match mode { + ShiftMode::Up if selected > 0 => { + self.queue.shift(selected, selected.saturating_sub(amount)); + self.list.move_focus(-(amount as i32)); + return Ok(CommandResult::Consumed(None)); + } + ShiftMode::Down if selected < len.saturating_sub(1) => { + self.queue + .shift(selected, min(selected + amount as usize, len - 1)); + self.list.move_focus(amount as i32); + return Ok(CommandResult::Consumed(None)); + } + _ => {} } } + Command::SaveQueue => { + let dialog = Self::save_dialog(self.queue.clone(), self.library.clone()); + s.add_layer(dialog); + return Ok(CommandResult::Consumed(None)); + } + _ => {} } - if cmd == "save" && args.get(0).unwrap_or(&"".to_string()) == "queue" { - let dialog = Self::save_dialog(self.queue.clone(), self.library.clone()); - s.add_layer(dialog); - return Ok(CommandResult::Consumed(None)); - } - - self.with_view_mut(move |v| v.on_command(s, cmd, args)) - .unwrap() + self.with_view_mut(move |v| v.on_command(s, cmd)).unwrap() } } diff --git a/src/ui/search.rs b/src/ui/search.rs index 9e19853..aa184fb 100644 --- a/src/ui/search.rs +++ b/src/ui/search.rs @@ -11,6 +11,7 @@ use std::sync::{Arc, Mutex, RwLock}; use album::Album; use artist::Artist; +use command::{Command, MoveMode}; use commands::CommandResult; use events::EventManager; use library::Library; @@ -435,42 +436,37 @@ impl View for SearchView { } impl ViewExt for SearchView { - fn on_command( - &mut self, - s: &mut Cursive, - cmd: &str, - args: &[String], - ) -> Result { - if cmd == "search" && !args.is_empty() { - self.run_search(args.join(" ")); - return Ok(CommandResult::Consumed(None)); - } - - if cmd == "focus" { - self.edit_focused = true; - self.clear(); - return Ok(CommandResult::Consumed(None)); + fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { + match cmd { + Command::Search(query) => self.run_search(query.to_string()), + Command::Focus(view) => { + self.edit_focused = true; + self.clear(); + return Ok(CommandResult::Consumed(None)); + } + _ => {} } let result = if !self.edit_focused { - self.tabs.on_command(s, cmd, args)? + self.tabs.on_command(s, cmd)? } else { CommandResult::Ignored }; if let CommandResult::Ignored = result { - if cmd == "move" { - if let Some(dir) = args.get(0) { - if dir == "up" && !self.edit_focused { + match cmd { + Command::Move(mode, amount) => match mode { + MoveMode::Up if !self.edit_focused => { self.edit_focused = true; return Ok(CommandResult::Consumed(None)); } - - if dir == "down" && self.edit_focused { + MoveMode::Down if self.edit_focused => { self.edit_focused = false; return Ok(CommandResult::Consumed(None)); } - } + _ => {} + }, + _ => {} } } diff --git a/src/ui/tabview.rs b/src/ui/tabview.rs index 3d71c0d..23658e3 100644 --- a/src/ui/tabview.rs +++ b/src/ui/tabview.rs @@ -7,6 +7,7 @@ use cursive::traits::View; use cursive::{Cursive, Printer, Vec2}; use unicode_width::UnicodeWidthStr; +use command::{Command, MoveMode}; use commands::CommandResult; use traits::{IntoBoxedViewExt, ViewExt}; @@ -102,36 +103,33 @@ impl View for TabView { } impl ViewExt for TabView { - fn on_command( - &mut self, - s: &mut Cursive, - cmd: &str, - args: &[String], - ) -> Result { - if cmd == "move" { - if let Some(dir) = args.get(0) { - let amount: i32 = args - .get(1) - .unwrap_or(&"1".to_string()) - .parse() - .map_err(|e| format!("{:?}", e))?; + fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { + match cmd { + Command::Move(mode, amount) => { + let amount = match amount { + Some(amount) => *amount, + _ => 1, + }; let len = self.tabs.len(); - if dir == "left" && self.selected > 0 { - self.move_focus(-amount); - return Ok(CommandResult::Consumed(None)); - } - - if dir == "right" && self.selected < len - 1 { - self.move_focus(amount); - return Ok(CommandResult::Consumed(None)); + match mode { + MoveMode::Left if self.selected > 0 => { + self.move_focus(-(amount as i32)); + return Ok(CommandResult::Consumed(None)); + } + MoveMode::Right if self.selected < len - 1 => { + self.move_focus(amount as i32); + return Ok(CommandResult::Consumed(None)); + } + _ => {} } } + _ => {} } if let Some(tab) = self.tabs.get_mut(self.selected) { - tab.view.on_command(s, cmd, args) + tab.view.on_command(s, cmd) } else { Ok(CommandResult::Ignored) } From 0beaa82a1fbb8a915d006e20f14dd9691de6f0c5 Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Mon, 20 May 2019 22:32:28 +0200 Subject: [PATCH 2/6] Clippy fixes --- src/commands.rs | 3 ++- src/ui/layout.rs | 39 +++++++++++++++++++-------------------- src/ui/playlists.rs | 11 ++++------- src/ui/search.rs | 25 ++++++++++++------------- src/ui/tabview.rs | 33 +++++++++++++++------------------ 5 files changed, 52 insertions(+), 59 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 3b92d49..7fa2575 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -252,7 +252,8 @@ impl CommandManager { Ok(None) } - /* handle default commands + /* + TODO: handle default commands else if let Some(callback) = self.callbacks.get(cmd) { callback.as_ref().map(|cb| cb(s, args)).unwrap_or(Ok(None)) } */ diff --git a/src/ui/layout.rs b/src/ui/layout.rs index 5b6be84..8cdefab 100644 --- a/src/ui/layout.rs +++ b/src/ui/layout.rs @@ -13,7 +13,6 @@ use cursive::{Cursive, Printer}; use unicode_width::UnicodeWidthStr; use command::Command; -use command::Command::Focus; use commands::CommandResult; use events; use traits::{IntoBoxedViewExt, ViewExt}; @@ -207,6 +206,25 @@ impl View for Layout { } } + fn layout(&mut self, size: Vec2) { + self.last_size = size; + + self.statusbar.layout(Vec2::new(size.x, 2)); + + self.cmdline.layout(Vec2::new(size.x, 1)); + + if let Some(screen) = self.get_current_screen_mut() { + screen.view.layout(Vec2::new(size.x, size.y - 3)); + } + + // the focus view has changed, let the views know so they can redraw + // their items + if self.screenchange { + debug!("layout: new screen selected: {:?}", self.focus); + self.screenchange = false; + } + } + fn required_size(&mut self, constraint: Vec2) -> Vec2 { Vec2::new(constraint.x, constraint.y) } @@ -246,25 +264,6 @@ impl View for Layout { } } - fn layout(&mut self, size: Vec2) { - self.last_size = size; - - self.statusbar.layout(Vec2::new(size.x, 2)); - - self.cmdline.layout(Vec2::new(size.x, 1)); - - if let Some(screen) = self.get_current_screen_mut() { - screen.view.layout(Vec2::new(size.x, size.y - 3)); - } - - // the focus view has changed, let the views know so they can redraw - // their items - if self.screenchange { - debug!("layout: new screen selected: {:?}", self.focus); - self.screenchange = false; - } - } - fn call_on_any<'a>(&mut self, s: &Selector, c: AnyCb<'a>) { if let Some(screen) = self.get_current_screen_mut() { screen.view.call_on_any(s, c); diff --git a/src/ui/playlists.rs b/src/ui/playlists.rs index 57071a2..d8b5c57 100644 --- a/src/ui/playlists.rs +++ b/src/ui/playlists.rs @@ -54,14 +54,11 @@ impl ViewWrapper for PlaylistsView { impl ViewExt for PlaylistsView { fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { - match cmd { - Command::Delete => { - if let Some(dialog) = self.delete_dialog() { - s.add_layer(dialog); - } - return Ok(CommandResult::Consumed(None)); + if let Command::Delete = cmd { + if let Some(dialog) = self.delete_dialog() { + s.add_layer(dialog); } - _ => {} + return Ok(CommandResult::Consumed(None)); } self.list.on_command(s, cmd) diff --git a/src/ui/search.rs b/src/ui/search.rs index aa184fb..8bd4eb9 100644 --- a/src/ui/search.rs +++ b/src/ui/search.rs @@ -412,6 +412,14 @@ impl View for SearchView { self.tabs.layout(Vec2::new(size.x, size.y - 1)); } + fn on_event(&mut self, event: Event) -> EventResult { + if self.edit_focused { + self.edit.on_event(event) + } else { + self.tabs.on_event(event) + } + } + fn call_on_any<'a>(&mut self, selector: &Selector<'_>, mut callback: AnyCb<'a>) { self.edit.call_on_any(selector, Box::new(|v| callback(v))); self.tabs.call_on_any(selector, Box::new(|v| callback(v))); @@ -425,21 +433,13 @@ impl View for SearchView { Err(()) } } - - fn on_event(&mut self, event: Event) -> EventResult { - if self.edit_focused { - self.edit.on_event(event) - } else { - self.tabs.on_event(event) - } - } } impl ViewExt for SearchView { fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { match cmd { Command::Search(query) => self.run_search(query.to_string()), - Command::Focus(view) => { + Command::Focus(_) => { self.edit_focused = true; self.clear(); return Ok(CommandResult::Consumed(None)); @@ -454,8 +454,8 @@ impl ViewExt for SearchView { }; if let CommandResult::Ignored = result { - match cmd { - Command::Move(mode, amount) => match mode { + if let Command::Move(mode, _) = cmd { + match mode { MoveMode::Up if !self.edit_focused => { self.edit_focused = true; return Ok(CommandResult::Consumed(None)); @@ -465,8 +465,7 @@ impl ViewExt for SearchView { return Ok(CommandResult::Consumed(None)); } _ => {} - }, - _ => {} + } } } diff --git a/src/ui/tabview.rs b/src/ui/tabview.rs index 23658e3..01f74d8 100644 --- a/src/ui/tabview.rs +++ b/src/ui/tabview.rs @@ -104,28 +104,25 @@ impl View for TabView { impl ViewExt for TabView { fn on_command(&mut self, s: &mut Cursive, cmd: &Command) -> Result { - match cmd { - Command::Move(mode, amount) => { - let amount = match amount { - Some(amount) => *amount, - _ => 1, - }; + if let Command::Move(mode, amount) = cmd { + let amount = match amount { + Some(amount) => *amount, + _ => 1, + }; - let len = self.tabs.len(); + let len = self.tabs.len(); - match mode { - MoveMode::Left if self.selected > 0 => { - self.move_focus(-(amount as i32)); - return Ok(CommandResult::Consumed(None)); - } - MoveMode::Right if self.selected < len - 1 => { - self.move_focus(amount as i32); - return Ok(CommandResult::Consumed(None)); - } - _ => {} + match mode { + MoveMode::Left if self.selected > 0 => { + self.move_focus(-(amount as i32)); + return Ok(CommandResult::Consumed(None)); } + MoveMode::Right if self.selected < len - 1 => { + self.move_focus(amount as i32); + return Ok(CommandResult::Consumed(None)); + } + _ => {} } - _ => {} } if let Some(tab) = self.tabs.get_mut(self.selected) { From 1e0d9ea952573acec416ed474672f87621824779 Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Tue, 21 May 2019 16:01:56 +0200 Subject: [PATCH 3/6] Handle default command behavior --- src/command.rs | 14 ++- src/commands.rs | 314 +++++++++++++++++------------------------------- src/main.rs | 2 +- src/queue.rs | 2 +- 4 files changed, 123 insertions(+), 209 deletions(-) diff --git a/src/command.rs b/src/command.rs index aa507cf..2ce5f0c 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,3 +1,5 @@ +use queue::RepeatSetting; + #[derive(Clone, Serialize, Deserialize, Debug)] pub enum PlaylistCommands { Update, @@ -36,6 +38,12 @@ pub enum GotoMode { Artist, } +#[derive(Clone, Serialize, Deserialize, Debug)] +pub enum SeekDirection { + Relative(i32), + Absolute(u32), +} + #[derive(Clone, Serialize, Deserialize, Debug)] pub enum Command { Quit, @@ -51,9 +59,9 @@ pub enum Command { SaveQueue, Delete, Focus(String), - Seek(SeekInterval), - Repeat, - Shuffle, + Seek(SeekDirection), + Repeat(Option), + Shuffle(Option), Share(TargetMode), Back, Open, diff --git a/src/commands.rs b/src/commands.rs index 7fa2575..93c1464 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -2,20 +2,12 @@ use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; +use command::{ + Command, GotoMode, MoveMode, PlaylistCommands, SeekDirection, ShiftMode, TargetMode, +}; use cursive::event::{Event, Key}; use cursive::views::ViewRef; use cursive::Cursive; - -use command::Command::{ - Back, Clear, Delete, Focus, Goto, Move, Next, Open, Play, Playlists, Previous, Quit, Repeat, - Save, SaveQueue, Seek, Share, Shift, Shuffle, Stop, TogglePlay, -}; -use command::GotoMode::{Album, Artist}; -use command::MoveMode::{Down, Left, Right, Up}; -use command::PlaylistCommands::Update; -use command::SeekInterval::{Backwards, Forward}; -use command::TargetMode::{Current, Selected}; -use command::{Command, ShiftMode}; use library::Library; use queue::{Queue, RepeatSetting}; use spotify::Spotify; @@ -33,13 +25,19 @@ pub enum CommandResult { pub struct CommandManager { callbacks: HashMap>>, aliases: HashMap, + spotify: Arc, + queue: Arc, + library: Arc, } impl CommandManager { - pub fn new() -> CommandManager { + pub fn new(spotify: Arc, queue: Arc, library: Arc) -> CommandManager { CommandManager { callbacks: HashMap::new(), aliases: HashMap::new(), + spotify, + queue, + library, } } @@ -74,158 +72,72 @@ impl CommandManager { self.register_command("back", None); self.register_command("open", None); self.register_command("goto", None); + } - self.register_command( - "quit", - Some(Box::new(move |s, _args| { + fn handle_default_commands( + &self, + s: &mut Cursive, + cmd: &Command, + ) -> Result, String> { + match cmd { + Command::Quit => { s.quit(); Ok(None) - })), - ); + } + Command::Stop => { + self.queue.stop(); + Ok(None) + } + Command::Previous => { + if self.spotify.get_current_progress() < Duration::from_secs(5) { + self.queue.previous(); + } else { + self.spotify.seek(0); + } + Ok(None) + } + Command::Next => { + self.queue.next(true); + Ok(None) + } + Command::Clear => { + self.queue.clear(); + Ok(None) + } - { - let queue = queue.clone(); - self.register_command( - "stop", - Some(Box::new(move |_s, _args| { - queue.stop(); - Ok(None) - })), - ); - } + Command::Playlists(mode) => { + match mode { + PlaylistCommands::Update => self.library.update_playlists(), + } + Ok(None) + } + Command::TogglePlay => { + self.queue.toggleplayback(); + Ok(None) + } + Command::Shuffle(mode) => { + let mode = mode.unwrap_or_else(|| !self.queue.get_shuffle()); + self.queue.set_shuffle(mode); + Ok(None) + } + Command::Repeat(mode) => { + let mode = mode.unwrap_or_else(|| match self.queue.get_repeat() { + RepeatSetting::None => RepeatSetting::RepeatPlaylist, + RepeatSetting::RepeatPlaylist => RepeatSetting::RepeatTrack, + RepeatSetting::RepeatTrack => RepeatSetting::None, + }); - { - let queue = queue.clone(); - let spotify = spotify.clone(); - self.register_command( - "previous", - Some(Box::new(move |_s, _args| { - if spotify.get_current_progress() < Duration::from_secs(5) { - queue.previous(); - } else { - spotify.seek(0); - } - Ok(None) - })), - ); - } - - { - let queue = queue.clone(); - self.register_command( - "next", - Some(Box::new(move |_s, _args| { - queue.next(true); - Ok(None) - })), - ); - } - - { - let queue = queue.clone(); - self.register_command( - "clear", - Some(Box::new(move |_s, _args| { - queue.clear(); - Ok(None) - })), - ); - } - - { - let library = library.clone(); - self.register_command( - "playlists", - Some(Box::new(move |_s, args| { - if let Some(arg) = args.get(0) { - if arg == "update" { - library.update_playlists(); - } - } - Ok(None) - })), - ); - } - - { - let queue = queue.clone(); - self.register_command( - "playpause", - Some(Box::new(move |_s, _args| { - queue.toggleplayback(); - Ok(None) - })), - ); - } - - { - let queue = queue.clone(); - self.register_command( - "shuffle", - Some(Box::new(move |_s, args| { - if let Some(arg) = args.get(0) { - queue.set_shuffle(match arg.as_ref() { - "on" => true, - "off" => false, - _ => { - return Err("Unknown shuffle setting.".to_string()); - } - }); - } else { - queue.set_shuffle(!queue.get_shuffle()); - } - - Ok(None) - })), - ); - } - - { - let queue = queue.clone(); - self.register_command( - "repeat", - Some(Box::new(move |_s, args| { - if let Some(arg) = args.get(0) { - queue.set_repeat(match arg.as_ref() { - "list" | "playlist" | "queue" => RepeatSetting::RepeatPlaylist, - "track" | "once" => RepeatSetting::RepeatTrack, - "none" | "off" => RepeatSetting::None, - _ => { - return Err("Unknown loop setting.".to_string()); - } - }); - } else { - queue.set_repeat(match queue.get_repeat() { - RepeatSetting::None => RepeatSetting::RepeatPlaylist, - RepeatSetting::RepeatPlaylist => RepeatSetting::RepeatTrack, - RepeatSetting::RepeatTrack => RepeatSetting::None, - }); - } - - Ok(None) - })), - ); - } - - { - let spotify = spotify.clone(); - self.register_command( - "seek", - Some(Box::new(move |_s, args| { - if let Some(arg) = args.get(0) { - match arg.chars().next().unwrap() { - '+' | '-' => { - spotify.seek_relative(arg.parse::().unwrap_or(0)); - } - _ => { - spotify.seek(arg.parse::().unwrap_or(0)); - } - } - } - - Ok(None) - })), - ); + self.queue.set_repeat(mode); + Ok(None) + } + Command::Seek(direction) => { + match *direction { + SeekDirection::Relative(rel) => self.spotify.seek_relative(rel), + SeekDirection::Absolute(abs) => self.spotify.seek(abs), + } + Ok(None) + } + _ => Err("Unknown Command".into()), } } @@ -251,14 +163,8 @@ impl CommandManager { }); Ok(None) - } - /* - TODO: handle default commands - else if let Some(callback) = self.callbacks.get(cmd) { - callback.as_ref().map(|cb| cb(s, args)).unwrap_or(Ok(None)) - } */ - else { - Err("Unknown command.".to_string()) + } else { + self.handle_default_commands(s, cmd) } } @@ -303,48 +209,48 @@ impl CommandManager { fn default_keybindings() -> HashMap { let mut kb = HashMap::new(); - kb.insert("q".into(), Quit); - kb.insert("P".into(), TogglePlay); - kb.insert("R".into(), Playlists(Update)); - kb.insert("S".into(), Stop); - kb.insert("<".into(), Previous); - kb.insert(">".into(), Next); - kb.insert("c".into(), Clear); + kb.insert("q".into(), Command::Quit); + kb.insert("P".into(), Command::TogglePlay); + kb.insert("R".into(), Command::Playlists(PlaylistCommands::Update)); + kb.insert("S".into(), Command::Stop); + kb.insert("<".into(), Command::Previous); + kb.insert(">".into(), Command::Next); + kb.insert("c".into(), Command::Clear); kb.insert(" ".into(), Command::Queue); - kb.insert("Enter".into(), Play); - kb.insert("s".into(), Save); - kb.insert("Ctrl+s".into(), SaveQueue); - kb.insert("d".into(), Delete); - kb.insert("/".into(), Focus("search".into())); - kb.insert(".".into(), Seek(Forward)); - kb.insert(",".into(), Seek(Backwards)); - kb.insert("r".into(), Repeat); - kb.insert("z".into(), Shuffle); - kb.insert("x".into(), Share(Current)); - kb.insert("Shift+x".into(), Share(Selected)); + kb.insert("Enter".into(), Command::Play); + kb.insert("s".into(), Command::Save); + kb.insert("Ctrl+s".into(), Command::SaveQueue); + kb.insert("d".into(), Command::Delete); + kb.insert("/".into(), Command::Focus("search".into())); + kb.insert(".".into(), Command::Seek(SeekDirection::Relative(500))); + kb.insert(",".into(), Command::Seek(SeekDirection::Relative(-500))); + kb.insert("r".into(), Command::Repeat(None)); + kb.insert("z".into(), Command::Shuffle(None)); + kb.insert("x".into(), Command::Share(TargetMode::Current)); + kb.insert("Shift+x".into(), Command::Share(TargetMode::Selected)); - kb.insert("F1".into(), Focus("queue".into())); - kb.insert("F2".into(), Focus("search".into())); - kb.insert("F3".into(), Focus("library".into())); - kb.insert("Backspace".into(), Back); + kb.insert("F1".into(), Command::Focus("queue".into())); + kb.insert("F2".into(), Command::Focus("search".into())); + kb.insert("F3".into(), Command::Focus("library".into())); + kb.insert("Backspace".into(), Command::Back); - kb.insert("o".into(), Open); - kb.insert("a".into(), Goto(Album)); - kb.insert("A".into(), Goto(Artist)); + kb.insert("o".into(), Command::Open); + kb.insert("a".into(), Command::Goto(GotoMode::Album)); + kb.insert("A".into(), Command::Goto(GotoMode::Artist)); - kb.insert("Up".into(), Move(Up, None)); - kb.insert("Down".into(), Move(Down, None)); - kb.insert("Left".into(), Move(Left, None)); - kb.insert("Right".into(), Move(Right, None)); - kb.insert("PageUp".into(), Move(Up, Some(5))); - kb.insert("PageDown".into(), Move(Down, Some(5))); - kb.insert("k".into(), Move(Up, None)); - kb.insert("j".into(), Move(Down, None)); - kb.insert("h".into(), Move(Left, None)); - kb.insert("l".into(), Move(Right, None)); + kb.insert("Up".into(), Command::Move(MoveMode::Up, None)); + kb.insert("Down".into(), Command::Move(MoveMode::Down, None)); + kb.insert("Left".into(), Command::Move(MoveMode::Left, None)); + kb.insert("Right".into(), Command::Move(MoveMode::Right, None)); + kb.insert("PageUp".into(), Command::Move(MoveMode::Up, Some(5))); + kb.insert("PageDown".into(), Command::Move(MoveMode::Down, Some(5))); + kb.insert("k".into(), Command::Move(MoveMode::Up, None)); + kb.insert("j".into(), Command::Move(MoveMode::Down, None)); + kb.insert("h".into(), Command::Move(MoveMode::Left, None)); + kb.insert("l".into(), Command::Move(MoveMode::Right, None)); - kb.insert("Shift+Up".into(), Shift(ShiftMode::Up, None)); - kb.insert("Shift+Down".into(), Shift(ShiftMode::Down, None)); + kb.insert("Shift+Up".into(), Command::Shift(ShiftMode::Up, None)); + kb.insert("Shift+Down".into(), Command::Shift(ShiftMode::Down, None)); kb } diff --git a/src/main.rs b/src/main.rs index 90cadb1..c201a49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -184,7 +184,7 @@ fn main() { cfg.use_nerdfont.unwrap_or(false), )); - let mut cmd_manager = CommandManager::new(); + let mut cmd_manager = CommandManager::new(spotify.clone(), queue.clone(), library.clone()); cmd_manager.register_all(spotify.clone(), queue.clone(), library.clone()); let cmd_manager = Arc::new(cmd_manager); diff --git a/src/queue.rs b/src/queue.rs index 97eefd2..d641530 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -5,7 +5,7 @@ use rand::prelude::*; use spotify::Spotify; use track::Track; -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] pub enum RepeatSetting { None, RepeatPlaylist, From 4343343ee77d5bdbd4b749a191f47eadda10e78b Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Tue, 21 May 2019 16:05:12 +0200 Subject: [PATCH 4/6] Implement silent default non-behaviour of a few commands --- src/commands.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 93c1464..1c8c97e 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -61,17 +61,6 @@ impl CommandManager { self.register_aliases("quit", vec!["q", "x"]); self.register_aliases("playpause", vec!["pause", "toggleplay", "toggleplayback"]); self.register_aliases("repeat", vec!["loop"]); - - self.register_command("search", None); - self.register_command("move", None); - self.register_command("shift", None); - self.register_command("play", None); - self.register_command("queue", None); - self.register_command("save", None); - self.register_command("delete", None); - self.register_command("back", None); - self.register_command("open", None); - self.register_command("goto", None); } fn handle_default_commands( @@ -104,7 +93,6 @@ impl CommandManager { self.queue.clear(); Ok(None) } - Command::Playlists(mode) => { match mode { PlaylistCommands::Update => self.library.update_playlists(), @@ -137,6 +125,16 @@ impl CommandManager { } Ok(None) } + Command::Search(_) + | Command::Move(_, _) + | Command::Shift(_, _) + | Command::Play + | Command::Queue + | Command::Save + | Command::Delete + | Command::Back + | Command::Open + | Command::Goto(_) => Ok(None), _ => Err("Unknown Command".into()), } } From 5500b348870afe1a16987b9cb7b0ecd177be2016 Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Tue, 21 May 2019 16:34:50 +0200 Subject: [PATCH 5/6] Remove dead code --- src/commands.rs | 23 +---------------------- src/main.rs | 2 +- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 1c8c97e..3483567 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -14,8 +14,6 @@ use spotify::Spotify; use traits::ViewExt; use ui::layout::Layout; -type CommandCb = dyn Fn(&mut Cursive, &[String]) -> Result, String>; - pub enum CommandResult { Consumed(Option), View(Box), @@ -23,7 +21,6 @@ pub enum CommandResult { } pub struct CommandManager { - callbacks: HashMap>>, aliases: HashMap, spotify: Arc, queue: Arc, @@ -33,7 +30,6 @@ pub struct CommandManager { impl CommandManager { pub fn new(spotify: Arc, queue: Arc, library: Arc) -> CommandManager { CommandManager { - callbacks: HashMap::new(), aliases: HashMap::new(), spotify, queue, @@ -41,10 +37,6 @@ impl CommandManager { } } - pub fn register_command>(&mut self, name: S, cb: Option>) { - self.callbacks.insert(name.into(), cb); - } - pub fn register_aliases>(&mut self, name: S, aliases: Vec) { let name = name.into(); for a in aliases { @@ -52,12 +44,7 @@ impl CommandManager { } } - pub fn register_all( - &mut self, - spotify: Arc, - queue: Arc, - library: Arc, - ) { + pub fn register_all(&mut self) { self.register_aliases("quit", vec!["q", "x"]); self.register_aliases("playpause", vec!["pause", "toggleplay", "toggleplayback"]); self.register_aliases("repeat", vec!["loop"]); @@ -139,14 +126,6 @@ impl CommandManager { } } - fn handle_aliases(&self, name: &str) -> String { - if let Some(s) = self.aliases.get(name) { - self.handle_aliases(s) - } else { - name.to_string() - } - } - fn handle_callbacks(&self, s: &mut Cursive, cmd: &Command) -> Result, String> { let local = { let mut main: ViewRef = s.find_id("main").unwrap(); diff --git a/src/main.rs b/src/main.rs index c201a49..77bef0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -185,7 +185,7 @@ fn main() { )); let mut cmd_manager = CommandManager::new(spotify.clone(), queue.clone(), library.clone()); - cmd_manager.register_all(spotify.clone(), queue.clone(), library.clone()); + cmd_manager.register_all(); let cmd_manager = Arc::new(cmd_manager); CommandManager::register_keybindings( From db7a09c87bcbb3b27450c7f6a370b51951b11eb3 Mon Sep 17 00:00:00 2001 From: Rasmus Larsen Date: Tue, 21 May 2019 18:11:54 +0200 Subject: [PATCH 6/6] Command parser --- src/command.rs | 151 +++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 9 +-- src/ui/queue.rs | 3 +- 3 files changed, 156 insertions(+), 7 deletions(-) diff --git a/src/command.rs b/src/command.rs index 2ce5f0c..9a8c3bd 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,4 +1,6 @@ use queue::RepeatSetting; +use std::collections::HashMap; +use std::iter::FromIterator; #[derive(Clone, Serialize, Deserialize, Debug)] pub enum PlaylistCommands { @@ -66,7 +68,152 @@ pub enum Command { Back, Open, Goto(GotoMode), - Move(MoveMode, Option), - Shift(ShiftMode, Option), + Move(MoveMode, Option), + Shift(ShiftMode, Option), Search(String), } + +fn register_aliases(map: &mut HashMap<&str, &str>, cmd: &'static str, names: Vec<&'static str>) { + for a in names { + map.insert(a, cmd); + } +} + +lazy_static! { + static ref ALIASES: HashMap<&'static str, &'static str> = { + let mut m = HashMap::new(); + + register_aliases(&mut m, "quit", vec!["q", "x"]); + register_aliases( + &mut m, + "playpause", + vec!["pause", "toggleplay", "toggleplayback"], + ); + register_aliases(&mut m, "repeat", vec!["loop"]); + + m.insert("1", "foo"); + m.insert("2", "bar"); + m.insert("3", "baz"); + m + }; +} + +fn handle_aliases(input: &str) -> &str { + if let Some(cmd) = ALIASES.get(input) { + handle_aliases(cmd) + } else { + input + } +} + +pub fn parse(input: &str) -> Option { + let components: Vec<_> = input.trim().split(' ').collect(); + + let command = handle_aliases(&components[0]); + let args = components[1..].to_vec(); + + 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), + "queue" => Some(Command::Queue), + "play" => Some(Command::Play), + "delete" => Some(Command::Delete), + "back" => Some(Command::Back), + "open" => Some(Command::Open), + "search" => args.get(0).map(|query| Command::Search(query.to_string())), + "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 amount = args.get(1).and_then(|amount| amount.parse().ok()); + + 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 + .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().nth(0) { + Some(x) if x == '-' || x == '+' => String::from_iter(arg.chars().skip(1)) + .parse::() + .ok() + .map(|amount| { + Command::Seek(SeekDirection::Relative( + amount + * match x { + '-' => -1, + _ => 1, + }, + )) + }), + _ => String::from_iter(arg.chars()) + .parse() + .ok() + .map(|amount| Command::Seek(SeekDirection::Absolute(amount))), + }), + "focus" => args.get(0).map(|target| Command::Focus(target.to_string())), + "playlists" => args + .get(0) + .and_then(|action| match *action { + "update" => Some(PlaylistCommands::Update), + _ => None, + }) + .map(Command::Playlists), + "save" => args.get(0).map(|target| match *target { + "queue" => Command::SaveQueue, + _ => Command::Save, + }), + _ => None, + } +} diff --git a/src/main.rs b/src/main.rs index 77bef0e..620ca83 100644 --- a/src/main.rs +++ b/src/main.rs @@ -233,8 +233,6 @@ fn main() { }); }); - /* - TODO: Write parser for commands { let ev = event_manager.clone(); let cmd_manager = cmd_manager.clone(); @@ -243,11 +241,14 @@ fn main() { let mut main = s.find_id::("main").unwrap(); main.clear_cmdline(); } - cmd_manager.handle(s, cmd.to_string()[1..].to_string()); + let c = &cmd[1..]; + let parsed = command::parse(c); + if let Some(parsed) = parsed { + cmd_manager.handle(s, parsed); + } ev.trigger(); }); } - */ cursive.add_fullscreen_layer(layout.with_id("main")); diff --git a/src/ui/queue.rs b/src/ui/queue.rs index 17e872a..cad090d 100644 --- a/src/ui/queue.rs +++ b/src/ui/queue.rs @@ -113,7 +113,8 @@ impl ViewExt for QueueView { match mode { ShiftMode::Up if selected > 0 => { - self.queue.shift(selected, selected.saturating_sub(amount)); + self.queue + .shift(selected, (selected as i32).saturating_sub(amount) as usize); self.list.move_focus(-(amount as i32)); return Ok(CommandResult::Consumed(None)); }