refactor: refactor

This commit is contained in:
graelo 2020-05-28 07:07:51 +02:00
parent 791aaadd49
commit 50391320ee
6 changed files with 120 additions and 29 deletions

View file

@ -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::Pane> = 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();
});

View file

@ -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,
}

View file

@ -78,15 +78,18 @@ pub fn list_panes() -> Result<Vec<Pane>, 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<Pane, _>`. All results
// are collected into a Result<Vec<Pane>, _>, thanks to `collect()`.
let result: Result<Vec<Pane>, ParseError> =
output.split('\n').map(|line| Pane::parse(line)).collect();
let result: Result<Vec<Pane>, 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<String, ParseError> {
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<String, Parse
// scroll_params,
}
/// Creates a new named window in the background (without switching to it) and
/// returns a `Pane` describing the newly created pane.
///
/// # Note
///
/// Returning a new `Pane` seems overkill, given we mostly take care of its
/// Id, but it is cleaner.
pub fn create_new_window(name: &str) -> Result<Pane, ParseError> {
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;

View file

@ -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,

15
tmux-copyrat.tmux Executable file
View file

@ -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

View file

@ -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