mirror of
https://github.com/NotAShelf/mpvrc.git
synced 2026-04-17 00:13:48 +00:00
interactive: improve general usage with vi mode & history; add shortcuts
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I5bf85f6ad656ddc3c8246f07630658806a6a6964
This commit is contained in:
parent
ec229485d4
commit
1768c14a33
1 changed files with 77 additions and 40 deletions
|
|
@ -1,41 +1,72 @@
|
||||||
use crate::commands::Commands;
|
use crate::commands::Commands;
|
||||||
use crate::{MrcError, Result};
|
use crate::{MrcError, Result};
|
||||||
|
use rustyline::config::EditMode;
|
||||||
|
use rustyline::{Cmd, Config, Editor, KeyEvent};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::io::{self, Write};
|
use std::io::Error;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub struct InteractiveMode;
|
pub struct InteractiveMode;
|
||||||
|
|
||||||
impl InteractiveMode {
|
impl InteractiveMode {
|
||||||
pub async fn run() -> Result<()> {
|
pub async fn run(socket_path: Option<&str>) -> Result<()> {
|
||||||
|
let hist_path = dirs::data_local_dir()
|
||||||
|
.map(|p| p.join("mrc").join("history.txt"))
|
||||||
|
.unwrap_or_else(|| PathBuf::from("history.txt"));
|
||||||
|
|
||||||
|
let config = Config::builder().edit_mode(EditMode::Vi).build();
|
||||||
|
|
||||||
|
let mut rl: Editor<(), _> = Editor::with_config(config)
|
||||||
|
.map_err(|e| MrcError::ConnectionError(Error::other(e.to_string())))?;
|
||||||
|
|
||||||
|
rl.bind_sequence(KeyEvent::ctrl('c'), Cmd::Interrupt);
|
||||||
|
rl.bind_sequence(KeyEvent::ctrl('l'), Cmd::ClearScreen);
|
||||||
|
|
||||||
|
let _ = rl.load_history(&hist_path);
|
||||||
|
|
||||||
println!("Entering interactive mode. Type 'help' for commands or 'exit' to quit.");
|
println!("Entering interactive mode. Type 'help' for commands or 'exit' to quit.");
|
||||||
let stdin = io::stdin();
|
println!("Socket: {}", socket_path.unwrap_or("/tmp/mpvsocket"));
|
||||||
let mut stdout = io::stdout();
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
print!("mpv> ");
|
let readline = rl.readline("mpv> ");
|
||||||
stdout.flush().map_err(MrcError::ConnectionError)?;
|
match readline {
|
||||||
|
Ok(input) => {
|
||||||
|
let trimmed = input.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rl.add_history_entry(trimmed).ok();
|
||||||
|
|
||||||
let mut input = String::new();
|
if trimmed.eq_ignore_ascii_case("exit") {
|
||||||
stdin
|
println!("Exiting interactive mode.");
|
||||||
.read_line(&mut input)
|
break;
|
||||||
.map_err(MrcError::ConnectionError)?;
|
}
|
||||||
let trimmed = input.trim();
|
|
||||||
|
|
||||||
if trimmed.eq_ignore_ascii_case("exit") {
|
if trimmed.eq_ignore_ascii_case("help") {
|
||||||
println!("Exiting interactive mode.");
|
Self::show_help();
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if trimmed.eq_ignore_ascii_case("help") {
|
if let Err(e) = Self::process_command(trimmed, socket_path).await {
|
||||||
Self::show_help();
|
eprintln!("Error: {}", e);
|
||||||
continue;
|
}
|
||||||
}
|
}
|
||||||
|
Err(rustyline::error::ReadlineError::Interrupted) => {
|
||||||
if let Err(e) = Self::process_command(trimmed).await {
|
println!("(interrupted)");
|
||||||
eprintln!("Error: {}", e);
|
continue;
|
||||||
|
}
|
||||||
|
Err(rustyline::error::ReadlineError::Eof) => {
|
||||||
|
println!("Exiting interactive mode.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Error: {:?}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rl.save_history(&hist_path).ok();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +84,7 @@ impl InteractiveMode {
|
||||||
("seek <seconds>", "Seek to the specified position"),
|
("seek <seconds>", "Seek to the specified position"),
|
||||||
("clear", "Clear the playlist"),
|
("clear", "Clear the playlist"),
|
||||||
("list", "List all items in the playlist"),
|
("list", "List all items in the playlist"),
|
||||||
("add <files>", "Add files to the playlist"),
|
("add <files...>", "Add files to the playlist"),
|
||||||
("get <property>", "Get the specified property"),
|
("get <property>", "Get the specified property"),
|
||||||
(
|
(
|
||||||
"set <property> <value>",
|
"set <property> <value>",
|
||||||
|
|
@ -64,56 +95,61 @@ impl InteractiveMode {
|
||||||
];
|
];
|
||||||
|
|
||||||
for (command, description) in commands {
|
for (command, description) in commands {
|
||||||
println!(" {} - {}", command, description);
|
println!(" {:<22} - {}", command, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("\nKeyboard shortcuts:");
|
||||||
|
println!(" Ctrl+C - Interrupt current command");
|
||||||
|
println!(" Ctrl+L - Clear screen");
|
||||||
|
println!(" Ctrl+D - Exit");
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_command(input: &str) -> Result<()> {
|
async fn process_command(input: &str, socket_path: Option<&str>) -> Result<()> {
|
||||||
let parts: Vec<&str> = input.split_whitespace().collect();
|
let parts: Vec<&str> = input.split_whitespace().collect();
|
||||||
|
|
||||||
match parts.as_slice() {
|
match parts.as_slice() {
|
||||||
["play"] => {
|
["play"] => {
|
||||||
Commands::play(None).await?;
|
Commands::play(None, socket_path).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
["play", index] => {
|
["play", index] => {
|
||||||
if let Ok(idx) = index.parse::<usize>() {
|
if let Ok(idx) = index.parse::<usize>() {
|
||||||
Commands::play(Some(idx)).await?;
|
Commands::play(Some(idx), socket_path).await?;
|
||||||
} else {
|
} else {
|
||||||
println!("Invalid index: {}", index);
|
println!("Invalid index: {}", index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
["pause"] => {
|
["pause"] => {
|
||||||
Commands::pause().await?;
|
Commands::pause(socket_path).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
["stop"] => {
|
["stop"] => {
|
||||||
Commands::stop().await?;
|
Commands::stop(socket_path).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
["next"] => {
|
["next"] => {
|
||||||
Commands::next().await?;
|
Commands::next(socket_path).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
["prev"] => {
|
["prev"] => {
|
||||||
Commands::prev().await?;
|
Commands::prev(socket_path).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
["seek", seconds] => {
|
["seek", seconds] => {
|
||||||
if let Ok(sec) = seconds.parse::<i32>() {
|
if let Ok(sec) = seconds.parse::<i32>() {
|
||||||
Commands::seek_to(sec.into()).await?;
|
Commands::seek_to(sec.into(), socket_path).await?;
|
||||||
} else {
|
} else {
|
||||||
println!("Invalid seconds: {}", seconds);
|
println!("Invalid seconds: {}", seconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
["clear"] => {
|
["clear"] => {
|
||||||
Commands::clear_playlist().await?;
|
Commands::clear_playlist(socket_path).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
["list"] => {
|
["list"] => {
|
||||||
Commands::list_playlist().await?;
|
Commands::list_playlist(socket_path).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
["add", files @ ..] => {
|
["add", files @ ..] => {
|
||||||
|
|
@ -121,18 +157,18 @@ impl InteractiveMode {
|
||||||
if file_strings.is_empty() {
|
if file_strings.is_empty() {
|
||||||
println!("No files provided to add to the playlist");
|
println!("No files provided to add to the playlist");
|
||||||
} else {
|
} else {
|
||||||
Commands::add_files(&file_strings).await?;
|
Commands::add_files(&file_strings, socket_path).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
["get", property] => {
|
["get", property] => {
|
||||||
Commands::get_single_property(property).await?;
|
Commands::get_single_property(property, socket_path).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
["set", property, value] => {
|
["set", property, value] => {
|
||||||
let json_value = serde_json::from_str::<serde_json::Value>(value)
|
let json_value = serde_json::from_str::<serde_json::Value>(value)
|
||||||
.unwrap_or_else(|_| json!(value));
|
.unwrap_or_else(|_| json!(value));
|
||||||
Commands::set_single_property(property, &json_value).await?;
|
Commands::set_single_property(property, &json_value, socket_path).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -144,3 +180,4 @@ impl InteractiveMode {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue