mirror of
https://github.com/TECHNOFAB11/tmux-copyrat.git
synced 2025-12-12 16:10:07 +01:00
refactor: refactor
This commit is contained in:
parent
791aaadd49
commit
50391320ee
6 changed files with 120 additions and 29 deletions
|
|
@ -351,11 +351,11 @@ mod tests {
|
||||||
#[clap(author, about, version)]
|
#[clap(author, about, version)]
|
||||||
struct BridgeOpt {
|
struct BridgeOpt {
|
||||||
/// Command to execute on selection.
|
/// Command to execute on selection.
|
||||||
#[clap(short, long, default_value = "tmux set-buffer {}")]
|
#[clap(long, default_value = "tmux set-buffer {}")]
|
||||||
command: String,
|
command: String,
|
||||||
|
|
||||||
/// Command to execute on uppercased selection.
|
/// 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,
|
alt_command: String,
|
||||||
|
|
||||||
/// Retrieve options from tmux.
|
/// Retrieve options from tmux.
|
||||||
|
|
@ -364,7 +364,7 @@ struct BridgeOpt {
|
||||||
/// You should consider reading them from the config file (the default
|
/// You should consider reading them from the config file (the default
|
||||||
/// option) as this saves both a command call (about 10ms) and a Regex
|
/// option) as this saves both a command call (about 10ms) and a Regex
|
||||||
/// compilation.
|
/// compilation.
|
||||||
#[clap(long)]
|
#[clap(short = "T", long)]
|
||||||
get_options_from_tmux: bool,
|
get_options_from_tmux: bool,
|
||||||
|
|
||||||
/// Optionally capture entire pane history.
|
/// Optionally capture entire pane history.
|
||||||
|
|
@ -415,6 +415,7 @@ fn main() -> Result<(), error::ParseError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let panes: Vec<tmux::Pane> = tmux::list_panes()?;
|
let panes: Vec<tmux::Pane> = tmux::list_panes()?;
|
||||||
|
|
||||||
let active_pane = panes
|
let active_pane = panes
|
||||||
.iter()
|
.iter()
|
||||||
.find(|p| p.is_active)
|
.find(|p| p.is_active)
|
||||||
|
|
@ -423,22 +424,48 @@ fn main() -> Result<(), error::ParseError> {
|
||||||
let buffer = tmux::capture_pane(&active_pane, &opt.capture)?;
|
let buffer = tmux::capture_pane(&active_pane, &opt.capture)?;
|
||||||
|
|
||||||
let selections: Vec<(String, bool)> = if active_pane.in_mode {
|
let selections: Vec<(String, bool)> = if active_pane.in_mode {
|
||||||
// TODO: fancy stuff
|
// If the current pane is in copy mode, we have to dance a little with
|
||||||
vec![(String::new(), false)]
|
// 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 {
|
} else {
|
||||||
copyrat::run(buffer, &opt.cli_options)
|
copyrat::run(buffer, &opt.cli_options)
|
||||||
};
|
};
|
||||||
|
|
||||||
selections.iter().for_each(|(text, b)| {
|
// Execute a command on each selection.
|
||||||
let command = if *b {
|
// TODO: consider getting rid of multi-selection mode.
|
||||||
|
selections.iter().for_each(|(text, uppercased)| {
|
||||||
|
let raw_command = if *uppercased {
|
||||||
opt.alt_command.replace("{}", text)
|
opt.alt_command.replace("{}", text)
|
||||||
} else {
|
} else {
|
||||||
opt.command.replace("{}", text)
|
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
|
// Simply execute the command as is, and let the program crash on
|
||||||
// errors.
|
// potential errors because it is not our responsibility.
|
||||||
process::execute(&command, &args).unwrap();
|
process::execute(&command, &args).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ pub fn run(buffer: String, opt: &CliOpt) -> Vec<(String, bool)> {
|
||||||
let hint_style = match &opt.hint_style {
|
let hint_style = match &opt.hint_style {
|
||||||
None => None,
|
None => None,
|
||||||
Some(style) => match style {
|
Some(style) => match style {
|
||||||
|
HintStyleCli::Italic => Some(view::HintStyle::Italic),
|
||||||
HintStyleCli::Underline => Some(view::HintStyle::Underline),
|
HintStyleCli::Underline => Some(view::HintStyle::Underline),
|
||||||
HintStyleCli::Surround => {
|
HintStyleCli::Surround => {
|
||||||
let (open, close) = opt.hint_surroundings;
|
let (open, close) = opt.hint_surroundings;
|
||||||
|
|
@ -118,6 +119,7 @@ pub struct CliOpt {
|
||||||
/// as we cannot directly parse into view::HintStyle.
|
/// as we cannot directly parse into view::HintStyle.
|
||||||
#[derive(Debug, Clap)]
|
#[derive(Debug, Clap)]
|
||||||
enum HintStyleCli {
|
enum HintStyleCli {
|
||||||
|
Italic,
|
||||||
Underline,
|
Underline,
|
||||||
Surround,
|
Surround,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
53
src/tmux.rs
53
src/tmux.rs
|
|
@ -78,15 +78,18 @@ pub fn list_panes() -> Result<Vec<Pane>, ParseError> {
|
||||||
let args = vec![
|
let args = vec![
|
||||||
"list-panes",
|
"list-panes",
|
||||||
"-F",
|
"-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)?;
|
let output = process::execute("tmux", &args)?;
|
||||||
|
|
||||||
// Each call to `Pane::parse` returns a `Result<Pane, _>`. All results
|
// Each call to `Pane::parse` returns a `Result<Pane, _>`. All results
|
||||||
// are collected into a Result<Vec<Pane>, _>, thanks to `collect()`.
|
// are collected into a Result<Vec<Pane>, _>, thanks to `collect()`.
|
||||||
let result: Result<Vec<Pane>, ParseError> =
|
let result: Result<Vec<Pane>, ParseError> = output
|
||||||
output.split('\n').map(|line| Pane::parse(line)).collect();
|
.trim_end() // trim last '\n' as it would create an empty line
|
||||||
|
.split('\n')
|
||||||
|
.map(|line| Pane::parse(line))
|
||||||
|
.collect();
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +206,7 @@ impl FromStr for CaptureRegion {
|
||||||
/// position. To support both cases, the implementation always provides those
|
/// position. To support both cases, the implementation always provides those
|
||||||
/// parameters to tmux.
|
/// parameters to tmux.
|
||||||
pub fn capture_pane(pane: &Pane, region: &CaptureRegion) -> Result<String, ParseError> {
|
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 {
|
let region_str = match region {
|
||||||
CaptureRegion::VisibleArea => {
|
CaptureRegion::VisibleArea => {
|
||||||
|
|
@ -231,6 +234,48 @@ pub fn capture_pane(pane: &Pane, region: &CaptureRegion) -> Result<String, Parse
|
||||||
// scroll_params,
|
// 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Pane;
|
use super::Pane;
|
||||||
|
|
|
||||||
17
src/view.rs
17
src/view.rs
|
|
@ -88,6 +88,8 @@ impl FromStr for HintAlignment {
|
||||||
/// # Note
|
/// # Note
|
||||||
/// In practice, this is wrapped in an `Option`, so that the hint's text can be rendered with no style.
|
/// In practice, this is wrapped in an `Option`, so that the hint's text can be rendered with no style.
|
||||||
pub enum HintStyle {
|
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`).
|
/// The hint's text will be underlined (leveraging `termion::style::Underline`).
|
||||||
Underline,
|
Underline,
|
||||||
/// The hint's text will be surrounded by these chars.
|
/// The hint's text will be surrounded by these chars.
|
||||||
|
|
@ -234,6 +236,21 @@ impl<'a> View<'a> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
Some(hint_style) => match hint_style {
|
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 => {
|
HintStyle::Underline => {
|
||||||
write!(
|
write!(
|
||||||
stdout,
|
stdout,
|
||||||
|
|
|
||||||
15
tmux-copyrat.tmux
Executable file
15
tmux-copyrat.tmux
Executable 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
|
||||||
|
|
@ -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
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue