tmux-copyrat/src/bridge.rs

127 lines
3.9 KiB
Rust
Raw Normal View History

2020-05-24 21:02:11 +02:00
use clap::Clap;
2020-05-27 10:04:42 +02:00
use std::collections::HashMap;
use std::str::FromStr;
2020-06-02 20:03:16 +02:00
2020-05-27 10:04:42 +02:00
use copyrat::{error, process, CliOpt};
2020-05-25 23:06:00 +02:00
mod tmux;
2020-05-24 21:02:11 +02:00
/// Main configuration, parsed from command line.
#[derive(Clap, Debug)]
#[clap(author, about, version)]
2020-05-27 10:04:42 +02:00
struct BridgeOpt {
2020-05-24 21:02:11 +02:00
/// Command to execute on selection.
2020-05-28 07:07:51 +02:00
#[clap(long, default_value = "tmux set-buffer {}")]
2020-05-24 21:02:11 +02:00
command: String,
2020-05-27 10:04:42 +02:00
/// Command to execute on uppercased selection.
2020-05-28 07:07:51 +02:00
#[clap(long, default_value = "tmux set-buffer {} && tmux-paste-buffer")]
2020-05-24 21:02:11 +02:00
alt_command: String,
2020-05-25 23:06:00 +02:00
2020-05-29 11:50:19 +02:00
/// Don't read options from Tmux.
2020-05-25 23:06:00 +02:00
///
2020-05-29 11:50:19 +02:00
/// By default, options formatted like `copyrat-*` are read from tmux.
/// However, you should consider reading them from the config file (the
/// default option) as this saves both a command call (about 10ms) and a
/// Regex compilation.
#[clap(long)]
ignore_options_from_tmux: bool,
/// Name of the copyrat temporary window.
///
/// Copyrat is launched in a temporary window of that name. The only pane
/// in this temp window gets swapped with the current active one for
/// in-place searching, then swapped back and killed after we exit.
#[clap(long, default_value = "[copyrat]")]
window_name: String,
2020-05-26 08:11:45 +02:00
2020-05-29 11:50:19 +02:00
/// Capture visible area or entire pane history.
#[clap(long, arg_enum, default_value = "visible-area")]
capture_region: tmux::CaptureRegion,
2020-05-27 10:04:42 +02:00
// Include CLI Options
#[clap(flatten)]
cli_options: CliOpt,
}
impl BridgeOpt {
/// Try parsing provided options, and update self with the valid values.
pub fn merge_map(
&mut self,
options: &HashMap<String, String>,
) -> Result<(), error::ParseError> {
for (name, value) in options {
match name.as_ref() {
"@copyrat-command" => {
self.command = String::from(value);
}
"@copyrat-alt-command" => {
self.alt_command = String::from(value);
}
"@copyrat-capture" => {
2020-05-29 11:50:19 +02:00
self.capture_region = tmux::CaptureRegion::from_str(&value)?;
2020-05-27 10:04:42 +02:00
}
// Ignore unknown options.
_ => (),
}
}
// Pass the call to cli_options.
self.cli_options.merge_map(options)?;
Ok(())
}
2020-06-02 20:03:16 +02:00
}
2020-05-29 11:50:19 +02:00
///
2020-05-25 23:06:00 +02:00
fn main() -> Result<(), error::ParseError> {
2020-05-27 10:04:42 +02:00
let mut opt = BridgeOpt::parse();
2020-05-24 21:02:11 +02:00
2020-05-29 11:50:19 +02:00
if !opt.ignore_options_from_tmux {
let tmux_options: HashMap<String, String> = tmux::get_options("@copyrat-")?;
// Override default values with those coming from tmux.
2020-05-27 10:04:42 +02:00
opt.merge_map(&tmux_options)?;
}
2020-05-24 21:02:11 +02:00
2020-05-25 23:06:00 +02:00
let panes: Vec<tmux::Pane> = tmux::list_panes()?;
2020-05-28 07:07:51 +02:00
2020-05-25 23:06:00 +02:00
let active_pane = panes
2020-05-29 11:50:19 +02:00
.into_iter()
2020-05-25 23:06:00 +02:00
.find(|p| p.is_active)
2020-05-29 11:50:19 +02:00
.expect("Exactly one tmux pane should be active in the current window.");
2020-05-28 07:07:51 +02:00
2020-05-29 11:50:19 +02:00
let buffer = tmux::capture_pane(&active_pane, &opt.capture_region)?;
2020-05-28 07:07:51 +02:00
2020-05-29 11:50:19 +02:00
// We have to dance a little with Panes, because this process i/o streams
// are connected to the pane in the window newly created for us, instead
// of the active current pane.
let temp_pane_spec = format!("{}.0", opt.window_name);
tmux::swap_pane_with(&temp_pane_spec)?;
2020-05-28 07:07:51 +02:00
2020-05-29 11:50:19 +02:00
let selections = copyrat::run(buffer, &opt.cli_options);
2020-05-28 07:07:51 +02:00
2020-05-29 11:50:19 +02:00
tmux::swap_pane_with(&temp_pane_spec)?;
2020-05-27 10:04:42 +02:00
2020-05-28 07:07:51 +02:00
// Execute a command on each selection.
// TODO: consider getting rid of multi-selection mode.
selections.iter().for_each(|(text, uppercased)| {
let raw_command = if *uppercased {
2020-05-27 10:04:42 +02:00
opt.alt_command.replace("{}", text)
} else {
opt.command.replace("{}", text)
};
2020-05-29 11:50:19 +02:00
2020-05-28 07:07:51 +02:00
let mut it = raw_command.split(' ').into_iter();
let command = it.next().unwrap();
let args: Vec<&str> = it.collect();
2020-05-27 10:04:42 +02:00
2020-05-28 07:07:51 +02:00
// Simply execute the command as is, and let the program crash on
// potential errors because it is not our responsibility.
2020-05-27 10:04:42 +02:00
process::execute(&command, &args).unwrap();
});
2020-05-26 08:11:45 +02:00
2020-05-24 21:02:11 +02:00
Ok(())
2020-06-02 20:03:16 +02:00
}