Capture pane with ANSI escape codes

Co-Authored-By: Ian Henry <ianthehenry@gmail.com>
This commit is contained in:
FliegendeWurst 2023-08-13 10:42:56 +02:00
parent ae91d5f7c0
commit c0ed78beee
7 changed files with 128 additions and 15 deletions

60
Cargo.lock generated
View File

@ -91,6 +91,24 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 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]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.13" version = "0.2.13"
@ -126,6 +144,15 @@ version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 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]] [[package]]
name = "strsim" name = "strsim"
version = "0.8.0" version = "0.8.0"
@ -161,22 +188,55 @@ dependencies = [
"clap", "clap",
"lazy_static", "lazy_static",
"regex", "regex",
"strip-ansi-escapes",
"termion", "termion",
"unicode-width", "unicode-width",
] ]
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.10" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "vec_map" name = "vec_map"
version = "0.8.2" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 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]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@ -15,6 +15,7 @@ clap = "2.34.0"
base64 = "0.13.1" base64 = "0.13.1"
unicode-width = "0.1.10" unicode-width = "0.1.10"
lazy_static = "1.4.0" lazy_static = "1.4.0"
strip-ansi-escapes = "0.2.0"
[[bin]] [[bin]]
name = "thumbs" name = "thumbs"

View File

@ -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-multi-bg-color](#thumbs-multi-bg-color)
* [@thumbs-contrast](#thumbs-contrast) * [@thumbs-contrast](#thumbs-contrast)
* [@thumbs-osc52](#thumbs-osc52) * [@thumbs-osc52](#thumbs-osc52)
* [@thumbs-keep-colors](#thumbs-keep-colors)
### @thumbs-key ### @thumbs-key
@ -344,6 +345,18 @@ For example:
set -g @thumbs-osc52 1 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 #### Colors
This is the list of predefined colors: This is the list of predefined colors:
@ -433,7 +446,7 @@ cargo install thumbs
And those are all available options: And those are all available options:
``` ```
thumbs 0.7.1 thumbs 0.8.0
A lightning fast version copy/pasting like vimium/vimperator A lightning fast version copy/pasting like vimium/vimperator
USAGE: USAGE:
@ -442,6 +455,7 @@ USAGE:
FLAGS: FLAGS:
-c, --contrast Put square brackets around hint for visibility -c, --contrast Put square brackets around hint for visibility
-h, --help Prints help information -h, --help Prints help information
--keep-colors Preserve text styling of input
-m, --multi Enable multi-selection -m, --multi Enable multi-selection
-r, --reverse Reverse the order for assigned hints -r, --reverse Reverse the order for assigned hints
-u, --unique Don't show duplicated hints for the same match -u, --unique Don't show duplicated hints for the same match
@ -456,12 +470,16 @@ OPTIONS:
--hint-bg-color <hint_background_color> Sets the background color for hints [default: black] --hint-bg-color <hint_background_color> Sets the background color for hints [default: black]
--hint-fg-color <hint_foreground_color> Sets the foregroud color for hints [default: yellow] --hint-fg-color <hint_foreground_color> Sets the foregroud color for hints [default: yellow]
--multi-bg-color <multi_background_color>
Sets the background color for a multi selected item [default: black]
--multi-fg-color <multi_foreground_color>
Sets the foreground color for a multi selected item [default: yellow]
-p, --position <position> Hint position [default: left] -p, --position <position> Hint position [default: left]
-x, --regexp <regexp>... Use this regexp as extra pattern to match -x, --regexp <regexp>... Use this regexp as extra pattern to match
--select-bg-color <select_background_color> Sets the background color for selection [default: black] --select-bg-color <select_background_color> Sets the background color for selection [default: black]
--select-fg-color <select_foreground_color> Sets the foreground color for selection [default: blue] --select-fg-color <select_foreground_color> Sets the foreground color for selection [default: blue]
--multi-bg-color <multi_background_color> Sets the background color for a multi selected item [default: black]
--multi-fg-color <multi_foreground_color> Sets the foreground color for a multi selected item [default: cyan]
-t, --target <target> Stores the hint in the specified path -t, --target <target> Stores the hint in the specified path
``` ```

View File

@ -136,6 +136,11 @@ fn app_args<'a>() -> clap::ArgMatches<'a> {
.short("t") .short("t")
.takes_value(true), .takes_value(true),
) )
.arg(
Arg::with_name("keep-colors")
.help("Preserve text styling of input")
.long("keep-colors"),
)
.get_matches() .get_matches()
} }
@ -149,6 +154,7 @@ fn main() {
let reverse = args.is_present("reverse"); let reverse = args.is_present("reverse");
let unique = args.is_present("unique"); let unique = args.is_present("unique");
let contrast = args.is_present("contrast"); let contrast = args.is_present("contrast");
let keep_colors = args.is_present("keep-colors");
let regexp = if let Some(items) = args.values_of("regexp") { let regexp = if let Some(items) = args.values_of("regexp") {
items.collect::<Vec<_>>() items.collect::<Vec<_>>()
} else { } else {
@ -172,7 +178,19 @@ fn main() {
let lines = output.split('\n').collect::<Vec<&str>>(); let lines = output.split('\n').collect::<Vec<&str>>();
let mut state = state::State::new(&lines, alphabet, &regexp); 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, &regexp);
} else {
state = state::State::new_extended(&reference, reference.clone(), alphabet, &regexp);
}
let selected = { let selected = {
let mut viewbox = view::View::new( let mut viewbox = view::View::new(

View File

@ -56,19 +56,36 @@ impl<'a> PartialEq for Match<'a> {
pub struct State<'a> { pub struct State<'a> {
pub lines: &'a Vec<&'a str>, pub lines: &'a Vec<&'a str>,
pub unescaped: Vec<&'a str>,
alphabet: &'a str, alphabet: &'a str,
regexp: &'a Vec<&'a str>, regexp: &'a Vec<&'a str>,
} }
impl<'a> State<'a> { impl<'a> State<'a> {
#[cfg(test)]
pub fn new(lines: &'a Vec<&'a str>, alphabet: &'a str, regexp: &'a Vec<&'a str>) -> State<'a> { pub fn new(lines: &'a Vec<&'a str>, alphabet: &'a str, regexp: &'a Vec<&'a str>) -> State<'a> {
State { State {
unescaped: lines.clone(),
lines, lines,
alphabet, alphabet,
regexp, 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<Match<'a>> { pub fn matches(&self, reverse: bool, unique: bool) -> Vec<Match<'a>> {
let mut matches = Vec::new(); let mut matches = Vec::new();
@ -91,8 +108,7 @@ impl<'a> State<'a> {
// This order determines the priority of pattern matching // This order determines the priority of pattern matching
let all_patterns = [exclude_patterns, custom_patterns, patterns].concat(); let all_patterns = [exclude_patterns, custom_patterns, patterns].concat();
for (index, line) in self.lines.iter().enumerate() { for (index, &(mut chunk)) in self.unescaped.iter().enumerate() {
let mut chunk: &str = line;
let mut offset: i32 = 0; let mut offset: i32 = 0;
loop { loop {

View File

@ -164,7 +164,7 @@ impl<'a> Swapper<'a> {
let name = captures.get(1).unwrap().as_str(); let name = captures.get(1).unwrap().as_str();
let value = captures.get(2).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) { if boolean_params.iter().any(|&x| x == name) {
return vec![format!("--{}", name)]; return vec![format!("--{}", name)];
@ -215,7 +215,7 @@ impl<'a> Swapper<'a> {
}; };
let pane_command = format!( 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, active_pane_id = active_pane_id,
scroll_params = scroll_params, scroll_params = scroll_params,
height = self.active_pane_height.unwrap_or(i32::MAX), height = self.active_pane_height.unwrap_or(i32::MAX),

View File

@ -124,7 +124,7 @@ impl<'a> View<'a> {
}; };
// Find long utf sequences and extract it from mat.x // 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 prefix = &line[0..mat.x as usize];
let extra = prefix.width_cjk() - prefix.chars().count(); let extra = prefix.width_cjk() - prefix.chars().count();
let offset = (mat.x as u16) - (extra as u16); let offset = (mat.x as u16) - (extra as u16);