mirror of
https://github.com/TECHNOFAB11/tmux-copyrat.git
synced 2025-12-12 16:10:07 +01:00
feat: output to clipboard or tmux buffer
This commit is contained in:
parent
849dd37b05
commit
646ee8b9fb
8 changed files with 164 additions and 25 deletions
39
Cargo.lock
generated
39
Cargo.lock
generated
|
|
@ -70,11 +70,24 @@ name = "copyrat"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"duct",
|
||||
"regex",
|
||||
"sequence_trie",
|
||||
"termion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "duct"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fc6a0a59ed0888e0041cf708e66357b7ae1a82f1c67247e1f93b5e0818f7d8d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"once_cell",
|
||||
"os_pipe",
|
||||
"shared_child",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.9.1"
|
||||
|
|
@ -133,6 +146,22 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "2.4.0"
|
||||
|
|
@ -222,6 +251,16 @@ version = "0.3.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee22067b7ccd072eeb64454b9c6e1b33b61cd0d49e895fd48676a184580e0c3"
|
||||
|
||||
[[package]]
|
||||
name = "shared_child"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6be9f7d5565b1483af3e72975e2dee33879b3b86bd48c0929fccf6585d79e65a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ termion = "1.5"
|
|||
regex = "1.4"
|
||||
clap = { version = "3.0.0-beta.2", features = ["suggestions", "color", "wrap_help"]}
|
||||
sequence_trie = "0.3.6"
|
||||
duct = "0.13"
|
||||
|
||||
[[bin]]
|
||||
name = "copyrat"
|
||||
|
|
@ -20,4 +21,4 @@ path = "src/main.rs"
|
|||
|
||||
[[bin]]
|
||||
name = "tmux-copyrat"
|
||||
path = "src/bridge.rs"
|
||||
path = "src/tmux_copyrat.rs"
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ pub mod alphabets;
|
|||
pub mod colors;
|
||||
pub mod error;
|
||||
pub mod model;
|
||||
pub mod output_destination;
|
||||
pub mod process;
|
||||
pub mod regexes;
|
||||
pub mod selection;
|
||||
pub mod ui;
|
||||
|
||||
/// Run copyrat on an input string `buffer`, configured by `Opt`.
|
||||
|
|
@ -16,7 +18,7 @@ pub mod ui;
|
|||
/// # Note
|
||||
///
|
||||
/// Maybe the decision to take ownership of the buffer is a bit bold.
|
||||
pub fn run(buffer: String, opt: &CliOpt) -> Option<(String, bool)> {
|
||||
pub fn run(buffer: String, opt: &CliOpt) -> Option<selection::Selection> {
|
||||
let mut model = model::Model::new(
|
||||
&buffer,
|
||||
&opt.alphabet,
|
||||
|
|
@ -39,11 +41,14 @@ pub fn run(buffer: String, opt: &CliOpt) -> Option<(String, bool)> {
|
|||
},
|
||||
};
|
||||
|
||||
let selection: Option<(String, bool)> = {
|
||||
let default_output_destination = output_destination::OutputDestination::Tmux;
|
||||
|
||||
let selection: Option<selection::Selection> = {
|
||||
let mut ui = ui::Ui::new(
|
||||
&mut model,
|
||||
opt.unique_hint,
|
||||
opt.focus_wrap_around,
|
||||
default_output_destination,
|
||||
&opt.colors,
|
||||
&opt.hint_alignment,
|
||||
hint_style,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::fs::OpenOptions;
|
|||
use std::io::prelude::*;
|
||||
use std::io::{self, Read};
|
||||
|
||||
use copyrat::{run, CliOpt};
|
||||
use copyrat::{run, selection::Selection, CliOpt};
|
||||
|
||||
fn main() {
|
||||
let opt = CliOpt::parse();
|
||||
|
|
@ -17,14 +17,14 @@ fn main() {
|
|||
|
||||
// Execute copyrat over the buffer (will take control over stdout).
|
||||
// This returns the selected matche.
|
||||
let selection: Option<(String, bool)> = run(buffer, &opt);
|
||||
let selection: Option<Selection> = run(buffer, &opt);
|
||||
|
||||
// Early exit, signaling no selections were found.
|
||||
if selection.is_none() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let (text, _) = selection.unwrap();
|
||||
let Selection { text, .. } = selection.unwrap();
|
||||
|
||||
// Write output to a target_path if provided, else print to original stdout.
|
||||
match opt.target_path {
|
||||
|
|
|
|||
30
src/output_destination.rs
Normal file
30
src/output_destination.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use std::fmt;
|
||||
|
||||
/// Describes the type of buffer the selected should be copied to: either a
|
||||
/// tmux buffer or the system clipboard.
|
||||
#[derive(Clone)]
|
||||
pub enum OutputDestination {
|
||||
/// The selection will be copied to the tmux buffer.
|
||||
Tmux,
|
||||
/// The selection will be copied to the system clipboard.
|
||||
Clipboard,
|
||||
}
|
||||
|
||||
impl OutputDestination {
|
||||
/// Toggle between the variants of `OutputDestination`.
|
||||
pub fn toggle(&mut self) {
|
||||
match *self {
|
||||
Self::Tmux => *self = Self::Clipboard,
|
||||
Self::Clipboard => *self = Self::Tmux,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for OutputDestination {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Tmux => write!(f, "tmux"),
|
||||
Self::Clipboard => write!(f, "clipboard"),
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/selection.rs
Normal file
7
src/selection.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
use crate::output_destination::OutputDestination;
|
||||
|
||||
pub struct Selection {
|
||||
pub text: String,
|
||||
pub uppercased: bool,
|
||||
pub output_destination: OutputDestination,
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ use clap::Clap;
|
|||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use copyrat::{error, process, CliOpt};
|
||||
use copyrat::{error, output_destination::OutputDestination, selection::Selection, CliOpt};
|
||||
|
||||
mod tmux;
|
||||
|
||||
|
|
@ -31,6 +31,13 @@ struct BridgeOpt {
|
|||
#[clap(long, arg_enum, default_value = "visible-area")]
|
||||
capture_region: tmux::CaptureRegion,
|
||||
|
||||
/// Name of the copy-to-clipboard executable.
|
||||
///
|
||||
/// If the output destination is set to keyboard, copyrat will pipe the
|
||||
/// selected text to this executable.
|
||||
#[clap(long, default_value = "pbcopy")]
|
||||
clipboard_exe: String,
|
||||
|
||||
// Include CLI Options
|
||||
#[clap(flatten)]
|
||||
cli_options: CliOpt,
|
||||
|
|
@ -77,28 +84,50 @@ fn main() -> Result<(), error::ParseError> {
|
|||
|
||||
let buffer = tmux::capture_pane(&active_pane, &opt.capture_region)?;
|
||||
|
||||
// We have to dance a little with Panes, because this process i/o streams
|
||||
// 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)?;
|
||||
|
||||
let selections = copyrat::run(buffer, &opt.cli_options);
|
||||
let selection = copyrat::run(buffer, &opt.cli_options);
|
||||
|
||||
tmux::swap_pane_with(&temp_pane_spec)?;
|
||||
|
||||
// Finally copy selection to a tmux buffer, and paste it to the active
|
||||
// buffer if it was uppercased.
|
||||
|
||||
match selections {
|
||||
match selection {
|
||||
None => return Ok(()),
|
||||
Some((text, uppercased)) => {
|
||||
let args = vec!["set-buffer", &text];
|
||||
process::execute("tmux", &args)?;
|
||||
|
||||
Some(Selection {
|
||||
text,
|
||||
uppercased,
|
||||
output_destination,
|
||||
}) => {
|
||||
if uppercased {
|
||||
let args = vec!["paste-buffer", "-t", active_pane.id.as_str()];
|
||||
process::execute("tmux", &args)?;
|
||||
// let args = vec!["send-keys", "-t", active_pane.id.as_str(), &text];
|
||||
// process::execute("tmux", &args)?;
|
||||
duct::cmd!("tmux", "send-keys", "-t", active_pane.id.as_str(), &text).run()?;
|
||||
}
|
||||
|
||||
match output_destination {
|
||||
OutputDestination::Tmux => {
|
||||
// let args = vec!["set-buffer", &text];
|
||||
// process::execute("tmux", &args)?;
|
||||
duct::cmd!("tmux", "set-buffer", &text).run()?;
|
||||
|
||||
// if uppercased {
|
||||
// let args = vec!["paste-buffer", "-t", active_pane.id.as_str()];
|
||||
// process::execute("tmux", &args)?;
|
||||
// }
|
||||
}
|
||||
OutputDestination::Clipboard => {
|
||||
// let args = [("echo", vec![&text[..]]), ("pbcopy", vec![])];
|
||||
// process::execute_piped(&args[..])?;
|
||||
duct::cmd!("echo", "-n", &text)
|
||||
.pipe(duct::cmd!(opt.clipboard_exe))
|
||||
.read()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/ui.rs
46
src/ui.rs
|
|
@ -8,7 +8,7 @@ use sequence_trie::SequenceTrie;
|
|||
use termion::{self, color, cursor, event, style};
|
||||
|
||||
use crate::error::ParseError;
|
||||
use crate::{colors, model};
|
||||
use crate::{colors, model, output_destination::OutputDestination, process, selection::Selection};
|
||||
|
||||
pub struct Ui<'a> {
|
||||
model: &'a mut model::Model<'a>,
|
||||
|
|
@ -18,6 +18,7 @@ pub struct Ui<'a> {
|
|||
lookup_trie: SequenceTrie<char, usize>,
|
||||
focus_index: usize,
|
||||
focus_wrap_around: bool,
|
||||
default_output_destination: OutputDestination,
|
||||
rendering_colors: &'a UiColors,
|
||||
hint_alignment: &'a HintAlignment,
|
||||
hint_style: Option<HintStyle>,
|
||||
|
|
@ -28,6 +29,7 @@ impl<'a> Ui<'a> {
|
|||
model: &'a mut model::Model<'a>,
|
||||
unique_hint: bool,
|
||||
focus_wrap_around: bool,
|
||||
default_output_destination: OutputDestination,
|
||||
rendering_colors: &'a UiColors,
|
||||
hint_alignment: &'a HintAlignment,
|
||||
hint_style: Option<HintStyle>,
|
||||
|
|
@ -47,6 +49,7 @@ impl<'a> Ui<'a> {
|
|||
lookup_trie,
|
||||
focus_index,
|
||||
focus_wrap_around,
|
||||
default_output_destination,
|
||||
rendering_colors,
|
||||
hint_alignment,
|
||||
hint_style,
|
||||
|
|
@ -408,8 +411,9 @@ impl<'a> Ui<'a> {
|
|||
return Event::Exit;
|
||||
}
|
||||
|
||||
let mut uppercased = false;
|
||||
let mut typed_hint = String::new();
|
||||
let mut uppercased = false;
|
||||
let mut output_destination = self.default_output_destination.clone();
|
||||
|
||||
self.full_render(writer);
|
||||
|
||||
|
|
@ -471,11 +475,28 @@ impl<'a> Ui<'a> {
|
|||
// Yank/copy
|
||||
event::Key::Char(_ch @ 'y') | event::Key::Char(_ch @ '\n') => {
|
||||
let text = self.matches.get(self.focus_index).unwrap().text;
|
||||
return Event::Match((text.to_string(), false));
|
||||
return Event::Match(Selection {
|
||||
text: text.to_string(),
|
||||
uppercased: false,
|
||||
output_destination,
|
||||
});
|
||||
}
|
||||
event::Key::Char(_ch @ 'Y') => {
|
||||
let text = self.matches.get(self.focus_index).unwrap().text;
|
||||
return Event::Match((text.to_string(), true));
|
||||
return Event::Match(Selection {
|
||||
text: text.to_string(),
|
||||
uppercased: true,
|
||||
output_destination,
|
||||
});
|
||||
}
|
||||
|
||||
event::Key::Char(_ch @ ' ') => {
|
||||
output_destination.toggle();
|
||||
let message = format!("output destination: `{}`", output_destination);
|
||||
let args = vec!["display-message", &message];
|
||||
process::execute("tmux", &args)
|
||||
.expect("could not make tmux display the message.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use a Trie or another data structure to determine
|
||||
|
|
@ -496,7 +517,7 @@ impl<'a> Ui<'a> {
|
|||
.get_node(&typed_hint.chars().collect::<Vec<char>>());
|
||||
|
||||
if node.is_none() {
|
||||
// An unknown key was entered.
|
||||
// A key outside the alphabet was entered.
|
||||
return Event::Exit;
|
||||
}
|
||||
|
||||
|
|
@ -508,7 +529,11 @@ impl<'a> Ui<'a> {
|
|||
);
|
||||
let mat = self.matches.get(*match_index).expect("By construction, the value in a leaf should correspond to an existing hint.");
|
||||
let text = mat.text.to_string();
|
||||
return Event::Match((text, uppercased));
|
||||
return Event::Match(Selection {
|
||||
text,
|
||||
uppercased,
|
||||
output_destination,
|
||||
});
|
||||
} else {
|
||||
// The prefix of a hint was entered, but we
|
||||
// still need more keys.
|
||||
|
|
@ -530,7 +555,7 @@ impl<'a> Ui<'a> {
|
|||
///
|
||||
/// - Setup steps: switch to alternate screen, switch to raw mode, hide the cursor.
|
||||
/// - Teardown steps: show cursor, back to main screen.
|
||||
pub fn present(&mut self) -> Option<(String, bool)> {
|
||||
pub fn present(&mut self) -> Option<Selection> {
|
||||
use std::io::Write;
|
||||
use termion::raw::IntoRawMode;
|
||||
use termion::screen::AlternateScreen;
|
||||
|
|
@ -546,7 +571,7 @@ impl<'a> Ui<'a> {
|
|||
|
||||
let selection = match self.listen(&mut stdin, &mut stdout) {
|
||||
Event::Exit => None,
|
||||
Event::Match((text, uppercased)) => Some((text, uppercased)),
|
||||
Event::Match(selection) => Some(selection),
|
||||
};
|
||||
|
||||
write!(stdout, "{}", cursor::Show).unwrap();
|
||||
|
|
@ -623,7 +648,7 @@ enum Event {
|
|||
/// Exit with no selected matches,
|
||||
Exit,
|
||||
/// A vector of matched text and whether it was selected with uppercase.
|
||||
Match((String, bool)),
|
||||
Match(Selection),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -915,6 +940,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
|
|||
lookup_trie: SequenceTrie::new(),
|
||||
focus_index: 0,
|
||||
focus_wrap_around: false,
|
||||
default_output_destination: OutputDestination::Tmux,
|
||||
rendering_colors: &rendering_colors,
|
||||
hint_alignment: &hint_alignment,
|
||||
hint_style: None,
|
||||
|
|
@ -968,6 +994,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
|
|||
);
|
||||
let unique_hint = false;
|
||||
let wrap_around = false;
|
||||
let default_output_destination = OutputDestination::Tmux;
|
||||
|
||||
let rendering_colors = UiColors {
|
||||
text_fg: Box::new(color::Black),
|
||||
|
|
@ -986,6 +1013,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
|
|||
&mut model,
|
||||
unique_hint,
|
||||
wrap_around,
|
||||
default_output_destination,
|
||||
&rendering_colors,
|
||||
&hint_alignment,
|
||||
hint_style,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue