feat: add insert command to paste spotify links (#277)
* feat: add insert command to paste spotify links - use Ctrl-v to paste from clipboard or use :insert without an argument * fix: handle paste on disabled clipboard feature * fix: handle wrong URIs Co-authored-by: Henrik Friedrichsen <henrik@affekt.org>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1728,6 +1728,7 @@ dependencies = [
|
|||||||
"log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"notify-rust 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"notify-rust 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
"reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rspotify 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rspotify 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ url = "1.7"
|
|||||||
strum = "0.17.1"
|
strum = "0.17.1"
|
||||||
strum_macros = "0.17.1"
|
strum_macros = "0.17.1"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
[dependencies.cursive]
|
[dependencies.cursive]
|
||||||
version = "0.15"
|
version = "0.15"
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ pub enum Command {
|
|||||||
Help,
|
Help,
|
||||||
ReloadConfig,
|
ReloadConfig,
|
||||||
Noop,
|
Noop,
|
||||||
|
Insert(Option<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Command {
|
impl fmt::Display for Command {
|
||||||
@@ -169,6 +170,7 @@ impl fmt::Display for Command {
|
|||||||
Command::Jump(term) => format!("jump {}", term),
|
Command::Jump(term) => format!("jump {}", term),
|
||||||
Command::Help => "help".to_string(),
|
Command::Help => "help".to_string(),
|
||||||
Command::ReloadConfig => "reload".to_string(),
|
Command::ReloadConfig => "reload".to_string(),
|
||||||
|
Command::Insert(_) => "insert".to_string(),
|
||||||
};
|
};
|
||||||
write!(f, "{}", repr)
|
write!(f, "{}", repr)
|
||||||
}
|
}
|
||||||
@@ -346,6 +348,14 @@ pub fn parse(input: &str) -> Option<Command> {
|
|||||||
"voldown" => Some(Command::VolumeDown),
|
"voldown" => Some(Command::VolumeDown),
|
||||||
"help" => Some(Command::Help),
|
"help" => Some(Command::Help),
|
||||||
"reload" => Some(Command::ReloadConfig),
|
"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())))
|
||||||
|
}
|
||||||
|
}
|
||||||
"noop" => Some(Command::Noop),
|
"noop" => Some(Command::Noop),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ impl CommandManager {
|
|||||||
| Command::Delete
|
| Command::Delete
|
||||||
| Command::Back
|
| Command::Back
|
||||||
| Command::Open(_)
|
| Command::Open(_)
|
||||||
|
| Command::Insert(_)
|
||||||
| Command::Goto(_) => Ok(None),
|
| Command::Goto(_) => Ok(None),
|
||||||
_ => Err("Unknown Command".into()),
|
_ => Err("Unknown Command".into()),
|
||||||
}
|
}
|
||||||
@@ -372,6 +373,7 @@ impl CommandManager {
|
|||||||
|
|
||||||
kb.insert("Shift+Up".into(), Command::Shift(ShiftMode::Up, None));
|
kb.insert("Shift+Up".into(), Command::Shift(ShiftMode::Up, None));
|
||||||
kb.insert("Shift+Down".into(), Command::Shift(ShiftMode::Down, None));
|
kb.insert("Shift+Down".into(), Command::Shift(ShiftMode::Down, None));
|
||||||
|
kb.insert("Ctrl+v".into(), Command::Insert(None));
|
||||||
|
|
||||||
kb
|
kb
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ extern crate url;
|
|||||||
extern crate strum;
|
extern crate strum;
|
||||||
extern crate strum_macros;
|
extern crate strum_macros;
|
||||||
|
|
||||||
|
extern crate regex;
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|||||||
@@ -9,11 +9,16 @@ use cursive::view::ScrollBase;
|
|||||||
use cursive::{Cursive, Printer, Rect, Vec2};
|
use cursive::{Cursive, Printer, Rect, Vec2};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
use crate::album::Album;
|
||||||
|
use crate::artist::Artist;
|
||||||
use crate::command::{Command, GotoMode, JumpMode, MoveAmount, MoveMode, TargetMode};
|
use crate::command::{Command, GotoMode, JumpMode, MoveAmount, MoveMode, TargetMode};
|
||||||
use crate::commands::CommandResult;
|
use crate::commands::CommandResult;
|
||||||
|
use crate::episode::Episode;
|
||||||
use crate::library::Library;
|
use crate::library::Library;
|
||||||
use crate::playable::Playable;
|
use crate::playable::Playable;
|
||||||
|
use crate::playlist::Playlist;
|
||||||
use crate::queue::Queue;
|
use crate::queue::Queue;
|
||||||
|
use crate::show::Show;
|
||||||
use crate::track::Track;
|
use crate::track::Track;
|
||||||
use crate::traits::{IntoBoxedViewExt, ListItem, ViewExt};
|
use crate::traits::{IntoBoxedViewExt, ListItem, ViewExt};
|
||||||
use crate::ui::album::AlbumView;
|
use crate::ui::album::AlbumView;
|
||||||
@@ -21,6 +26,7 @@ use crate::ui::artist::ArtistView;
|
|||||||
use crate::ui::contextmenu::ContextMenu;
|
use crate::ui::contextmenu::ContextMenu;
|
||||||
#[cfg(feature = "share_clipboard")]
|
#[cfg(feature = "share_clipboard")]
|
||||||
use clipboard::{ClipboardContext, ClipboardProvider};
|
use clipboard::{ClipboardContext, ClipboardProvider};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
pub type Paginator<I> = Box<dyn Fn(Arc<RwLock<Vec<I>>>) + Send + Sync>;
|
pub type Paginator<I> = Box<dyn Fn(Arc<RwLock<Vec<I>>>) + Send + Sync>;
|
||||||
pub struct Pagination<I: ListItem> {
|
pub struct Pagination<I: ListItem> {
|
||||||
@@ -525,6 +531,65 @@ impl<I: ListItem + Clone> ViewExt for ListView<I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Command::Insert(url) => {
|
||||||
|
let url = match url.as_ref().map(String::as_str) {
|
||||||
|
#[cfg(feature = "share_clipboard")]
|
||||||
|
Some("") | None => ClipboardProvider::new()
|
||||||
|
.and_then(|mut ctx: ClipboardContext| ctx.get_contents())
|
||||||
|
.ok()
|
||||||
|
.unwrap(),
|
||||||
|
Some(url) => url.to_owned(),
|
||||||
|
// do nothing if clipboard feature is disabled and there is no url provided
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => return Ok(CommandResult::Consumed(None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let spotify = self.queue.get_spotify();
|
||||||
|
|
||||||
|
let re =
|
||||||
|
Regex::new("https://open\\.spotify\\.com/(user/[^/]+/)?([a-z]+)/.+").unwrap();
|
||||||
|
let captures = re.captures(&url);
|
||||||
|
|
||||||
|
if let Some(captures) = captures {
|
||||||
|
let target: Option<Box<dyn ListItem>> = match &captures[2] {
|
||||||
|
"track" => spotify
|
||||||
|
.track(&url)
|
||||||
|
.and_then(|track| Some(Track::from(&track).as_listitem())),
|
||||||
|
"album" => spotify
|
||||||
|
.album(&url)
|
||||||
|
.and_then(|album| Some(Album::from(&album).as_listitem())),
|
||||||
|
"playlist" => spotify
|
||||||
|
.playlist(&url)
|
||||||
|
.and_then(|playlist| Some(Playlist::from(&playlist).as_listitem())),
|
||||||
|
"artist" => spotify
|
||||||
|
.artist(&url)
|
||||||
|
.and_then(|artist| Some(Artist::from(&artist).as_listitem())),
|
||||||
|
"episode" => spotify
|
||||||
|
.episode(&url)
|
||||||
|
.and_then(|episode| Some(Episode::from(&episode).as_listitem())),
|
||||||
|
"show" => spotify
|
||||||
|
.get_show(&url)
|
||||||
|
.and_then(|show| Some(Show::from(&show).as_listitem())),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let queue = self.queue.clone();
|
||||||
|
let library = self.library.clone();
|
||||||
|
// if item has a dedicated view, show it; otherwise open the context menu
|
||||||
|
if let Some(target) = target {
|
||||||
|
let view = target.open(queue.clone(), library.clone());
|
||||||
|
return match view {
|
||||||
|
Some(view) => Ok(CommandResult::View(view)),
|
||||||
|
None => {
|
||||||
|
let contextmenu = ContextMenu::new(target.as_ref(), queue, library);
|
||||||
|
Ok(CommandResult::Modal(Box::new(contextmenu)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(CommandResult::Consumed(None));
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user