From c0ed78beeea5909a51517275ea8c03962ad971ef Mon Sep 17 00:00:00 2001 From: FliegendeWurst <2012gdwu+github@posteo.de> Date: Sun, 13 Aug 2023 10:42:56 +0200 Subject: [PATCH] Capture pane with ANSI escape codes Co-Authored-By: Ian Henry --- Cargo.lock | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + README.md | 36 ++++++++++++++++++++++-------- src/main.rs | 20 ++++++++++++++++- src/state.rs | 20 +++++++++++++++-- src/swapper.rs | 4 ++-- src/view.rs | 2 +- 7 files changed, 128 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9349c19..fd9f563 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,6 +91,24 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +dependencies = [ + "proc-macro2", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -126,6 +144,15 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "strip-ansi-escapes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.8.0" @@ -161,22 +188,55 @@ dependencies = [ "clap", "lazy_static", "regex", + "strip-ansi-escapes", "termion", "unicode-width", ] +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "vte" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +dependencies = [ + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 582943a..b268fe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ clap = "2.34.0" base64 = "0.13.1" unicode-width = "0.1.10" lazy_static = "1.4.0" +strip-ansi-escapes = "0.2.0" [[bin]] name = "thumbs" diff --git a/README.md b/README.md index dfc5af1..d30dcff 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ NOTE: for changes to take effect, you'll need to source again your `.tmux.conf` * [@thumbs-multi-bg-color](#thumbs-multi-bg-color) * [@thumbs-contrast](#thumbs-contrast) * [@thumbs-osc52](#thumbs-osc52) +* [@thumbs-keep-colors](#thumbs-keep-colors) ### @thumbs-key @@ -344,6 +345,18 @@ For example: set -g @thumbs-osc52 1 ``` +### @thumbs-keep-colors + +`default: 0` + +Keep text styling of the input when displaying matches. + +For example: + +``` +set -g @thumbs-keep-colors 1 +``` + #### Colors This is the list of predefined colors: @@ -433,19 +446,20 @@ cargo install thumbs And those are all available options: ``` -thumbs 0.7.1 +thumbs 0.8.0 A lightning fast version copy/pasting like vimium/vimperator USAGE: thumbs [FLAGS] [OPTIONS] FLAGS: - -c, --contrast Put square brackets around hint for visibility - -h, --help Prints help information - -m, --multi Enable multi-selection - -r, --reverse Reverse the order for assigned hints - -u, --unique Don't show duplicated hints for the same match - -V, --version Prints version information + -c, --contrast Put square brackets around hint for visibility + -h, --help Prints help information + --keep-colors Preserve text styling of input + -m, --multi Enable multi-selection + -r, --reverse Reverse the order for assigned hints + -u, --unique Don't show duplicated hints for the same match + -V, --version Prints version information OPTIONS: -a, --alphabet Sets the alphabet [default: qwerty] @@ -456,12 +470,16 @@ OPTIONS: --hint-bg-color Sets the background color for hints [default: black] --hint-fg-color Sets the foregroud color for hints [default: yellow] + --multi-bg-color + Sets the background color for a multi selected item [default: black] + + --multi-fg-color + Sets the foreground color for a multi selected item [default: yellow] + -p, --position Hint position [default: left] -x, --regexp ... Use this regexp as extra pattern to match --select-bg-color Sets the background color for selection [default: black] --select-fg-color Sets the foreground color for selection [default: blue] - --multi-bg-color Sets the background color for a multi selected item [default: black] - --multi-fg-color Sets the foreground color for a multi selected item [default: cyan] -t, --target Stores the hint in the specified path ``` diff --git a/src/main.rs b/src/main.rs index 49ded04..cf774ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -136,6 +136,11 @@ fn app_args<'a>() -> clap::ArgMatches<'a> { .short("t") .takes_value(true), ) + .arg( + Arg::with_name("keep-colors") + .help("Preserve text styling of input") + .long("keep-colors"), + ) .get_matches() } @@ -149,6 +154,7 @@ fn main() { let reverse = args.is_present("reverse"); let unique = args.is_present("unique"); let contrast = args.is_present("contrast"); + let keep_colors = args.is_present("keep-colors"); let regexp = if let Some(items) = args.values_of("regexp") { items.collect::>() } else { @@ -172,7 +178,19 @@ fn main() { let lines = output.split('\n').collect::>(); - let mut state = state::State::new(&lines, alphabet, ®exp); + let mut state; + // strip ansi color codes for match searching + let unescaped: Vec<_> = lines + .iter() + .map(|line| String::from_utf8(strip_ansi_escapes::strip(line)).unwrap()) + .collect(); + let reference = unescaped.iter().map(|x| &**x).collect(); + + if keep_colors { + state = state::State::new_extended(&lines, reference, alphabet, ®exp); + } else { + state = state::State::new_extended(&reference, reference.clone(), alphabet, ®exp); + } let selected = { let mut viewbox = view::View::new( diff --git a/src/state.rs b/src/state.rs index 2e3e00c..3e5d1fb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -56,19 +56,36 @@ impl<'a> PartialEq for Match<'a> { pub struct State<'a> { pub lines: &'a Vec<&'a str>, + pub unescaped: Vec<&'a str>, alphabet: &'a str, regexp: &'a Vec<&'a str>, } impl<'a> State<'a> { + #[cfg(test)] pub fn new(lines: &'a Vec<&'a str>, alphabet: &'a str, regexp: &'a Vec<&'a str>) -> State<'a> { State { + unescaped: lines.clone(), lines, alphabet, regexp, } } + pub fn new_extended( + lines: &'a Vec<&'a str>, + unescaped: Vec<&'a str>, + alphabet: &'a str, + regexp: &'a Vec<&'a str>, + ) -> State<'a> { + State { + lines, + unescaped, + alphabet, + regexp, + } + } + pub fn matches(&self, reverse: bool, unique: bool) -> Vec> { let mut matches = Vec::new(); @@ -91,8 +108,7 @@ impl<'a> State<'a> { // This order determines the priority of pattern matching let all_patterns = [exclude_patterns, custom_patterns, patterns].concat(); - for (index, line) in self.lines.iter().enumerate() { - let mut chunk: &str = line; + for (index, &(mut chunk)) in self.unescaped.iter().enumerate() { let mut offset: i32 = 0; loop { diff --git a/src/swapper.rs b/src/swapper.rs index c901f48..abc4a80 100644 --- a/src/swapper.rs +++ b/src/swapper.rs @@ -164,7 +164,7 @@ impl<'a> Swapper<'a> { let name = captures.get(1).unwrap().as_str(); let value = captures.get(2).unwrap().as_str(); - let boolean_params = vec!["reverse", "unique", "contrast"]; + let boolean_params = vec!["reverse", "unique", "contrast", "keep-colors"]; if boolean_params.iter().any(|&x| x == name) { return vec![format!("--{}", name)]; @@ -215,7 +215,7 @@ impl<'a> Swapper<'a> { }; let pane_command = format!( - "tmux capture-pane -J -t {active_pane_id} -p{scroll_params} | tail -n {height} | {dir}/target/release/thumbs -f '%U:%H' -t {tmp} {args}; tmux swap-pane -t {active_pane_id}; {zoom_command} tmux wait-for -S {signal}", + "tmux capture-pane -J -et {active_pane_id} -p{scroll_params} | tail -n {height} | {dir}/target/release/thumbs -f '%U:%H' -t {tmp} {args}; tmux swap-pane -t {active_pane_id}; {zoom_command} tmux wait-for -S {signal}", active_pane_id = active_pane_id, scroll_params = scroll_params, height = self.active_pane_height.unwrap_or(i32::MAX), diff --git a/src/view.rs b/src/view.rs index e92cf95..97d9224 100644 --- a/src/view.rs +++ b/src/view.rs @@ -124,7 +124,7 @@ impl<'a> View<'a> { }; // Find long utf sequences and extract it from mat.x - let line = &self.state.lines[mat.y as usize]; + let line = &self.state.unescaped[mat.y as usize]; let prefix = &line[0..mat.x as usize]; let extra = prefix.width_cjk() - prefix.chars().count(); let offset = (mat.x as u16) - (extra as u16);