diff --git a/src/bridge.rs b/src/bridge.rs index b33f95e..8546dd2 100644 --- a/src/bridge.rs +++ b/src/bridge.rs @@ -351,11 +351,11 @@ mod tests { #[clap(author, about, version)] struct BridgeOpt { /// Command to execute on selection. - #[clap(short, long, default_value = "tmux set-buffer {}")] + #[clap(long, default_value = "tmux set-buffer {}")] command: String, /// Command to execute on uppercased selection. - #[clap(short, long, default_value = "tmux set-buffer {} && tmux-paste-buffer")] + #[clap(long, default_value = "tmux set-buffer {} && tmux-paste-buffer")] alt_command: String, /// Retrieve options from tmux. @@ -364,7 +364,7 @@ struct BridgeOpt { /// 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)] + #[clap(short = "T", long)] get_options_from_tmux: bool, /// Optionally capture entire pane history. @@ -415,6 +415,7 @@ fn main() -> Result<(), error::ParseError> { } let panes: Vec = tmux::list_panes()?; + let active_pane = panes .iter() .find(|p| p.is_active) @@ -423,22 +424,48 @@ fn main() -> Result<(), error::ParseError> { let buffer = tmux::capture_pane(&active_pane, &opt.capture)?; let selections: Vec<(String, bool)> = if active_pane.in_mode { - // TODO: fancy stuff - vec![(String::new(), false)] + // If the current pane is in copy mode, we have to dance a little with + // Panes, because the current pane has already locked the Alternate + // Screen, preventing copyrat::run to execute. + let initial_pane = active_pane; + + // Create a new window without switching to it. + let temp_pane: tmux::Pane = tmux::create_new_window("[copyrat]")?; + + // Swap the two panes, changing the active pane to be the temp_pane. + // After swap, temp_pane has the same height than the initial_pane + // had before being swapped. + tmux::swap_panes(&initial_pane, &temp_pane)?; + + // Running copyrat now will render in the newly created temp_pane + // (locking stdin, writing to its stdout), but this is almost + // transparent to the user. + let selections = copyrat::run(buffer, &opt.cli_options); + + // Swap back the two panes, making initial_pane the active one again. + tmux::swap_panes(&temp_pane, &initial_pane)?; + + tmux::kill_pane(&temp_pane)?; + + selections } else { copyrat::run(buffer, &opt.cli_options) }; - selections.iter().for_each(|(text, b)| { - let command = if *b { + // 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 { opt.alt_command.replace("{}", text) } else { opt.command.replace("{}", text) }; - let args: Vec<&str> = vec![]; + let mut it = raw_command.split(' ').into_iter(); + let command = it.next().unwrap(); + let args: Vec<&str> = it.collect(); - // Simply execute the command as is and don't mind about potential - // errors. + // Simply execute the command as is, and let the program crash on + // potential errors because it is not our responsibility. process::execute(&command, &args).unwrap(); }); diff --git a/src/lib.rs b/src/lib.rs index d951492..be776fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ pub fn run(buffer: String, opt: &CliOpt) -> Vec<(String, bool)> { let hint_style = match &opt.hint_style { None => None, Some(style) => match style { + HintStyleCli::Italic => Some(view::HintStyle::Italic), HintStyleCli::Underline => Some(view::HintStyle::Underline), HintStyleCli::Surround => { let (open, close) = opt.hint_surroundings; @@ -118,6 +119,7 @@ pub struct CliOpt { /// as we cannot directly parse into view::HintStyle. #[derive(Debug, Clap)] enum HintStyleCli { + Italic, Underline, Surround, } diff --git a/src/tmux.rs b/src/tmux.rs index dece861..313124c 100644 --- a/src/tmux.rs +++ b/src/tmux.rs @@ -78,15 +78,18 @@ pub fn list_panes() -> Result, ParseError> { let args = vec![ "list-panes", "-F", - "#{pane_id}:#{?pane_in_mode,1,0}:#{pane_height}:#{scroll_position}:#{?pane_active,active,nope}", + "#{pane_id}:#{?pane_in_mode,true,false}:#{pane_height}:#{scroll_position}:#{?pane_active,true,false}", ]; let output = process::execute("tmux", &args)?; // Each call to `Pane::parse` returns a `Result`. All results // are collected into a Result, _>, thanks to `collect()`. - let result: Result, ParseError> = - output.split('\n').map(|line| Pane::parse(line)).collect(); + let result: Result, ParseError> = output + .trim_end() // trim last '\n' as it would create an empty line + .split('\n') + .map(|line| Pane::parse(line)) + .collect(); result } @@ -203,7 +206,7 @@ impl FromStr for CaptureRegion { /// position. To support both cases, the implementation always provides those /// parameters to tmux. pub fn capture_pane(pane: &Pane, region: &CaptureRegion) -> Result { - let mut args = format!("capture-pane -t {id} -p", id = pane.id); + let mut args = format!("capture-pane -t %{id} -p", id = pane.id); let region_str = match region { CaptureRegion::VisibleArea => { @@ -231,6 +234,48 @@ pub fn capture_pane(pane: &Pane, region: &CaptureRegion) -> Result Result { + let args = vec!["new-window", "-P", "-d", "-n", name, "-F", + "#{pane_id}:#{?pane_in_mode,true,false}:#{pane_height}:#{scroll_position}:#{?pane_active,true,false}"]; + + let output = process::execute("tmux", &args)?; + + let pane = Pane::parse(output.trim_end())?; // trim last '\n' as it would create an empty line + + Ok(pane) +} + +/// Ask tmux to swap two `Pane`s and change the active pane to be the target +/// `Pane`. +pub fn swap_panes(pane_a: &Pane, pane_b: &Pane) -> Result<(), ParseError> { + let pa_id = format!("%{}", pane_a.id); + let pb_id = format!("%{}", pane_b.id); + + let args = vec!["swap-pane", "-s", &pa_id, "-t", &pb_id]; + + process::execute("tmux", &args)?; + + Ok(()) +} + +/// Ask tmux to kill the provided `Pane`. +pub fn kill_pane(pane: &Pane) -> Result<(), ParseError> { + let p_id = format!("%{}", pane.id); + + let args = vec!["kill-pane", "-t", &p_id]; + + process::execute("tmux", &args)?; + + Ok(()) +} + #[cfg(test)] mod tests { use super::Pane; diff --git a/src/view.rs b/src/view.rs index c3eb5e6..ce27a8a 100644 --- a/src/view.rs +++ b/src/view.rs @@ -88,6 +88,8 @@ impl FromStr for HintAlignment { /// # Note /// In practice, this is wrapped in an `Option`, so that the hint's text can be rendered with no style. pub enum HintStyle { + /// The hint's text will be italicized (leveraging `termion::style::Italic`). + Italic, /// The hint's text will be underlined (leveraging `termion::style::Underline`). Underline, /// The hint's text will be surrounded by these chars. @@ -234,6 +236,21 @@ impl<'a> View<'a> { .unwrap(); } Some(hint_style) => match hint_style { + HintStyle::Italic => { + write!( + stdout, + "{goto}{bg_color}{fg_color}{sty}{hint}{sty_reset}{fg_reset}{bg_reset}", + goto = cursor::Goto(offset.0 as u16 + 1, offset.1 as u16 + 1), + fg_color = fg_color, + bg_color = bg_color, + fg_reset = fg_reset, + bg_reset = bg_reset, + sty = style::Italic, + sty_reset = style::NoItalic, + hint = hint_text, + ) + .unwrap(); + } HintStyle::Underline => { write!( stdout, diff --git a/tmux-copyrat.tmux b/tmux-copyrat.tmux new file mode 100755 index 0000000..9409dc9 --- /dev/null +++ b/tmux-copyrat.tmux @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +DEFAULT_COPYRAT_KEY="space" +COPYRAT_KEY=$(tmux show-option -gqv @copyrat-key) +COPYRAT_KEY=${COPYRAT_KEY:-$DEFAULT_COPYRAT_KEY} + +BINARY="${CURRENT_DIR}/target/release/tmux-copyrat" + +tmux bind-key $COPYRAT_KEY run-shell -b "${BINARY} -T" + +if [ ! -f "$BINARY" ]; then + cd "${CURRENT_DIR}" && cargo build --release +fi diff --git a/tmux-thumbs.tmux b/tmux-thumbs.tmux deleted file mode 100755 index d879bdc..0000000 --- a/tmux-thumbs.tmux +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -DEFAULT_THUMBS_KEY="space" -THUMBS_KEY=$(tmux show-option -gqv @thumbs-key) -THUMBS_KEY=${THUMBS_KEY:-$DEFAULT_THUMBS_KEY} - -tmux bind-key $THUMBS_KEY run-shell -b "${CURRENT_DIR}/tmux-thumbs.sh" - -BINARY="${CURRENT_DIR}/target/release/thumbs" - -if [ ! -f "$BINARY" ]; then - cd "${CURRENT_DIR}" && cargo build --release -fi