From 25ed6dc78fb8a0254ce3e8ecf47a1ce0daa721de Mon Sep 17 00:00:00 2001 From: Jiahao Li Date: Sat, 23 Nov 2019 17:49:15 -0500 Subject: [PATCH] Adds OSC52 copy escape sequence support Currently I can only get the copied content in the tmux copy buffer, not in the system clipboard. This commit adds an option to print the copied text as a OSC52 copy escape sequence, which in supported terminals (tested in iTerm) will be copied to the system clipboard. --- Cargo.toml | 1 + README.md | 13 +++++++++++++ src/main.rs | 36 +++++++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5ffbe36..f8eb3b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ license = "MIT" termion = "1.5" regex = "1.3.1" clap = "2.33.0" +base64 = "0.11.0" [[bin]] name = "thumbs" diff --git a/README.md b/README.md index 63162b7..3aabcc7 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ NOTE: for changes to take effect, you'll need to source again your `.tmux.conf` * [@thumbs-select-fg-color](#thumbs-select-fg-color) * [@thumbs-select-bg-color](#thumbs-select-bg-color) * [@thumbs-contrast](#thumbs-contrast) +* [@thumbs-osc52](#thumbs-osc52) ### @thumbs-key @@ -273,6 +274,18 @@ For example: set -g @thumbs-contrast 1 ``` +### @thumbs-osc52 + +`default: 0` + +If this is set to `1`, `tmux-thumbs` will print a OSC52 copy escape sequence when you select a match, in addition to running the pick command. This sequence, in terminals that support it (e.g. iTerm), allows the content to be copied into the system clipboard in addition to the tmux copy buffer. + +For example: + +``` +set -g @thumbs-osc52 1 +``` + #### Colors This is the list of available colors: diff --git a/src/main.rs b/src/main.rs index eda20eb..3bd1721 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +extern crate base64; extern crate clap; extern crate termion; @@ -10,7 +11,7 @@ use self::clap::{App, Arg}; use clap::crate_version; use std::fs::OpenOptions; use std::io::prelude::*; -use std::io::{self, Read}; +use std::io::{self, Read, Write}; fn app_args<'a>() -> clap::ArgMatches<'a> { App::new("thumbs") @@ -84,6 +85,12 @@ fn app_args<'a>() -> clap::ArgMatches<'a> { .long("unique") .short("u"), ) + .arg( + Arg::with_name("osc52") + .help("Print OSC52 copy escape sequence in addition to running the pick command") + .long("osc52") + .short("o"), + ) .arg( Arg::with_name("position") .help("Hint position") @@ -124,6 +131,7 @@ fn main() { let multi = args.is_present("multi"); let reverse = args.is_present("reverse"); let unique = args.is_present("unique"); + let osc52 = args.is_present("osc52"); let contrast = args.is_present("contrast"); let regexp = if let Some(items) = args.values_of("regexp") { items.collect::>() @@ -182,6 +190,32 @@ fn main() { .collect::>() .join("\n"); + if osc52 { + let base64_text = base64::encode(text.as_bytes()); + let osc_seq = format!("\x1b]52;0;{}\x07", base64_text); + let tmux_seq = format!("\x1bPtmux;{}\x1b\\", osc_seq.replace("\x1b", "\x1b\x1b")); + + // When the user selects a match: + // 1. The `rustbox` object created in the `viewbox` above is dropped. + // 2. During its `drop`, the `rustbox` object sends a CSI 1049 escape + // sequence to tmux. + // 3. This escape sequence causes the `window_pane_alternate_off` function + // in tmux to be called. + // 4. In `window_pane_alternate_off`, tmux sets the needs-redraw flag in the + // pane. + // 5. If we print the OSC copy escape sequence before the redraw is completed, + // tmux will *not* send the sequence to the host terminal. See the following + // call chain in tmux: `input_dcs_dispatch` -> `screen_write_rawstring` + // -> `tty_write` -> `tty_client_ready`. In this case, `tty_client_ready` + // will return false, thus preventing the escape sequence from being sent. + // + // Therefore, for now we wait a little bit here for the redraw to finish. + std::thread::sleep(std::time::Duration::from_millis(100)); + + std::io::stdout().write_all(tmux_seq.as_bytes()).unwrap(); + std::io::stdout().flush().unwrap(); + } + if let Some(target) = target { let mut file = OpenOptions::new() .create(true)