feat: output to clipboard or tmux buffer

This commit is contained in:
graelo 2021-03-17 07:55:24 +01:00
parent 849dd37b05
commit 646ee8b9fb
8 changed files with 164 additions and 25 deletions

39
Cargo.lock generated
View file

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

View file

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

View file

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

View file

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

@ -0,0 +1,7 @@
use crate::output_destination::OutputDestination;
pub struct Selection {
pub text: String,
pub uppercased: bool,
pub output_destination: OutputDestination,
}

View file

@ -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()?;
}
}
}
}

View file

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