mirror of
https://github.com/TECHNOFAB11/tmux-copyrat.git
synced 2025-12-10 23:20:06 +01:00
refactor: refactor
This commit is contained in:
parent
37f22b67af
commit
905bd2862c
7 changed files with 1200 additions and 884 deletions
|
|
@ -1,13 +1,14 @@
|
|||
format_code_in_doc_comments = true
|
||||
match_block_trailing_comma = true
|
||||
condense_wildcard_suffixes = true
|
||||
use_field_init_shorthand = true
|
||||
overflow_delimited_expr = true
|
||||
# width_heuristics = "Max"
|
||||
normalize_comments = true
|
||||
reorder_impl_items = true
|
||||
use_try_shorthand = true
|
||||
newline_style = "Unix"
|
||||
format_strings = true
|
||||
wrap_comments = true
|
||||
# comment_width = 100
|
||||
|
||||
# - nightly features
|
||||
# wrap_comments = true
|
||||
# format_code_in_doc_comments = true
|
||||
# normalize_comments = true
|
||||
# reorder_impl_items = true
|
||||
# overflow_delimited_expr = true
|
||||
# match_block_trailing_comma = true
|
||||
# condense_wildcard_suffixes = true
|
||||
# format_strings = true
|
||||
|
|
|
|||
46
src/error.rs
46
src/error.rs
|
|
@ -2,17 +2,45 @@ use std::fmt;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseError {
|
||||
ExpectedSurroundingPair,
|
||||
UnknownAlphabet,
|
||||
UnknownColor,
|
||||
ExpectedSurroundingPair,
|
||||
UnknownAlphabet,
|
||||
UnknownColor,
|
||||
ExpectedPaneIdMarker,
|
||||
ExpectedInt(std::num::ParseIntError),
|
||||
ExpectedBool(std::str::ParseBoolError),
|
||||
ProcessFailure(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ParseError::ExpectedSurroundingPair => write!(f, "Expected 2 chars"),
|
||||
ParseError::UnknownAlphabet => write!(f, "Expected a known alphabet"),
|
||||
ParseError::UnknownColor => write!(f, "Expected ANSI color name (magenta, cyan, black, ...)"),
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ParseError::ExpectedSurroundingPair => write!(f, "Expected 2 chars"),
|
||||
ParseError::UnknownAlphabet => write!(f, "Expected a known alphabet"),
|
||||
ParseError::UnknownColor => {
|
||||
write!(f, "Expected ANSI color name (magenta, cyan, black, ...)")
|
||||
}
|
||||
ParseError::ExpectedPaneIdMarker => write!(f, "Expected pane id marker"),
|
||||
ParseError::ExpectedInt(msg) => write!(f, "Expected an int: {}", msg),
|
||||
ParseError::ExpectedBool(msg) => write!(f, "Expected a bool: {}", msg),
|
||||
ParseError::ProcessFailure(msg) => write!(f, "{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::num::ParseIntError> for ParseError {
|
||||
fn from(error: std::num::ParseIntError) -> Self {
|
||||
ParseError::ExpectedInt(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::str::ParseBoolError> for ParseError {
|
||||
fn from(error: std::str::ParseBoolError) -> Self {
|
||||
ParseError::ExpectedBool(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ParseError {
|
||||
fn from(error: std::io::Error) -> Self {
|
||||
ParseError::ProcessFailure(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
156
src/lib.rs
Normal file
156
src/lib.rs
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
use clap::Clap;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::prelude::*;
|
||||
use std::path;
|
||||
|
||||
pub mod alphabets;
|
||||
pub mod colors;
|
||||
pub mod error;
|
||||
pub mod state;
|
||||
pub mod view;
|
||||
|
||||
/// Run copyrat on an input string `buffer`, configured by `Opt`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Maybe the decision to move ownership is a bit bold.
|
||||
pub fn run(buffer: String, opt: Opt) {
|
||||
let lines: Vec<&str> = buffer.split('\n').collect();
|
||||
|
||||
let mut state = state::State::new(&lines, &opt.alphabet, &opt.custom_regex);
|
||||
|
||||
let hint_style = match opt.hint_style {
|
||||
None => None,
|
||||
Some(style) => match style {
|
||||
HintStyleCli::Underline => Some(view::HintStyle::Underline),
|
||||
HintStyleCli::Surround => {
|
||||
let (open, close) = opt.hint_surroundings;
|
||||
Some(view::HintStyle::Surround(open, close))
|
||||
}
|
||||
},
|
||||
};
|
||||
let uppercased_marker = opt.uppercased_marker;
|
||||
|
||||
let selections: Vec<(String, bool)> = {
|
||||
let mut viewbox = view::View::new(
|
||||
&mut state,
|
||||
opt.multi_selection,
|
||||
opt.reverse,
|
||||
opt.unique,
|
||||
opt.hint_alignment,
|
||||
&opt.colors,
|
||||
hint_style,
|
||||
);
|
||||
|
||||
viewbox.present()
|
||||
};
|
||||
|
||||
// Early exit, signaling tmux we had no selections.
|
||||
if selections.is_empty() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let output: String = if uppercased_marker {
|
||||
selections
|
||||
.iter()
|
||||
.map(|(text, uppercased)| format!("{}:{}", *uppercased, text))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
} else {
|
||||
selections
|
||||
.iter()
|
||||
.map(|(text, _)| text.as_str())
|
||||
.collect::<Vec<&str>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
match opt.target_path {
|
||||
None => println!("{}", output),
|
||||
Some(target) => {
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(target)
|
||||
.expect("Unable to open the target file");
|
||||
|
||||
file.write(output.as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Main configuration, parsed from command line.
|
||||
#[derive(Clap, Debug)]
|
||||
#[clap(author, about, version)]
|
||||
pub struct Opt {
|
||||
/// Alphabet to draw hints from.
|
||||
///
|
||||
/// Possible values are "{A}", "{A}-homerow", "{A}-left-hand",
|
||||
/// "{A}-right-hand", where "{A}" is one of "qwerty", "azerty", "qwertz",
|
||||
/// "dvorak", "colemak". Examples: "qwerty", "dvorak-homerow".
|
||||
#[clap(short = "k", long, default_value = "qwerty",
|
||||
parse(try_from_str = alphabets::parse_alphabet))]
|
||||
alphabet: alphabets::Alphabet,
|
||||
|
||||
/// Enable multi-selection.
|
||||
#[clap(short, long)]
|
||||
multi_selection: bool,
|
||||
|
||||
#[clap(flatten)]
|
||||
colors: view::ViewColors,
|
||||
|
||||
/// Reverse the order for assigned hints.
|
||||
#[clap(short, long)]
|
||||
reverse: bool,
|
||||
|
||||
/// Keep the same hint for identical matches.
|
||||
#[clap(short, long)]
|
||||
unique: bool,
|
||||
|
||||
/// Align hint with its match.
|
||||
#[clap(short = "a", long, arg_enum, default_value = "Leading")]
|
||||
hint_alignment: view::HintAlignment,
|
||||
|
||||
/// Additional regex patterns.
|
||||
#[clap(short = "c", long)]
|
||||
custom_regex: Vec<String>,
|
||||
|
||||
/// Optional hint styling.
|
||||
///
|
||||
/// Underline or surround the hint for increased visibility.
|
||||
/// If not provided, only the hint colors will be used.
|
||||
#[clap(short = "s", long, arg_enum)]
|
||||
hint_style: Option<HintStyleCli>,
|
||||
|
||||
/// Chars surrounding each hint, used with `Surround` style.
|
||||
#[clap(long, default_value = "{}",
|
||||
parse(try_from_str = parse_chars))]
|
||||
hint_surroundings: (char, char),
|
||||
|
||||
/// Target path where to store the selected matches.
|
||||
#[clap(short = "o", long = "output", parse(from_os_str))]
|
||||
target_path: Option<path::PathBuf>,
|
||||
|
||||
/// Describes if the uppercased marker should be added to the output,
|
||||
/// indicating if hint key was uppercased. This is only used by
|
||||
/// tmux-copyrat, so it is skipped from clap configuration.
|
||||
#[clap(skip)]
|
||||
uppercased_marker: bool,
|
||||
}
|
||||
|
||||
/// Type introduced due to parsing limitation,
|
||||
/// as we cannot directly parse into view::HintStyle.
|
||||
#[derive(Debug, Clap)]
|
||||
enum HintStyleCli {
|
||||
Underline,
|
||||
Surround,
|
||||
}
|
||||
|
||||
fn parse_chars(src: &str) -> Result<(char, char), error::ParseError> {
|
||||
if src.len() != 2 {
|
||||
return Err(error::ParseError::ExpectedSurroundingPair);
|
||||
}
|
||||
|
||||
let chars: Vec<char> = src.chars().collect();
|
||||
Ok((chars[0], chars[1]))
|
||||
}
|
||||
146
src/main.rs
146
src/main.rs
|
|
@ -1,88 +1,7 @@
|
|||
use clap::Clap;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, Read};
|
||||
use std::path;
|
||||
|
||||
mod alphabets;
|
||||
mod colors;
|
||||
mod error;
|
||||
mod state;
|
||||
mod view;
|
||||
|
||||
/// Main configuration, parsed from command line.
|
||||
#[derive(Clap, Debug)]
|
||||
#[clap(author, about, version)]
|
||||
struct Opt {
|
||||
/// Alphabet to draw hints from.
|
||||
///
|
||||
/// Possible values are "{A}", "{A}-homerow", "{A}-left-hand",
|
||||
/// "{A}-right-hand", where "{A}" is one of "qwerty", "azerty", "qwertz",
|
||||
/// "dvorak", "colemak". Examples: "qwerty", "dvorak-homerow".
|
||||
#[clap(short = "k", long, default_value = "qwerty",
|
||||
parse(try_from_str = alphabets::parse_alphabet))]
|
||||
alphabet: alphabets::Alphabet,
|
||||
|
||||
/// Enable multi-selection.
|
||||
#[clap(short, long)]
|
||||
multi_selection: bool,
|
||||
|
||||
#[clap(flatten)]
|
||||
colors: view::ViewColors,
|
||||
|
||||
/// Reverse the order for assigned hints.
|
||||
#[clap(short, long)]
|
||||
reverse: bool,
|
||||
|
||||
/// Keep the same hint for identical matches.
|
||||
#[clap(short, long)]
|
||||
unique: bool,
|
||||
|
||||
/// Align hint with its match.
|
||||
#[clap(short = "a", long, arg_enum, default_value = "Leading")]
|
||||
hint_alignment: view::HintAlignment,
|
||||
|
||||
/// Additional regex patterns.
|
||||
#[clap(short = "c", long)]
|
||||
custom_regex: Vec<String>,
|
||||
|
||||
/// Optional hint styling.
|
||||
///
|
||||
/// Underline or surround the hint for increased visibility.
|
||||
/// If not provided, only the hint colors will be used.
|
||||
#[clap(short = "s", long, arg_enum)]
|
||||
hint_style: Option<HintStyleCli>,
|
||||
|
||||
/// Chars surrounding each hint, used with `Surrounded` style.
|
||||
#[clap(long, default_value = "{}",
|
||||
parse(try_from_str = parse_chars))]
|
||||
hint_surroundings: (char, char),
|
||||
|
||||
/// Target path where to store the selected matches.
|
||||
#[clap(short = "o", long = "output", parse(from_os_str))]
|
||||
target_path: Option<path::PathBuf>,
|
||||
|
||||
/// Only output if key was uppercased.
|
||||
#[clap(long)]
|
||||
uppercased: bool,
|
||||
}
|
||||
|
||||
/// Type introduced due to parsing limitation,
|
||||
/// as we cannot directly parse into view::HintStyle.
|
||||
#[derive(Debug, Clap)]
|
||||
enum HintStyleCli {
|
||||
Underlined,
|
||||
Surrounded,
|
||||
}
|
||||
|
||||
fn parse_chars(src: &str) -> Result<(char, char), error::ParseError> {
|
||||
if src.len() != 2 {
|
||||
return Err(error::ParseError::ExpectedSurroundingPair);
|
||||
}
|
||||
|
||||
let chars: Vec<char> = src.chars().collect();
|
||||
Ok((chars[0], chars[1]))
|
||||
}
|
||||
use copyrat::{run, Opt};
|
||||
|
||||
fn main() {
|
||||
let opt = Opt::parse();
|
||||
|
|
@ -93,67 +12,6 @@ fn main() {
|
|||
|
||||
let mut buffer = String::new();
|
||||
handle.read_to_string(&mut buffer).unwrap();
|
||||
let lines: Vec<&str> = buffer.split('\n').collect();
|
||||
|
||||
let mut state = state::State::new(&lines, &opt.alphabet, &opt.custom_regex);
|
||||
|
||||
let hint_style = match opt.hint_style {
|
||||
None => None,
|
||||
Some(style) => match style {
|
||||
HintStyleCli::Underlined => Some(view::HintStyle::Underlined),
|
||||
HintStyleCli::Surrounded => {
|
||||
let (open, close) = opt.hint_surroundings;
|
||||
Some(view::HintStyle::Surrounded(open, close))
|
||||
}
|
||||
},
|
||||
};
|
||||
let uppercase_flag = opt.uppercased;
|
||||
|
||||
let selections = {
|
||||
let mut viewbox = view::View::new(
|
||||
&mut state,
|
||||
opt.multi_selection,
|
||||
opt.reverse,
|
||||
opt.unique,
|
||||
opt.hint_alignment,
|
||||
&opt.colors,
|
||||
hint_style,
|
||||
);
|
||||
|
||||
viewbox.present()
|
||||
};
|
||||
|
||||
// Early exit, signaling tmux we had no selections.
|
||||
if selections.is_empty() {
|
||||
::std::process::exit(1);
|
||||
}
|
||||
|
||||
let output = selections
|
||||
.iter()
|
||||
.map(|(text, uppercased)| {
|
||||
let upcase_value = if *uppercased { "true" } else { "false" };
|
||||
|
||||
let output = if uppercase_flag { upcase_value } else { text };
|
||||
// let mut output = &opt.format;
|
||||
|
||||
// output = str::replace(&output, "%U", upcase_value);
|
||||
// output = str::replace(&output, "%H", text.as_str());
|
||||
output
|
||||
})
|
||||
.collect::<Vec<&str>>()
|
||||
.join("\n");
|
||||
|
||||
match opt.target_path {
|
||||
None => println!("{}", output),
|
||||
Some(target) => {
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(target)
|
||||
.expect("Unable to open the target file");
|
||||
|
||||
file.write(output.as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
run(buffer, opt);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ use std::path;
|
|||
use std::process::Command;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use copyrat::error;
|
||||
|
||||
mod tmux;
|
||||
|
||||
trait Executor {
|
||||
fn execute(&mut self, args: Vec<String>) -> String;
|
||||
fn last_executed(&self) -> Option<Vec<String>>;
|
||||
|
|
@ -38,7 +42,7 @@ impl Executor for RealShell {
|
|||
}
|
||||
}
|
||||
|
||||
const TMP_FILE: &str = "/tmp/thumbs-last";
|
||||
const TMP_FILE: &str = "/tmp/copyrat-last";
|
||||
|
||||
pub struct Swapper<'a> {
|
||||
executor: Box<&'a mut dyn Executor>,
|
||||
|
|
@ -83,11 +87,11 @@ impl<'a> Swapper<'a> {
|
|||
|
||||
pub fn capture_active_pane(&mut self) {
|
||||
let active_command = vec![
|
||||
"tmux",
|
||||
"list-panes",
|
||||
"-F",
|
||||
"#{pane_id}:#{?pane_in_mode,1,0}:#{pane_height}:#{scroll_position}:#{?pane_active,active,nope}",
|
||||
];
|
||||
"tmux",
|
||||
"list-panes",
|
||||
"-F",
|
||||
"#{pane_id}:#{?pane_in_mode,1,0}:#{pane_height}:#{scroll_position}:#{?pane_active,active,nope}",
|
||||
];
|
||||
|
||||
let output = self
|
||||
.executor
|
||||
|
|
@ -194,15 +198,15 @@ impl<'a> Swapper<'a> {
|
|||
|
||||
// NOTE: For debugging add echo $PWD && sleep 5 after tee
|
||||
let pane_command = format!(
|
||||
"tmux capture-pane -t {} -p{} | {}/target/release/thumbs -f '%U:%H' -t {} {}; tmux swap-pane -t {}; tmux wait-for -S {}",
|
||||
active_pane_id,
|
||||
scroll_params,
|
||||
self.directory.to_str().unwrap(),
|
||||
TMP_FILE,
|
||||
args.join(" "),
|
||||
active_pane_id,
|
||||
self.signal
|
||||
);
|
||||
"tmux capture-pane -t {} -p{} | {}/target/release/thumbs -f '%U:%H' -t {} {}; tmux swap-pane -t {}; tmux wait-for -S {}",
|
||||
active_pane_id,
|
||||
scroll_params,
|
||||
self.directory.to_str().unwrap(),
|
||||
TMP_FILE,
|
||||
args.join(" "),
|
||||
active_pane_id,
|
||||
self.signal
|
||||
);
|
||||
|
||||
let thumbs_command = vec![
|
||||
"tmux",
|
||||
|
|
@ -358,37 +362,21 @@ struct Opt {
|
|||
#[clap(
|
||||
short,
|
||||
long,
|
||||
default_value = "'tmux set-bufffer {} && tmux-paste-buffer'"
|
||||
default_value = "'tmux set-buffer {} && tmux-paste-buffer'"
|
||||
)]
|
||||
alt_command: String,
|
||||
|
||||
/// Retrieve options from tmux.
|
||||
///
|
||||
/// If active, options formatted like `copyrat-*` are read from tmux.
|
||||
/// You should prefer reading them from the config file (the default
|
||||
/// option) as this saves both a command call (about 10ms) and a Regex
|
||||
/// compilation.
|
||||
#[clap(long)]
|
||||
options_from_tmux: bool,
|
||||
}
|
||||
|
||||
// fn app_args<'a>() -> clap::ArgMatches<'a> {
|
||||
// App::new("tmux-thumbs")
|
||||
// .version(crate_version!())
|
||||
// .about("A lightning fast version of tmux-fingers, copy/pasting tmux like vimium/vimperator")
|
||||
// .arg(
|
||||
// Arg::with_name("dir")
|
||||
// .help("Directory where to execute thumbs")
|
||||
// .long("dir")
|
||||
// .default_value(""),
|
||||
// )
|
||||
// .arg(
|
||||
// Arg::with_name("command")
|
||||
// .help("Pick command")
|
||||
// .long("command")
|
||||
// .default_value("tmux set-buffer {}"),
|
||||
// )
|
||||
// .arg(
|
||||
// Arg::with_name("upcase_command")
|
||||
// .help("Upcase command")
|
||||
// .long("upcase-command")
|
||||
// .default_value("tmux set-buffer {} && tmux paste-buffer"),
|
||||
// )
|
||||
// .get_matches()
|
||||
// }
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
fn main() -> Result<(), error::ParseError> {
|
||||
let opt = Opt::parse();
|
||||
// let dir = args.value_of("dir").unwrap();
|
||||
// let command = args.value_of("command").unwrap();
|
||||
|
|
@ -398,7 +386,14 @@ fn main() -> std::io::Result<()> {
|
|||
// panic!("Invalid tmux-thumbs execution. Are you trying to execute tmux-thumbs directly?")
|
||||
// }
|
||||
|
||||
let panes: Vec<tmux::Pane> = tmux::list_panes()?;
|
||||
let active_pane = panes
|
||||
.iter()
|
||||
.find(|p| p.is_active)
|
||||
.expect("One tmux pane should be active");
|
||||
|
||||
let mut executor = RealShell::new();
|
||||
|
||||
let mut swapper = Swapper::new(
|
||||
Box::new(&mut executor),
|
||||
opt.directory.as_path(),
|
||||
|
|
|
|||
272
src/tmux.rs
Normal file
272
src/tmux.rs
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
use copyrat::error::ParseError;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use std::process::Command;
|
||||
|
||||
/// Execute an arbitrary Unix command and return the stdout as a `String` if
|
||||
/// successful.
|
||||
pub fn execute(command: &str, args: &Vec<&str>) -> Result<String, ParseError> {
|
||||
let output = Command::new(command).args(args).output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
let msg = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(ParseError::ProcessFailure(format!(
|
||||
"Process failure: {} {}, error {}",
|
||||
command,
|
||||
args.join(" "),
|
||||
msg
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Pane {
|
||||
/// Pane identifier.
|
||||
pub id: u32,
|
||||
/// Describes if the pane is in some mode.
|
||||
pub in_mode: bool,
|
||||
/// Number of lines in the pane.
|
||||
pub height: u32,
|
||||
/// Optional offset from the bottom if the pane is in some mode.
|
||||
///
|
||||
/// When a pane is in copy mode, scrolling up changes the
|
||||
/// `scroll_position`. If the pane is in normal mode, or unscrolled,
|
||||
/// then `0` is returned.
|
||||
pub scroll_position: u32,
|
||||
/// Describes if the pane is currently active (focused).
|
||||
pub is_active: bool,
|
||||
}
|
||||
|
||||
impl Pane {
|
||||
/// Parse a string containing tmux panes status into a new `Pane`.
|
||||
///
|
||||
/// This returns a `Result<Pane, ParseError>` as this call can obviously
|
||||
/// fail if provided an invalid format.
|
||||
///
|
||||
/// The expected format of the tmux status is "%52:false:62:3:false",
|
||||
/// or "%53:false:23::true".
|
||||
///
|
||||
/// This status line is obtained with `tmux list-panes -F '#{pane_id}:#{?pane_in_mode,true,false}:#{pane_height}:#{scroll_position}:#{?pane_active,true,false}'`.
|
||||
///
|
||||
/// For definitions, look at `Pane` type,
|
||||
/// and at the tmux man page for definitions.
|
||||
pub fn parse(src: &str) -> Result<Pane, ParseError> {
|
||||
let items: Vec<&str> = src.split(':').collect();
|
||||
assert_eq!(items.len(), 5, "tmux should have returned 5 items per line");
|
||||
|
||||
let mut iter = items.iter();
|
||||
|
||||
let id_str = iter.next().unwrap();
|
||||
if !id_str.starts_with('%') {
|
||||
return Err(ParseError::ExpectedPaneIdMarker);
|
||||
}
|
||||
let id = id_str[1..].parse::<u32>()?;
|
||||
|
||||
let in_mode = iter.next().unwrap().parse::<bool>()?;
|
||||
|
||||
let height = iter.next().unwrap().parse::<u32>()?;
|
||||
|
||||
let scroll_position = iter.next().unwrap();
|
||||
let scroll_position = if scroll_position.is_empty() {
|
||||
"0"
|
||||
} else {
|
||||
scroll_position
|
||||
};
|
||||
let scroll_position = scroll_position.parse::<u32>()?;
|
||||
|
||||
let is_active = iter.next().unwrap().parse::<bool>()?;
|
||||
|
||||
Ok(Pane {
|
||||
id,
|
||||
in_mode,
|
||||
height,
|
||||
scroll_position,
|
||||
is_active,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of `Pane` from the current tmux session.
|
||||
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}",
|
||||
];
|
||||
|
||||
let output = 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();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns tmux global options as a `HashMap`. The prefix argument is for
|
||||
/// convenience, in order to target only some of our options. For instance,
|
||||
/// `get_options("@copyrat-")` will return a `HashMap` which keys are tmux options names like `@copyrat-command`, and associated values.
|
||||
///
|
||||
/// # Example
|
||||
/// ```get_options("@copyrat-")```
|
||||
pub fn get_options(prefix: &str) -> Result<HashMap<String, String>, ParseError> {
|
||||
let args = vec!["show", "-g"];
|
||||
|
||||
let output = execute("tmux", &args)?;
|
||||
let lines: Vec<&str> = output.split('\n').collect();
|
||||
|
||||
let pattern = format!(r#"{prefix}([\w\-0-9]+) "?(\w+)"?"#, prefix = prefix);
|
||||
let re = Regex::new(&pattern).unwrap();
|
||||
|
||||
let args: HashMap<String, String> = lines
|
||||
.iter()
|
||||
.flat_map(|line| match re.captures(line) {
|
||||
None => None,
|
||||
Some(captures) => {
|
||||
let key = captures[1].to_string();
|
||||
let value = captures[2].to_string();
|
||||
Some((key, value))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
// pub fn toto() {
|
||||
// let options_command = vec!["tmux", "show", "-g"];
|
||||
// let params: Vec<String> = options_command.iter().map(|arg| arg.to_string()).collect();
|
||||
// let options = self.executor.execute(params);
|
||||
// let lines: Vec<&str> = options.split('\n').collect();
|
||||
|
||||
// let pattern = Regex::new(r#"@thumbs-([\w\-0-9]+) "?(\w+)"?"#).unwrap();
|
||||
|
||||
// let args = lines
|
||||
// .iter()
|
||||
// .flat_map(|line| {
|
||||
// if let Some(captures) = pattern.captures(line) {
|
||||
// let name = captures.get(1).unwrap().as_str();
|
||||
// let value = captures.get(2).unwrap().as_str();
|
||||
|
||||
// let boolean_params = vec!["reverse", "unique", "contrast"];
|
||||
|
||||
// if boolean_params.iter().any(|&x| x == name) {
|
||||
// return vec![format!("--{}", name)];
|
||||
// }
|
||||
|
||||
// let string_params = vec![
|
||||
// "position",
|
||||
// "fg-color",
|
||||
// "bg-color",
|
||||
// "hint-bg-color",
|
||||
// "hint-fg-color",
|
||||
// "select-fg-color",
|
||||
// "select-bg-color",
|
||||
// ];
|
||||
|
||||
// if string_params.iter().any(|&x| x == name) {
|
||||
// return vec![format!("--{}", name), format!("'{}'", value)];
|
||||
// }
|
||||
|
||||
// if name.starts_with("regexp") {
|
||||
// return vec!["--regexp".to_string(), format!("'{}'", value)];
|
||||
// }
|
||||
// }
|
||||
// };}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CaptureRegion {
|
||||
/// The entire history.
|
||||
///
|
||||
/// This will end up sending `-S - -E -` to `tmux capture-pane`.
|
||||
EntireHistory,
|
||||
/// Region from start line to end line
|
||||
///
|
||||
/// This works as defined in tmux's docs (order does not matter).
|
||||
Region(i32, i32),
|
||||
}
|
||||
|
||||
/// Returns the Pane's content as a `String`. The `CaptureRegion` if `None`
|
||||
/// will result in the Pane's visible area to be capture (mimics the default
|
||||
/// behavior of tmux `capture-pane`). If a `CaptureRegion` is provided,
|
||||
/// depending on its value, either the entire history will be captured, or a
|
||||
/// user-provided region. For the user-provided region, the order of `start`
|
||||
/// and `end` does not matter. They have the same meaning as described in Tmux
|
||||
/// documentation.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If the pane is in normal mode, capturing the visible area can be done
|
||||
/// without extra arguments (default behavior of `capture-pane`), but if the
|
||||
/// pane is in copy mode, we need to take into account the current scroll
|
||||
/// position. To support both cases, the implementation always provides those
|
||||
/// parameters to tmux.
|
||||
pub fn capture_pane(pane: &Pane, region: &Option<CaptureRegion>) -> Result<String, ParseError> {
|
||||
let mut args = format!("capture-pane -t {id} -p", id = pane.id);
|
||||
|
||||
let region_str = match region {
|
||||
None => {
|
||||
// Will capture the visible area.
|
||||
// Providing start/end helps support both copy and normal modes.
|
||||
format!(
|
||||
" -S {start} -E {end}",
|
||||
start = pane.scroll_position,
|
||||
end = pane.height - pane.scroll_position - 1
|
||||
)
|
||||
}
|
||||
Some(region) => match region {
|
||||
CaptureRegion::EntireHistory => String::from(" -S - -E -"),
|
||||
CaptureRegion::Region(start, end) => {
|
||||
format!(" -S {start} -E {end}", start = start, end = end)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
args.push_str(®ion_str);
|
||||
|
||||
let args: Vec<&str> = args.split(' ').collect();
|
||||
|
||||
let output = execute("tmux", &args)?;
|
||||
Ok(output)
|
||||
|
||||
// format!(
|
||||
// "tmux capture-pane -t {} -p{} | {}/target/release/thumbs -f '%U:%H' -t {} {}; tmux swap-pane -t {}; tmux wait-for -S {}",
|
||||
// active_pane_id,
|
||||
// scroll_params,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Pane;
|
||||
use copyrat::error;
|
||||
|
||||
#[test]
|
||||
fn test_parse_pass() {
|
||||
let output = vec!["%52:false:62:3:false", "%53:false:23::true"];
|
||||
let panes: Result<Vec<Pane>, error::ParseError> =
|
||||
output.iter().map(|&line| Pane::parse(line)).collect();
|
||||
let panes = panes.expect("Could not parse tmux panes");
|
||||
|
||||
let expected = vec![
|
||||
Pane {
|
||||
id: 52,
|
||||
in_mode: false,
|
||||
height: 62,
|
||||
scroll_position: 3,
|
||||
is_active: false,
|
||||
},
|
||||
Pane {
|
||||
id: 53,
|
||||
in_mode: false,
|
||||
height: 23,
|
||||
scroll_position: 0,
|
||||
is_active: true,
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(panes, expected);
|
||||
}
|
||||
}
|
||||
1366
src/view.rs
1366
src/view.rs
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue