From c97b4f194e80c840ff477df0c55ea08806d470ca Mon Sep 17 00:00:00 2001 From: graelo Date: Wed, 3 Jun 2020 07:09:24 +0200 Subject: [PATCH 01/50] refactor: rename -> feat: copyrat.tmux --- README.md | 2 +- tmux-copyrat.tmux => copyrat.tmux | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tmux-copyrat.tmux => copyrat.tmux (100%) diff --git a/README.md b/README.md index e8f3d6a..2407a25 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ cargo build --release Source it in your `.tmux.conf`: ``` -run-shell ~/.tmux/plugins/tmux-copyrat/tmux-copyrat.tmux +run-shell ~/.tmux/plugins/tmux-copyrat/copyrat.tmux ``` Reload TMUX conf by running: diff --git a/tmux-copyrat.tmux b/copyrat.tmux similarity index 100% rename from tmux-copyrat.tmux rename to copyrat.tmux From 9deed2fd11d0767fe0d2359a32e753c44969b5da Mon Sep 17 00:00:00 2001 From: graelo Date: Wed, 3 Jun 2020 23:55:09 +0200 Subject: [PATCH 02/50] fix(tmux): update feat: copyrat.tmux --- copyrat.tmux | 70 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/copyrat.tmux b/copyrat.tmux index a7b7c1f..45a6bf7 100755 --- a/copyrat.tmux +++ b/copyrat.tmux @@ -1,19 +1,67 @@ #!/usr/bin/env bash +# This script is run only once at tmux launch. +# It provides configuration based on the @copyrat-* options set in your `tmux.conf`. + CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +BINARY="${CURRENT_DIR}/tmux-copyrat" -DEFAULT_COPYRAT_KEY="space" -COPYRAT_KEY=$(tmux show-option -gqv @copyrat-key) -COPYRAT_KEY=${COPYRAT_KEY:-$DEFAULT_COPYRAT_KEY} +setup_option() { + local opt_name=$1 + local default_value=$2 + local current_value=$(tmux show-option -gqv @copyrat-${opt_name}) + value=${current_value:-${default_value}} + tmux set-option -g @copyrat-${opt_name} ${value} +} -DEFAULT_COPYRAT_WINDOW_NAME="[copyrat]" -COPYRAT_WINDOW_NAME=$(tmux show-option -gqv @copyrat-window-name) -COPYRAT_WINDOW_NAME=${COPYRAT_WINDOW_NAME:-$DEFAULT_COPYRAT_WINDOW_NAME} -BINARY="${CURRENT_DIR}/target/release/tmux-copyrat" +# Sets the window name when copyrat is run, providing a default if @copyrat-window-name was not defined. +setup_option "window-name" "[copyrat]" -tmux bind-key ${COPYRAT_KEY} new-window -d -n ${COPYRAT_WINDOW_NAME} "${BINARY} --window-name ${COPYRAT_WINDOW_NAME} --reverse --unique" +# DEFAULT_COPYRAT_WINDOW_NAME="[copyrat]" +# COPYRAT_WINDOW_NAME=$(tmux show-option -gqv @copyrat-window-name) +# COPYRAT_WINDOW_NAME=${COPYRAT_WINDOW_NAME:-$DEFAULT_COPYRAT_WINDOW_NAME} +# # overrides with the same value, I did not bother writing a test +# tmux set-option -g @copyrat-window-name ${COPYRAT_WINDOW_NAME} -if [ ! -f "$BINARY" ]; then - cd "${CURRENT_DIR}" && cargo build --release -fi +# Sets the keytable for all bindings, providing a default if @copyrat-keytable was not defined. +# Keytables open a new shortcut space: if 't' is the switcher (see below), prefix + t + +setup_option "keytable" "cpyrt" +# DEFAULT_COPYRAT_KEYTABLE="cpyrt" +# COPYRAT_KEYTABLE=$(tmux show-option -gqv @copyrat-keytable) +# COPYRAT_KEYTABLE=${COPYRAT_KEYTABLE:-$DEFAULT_COPYRAT_KEYTABLE} + +# Sets the key to access the keytable: prefix + + +# providing a default if @copyrat-keyswitch is not defined. +setup_option "keyswitch" "u" + +local keyswitch=$(tmux show-option @copyrat-keyswitch) +local keytable=$(tmux show-option @copyrat-keytable) +tmux bind-key ${keyswitch} -T ${keytable} + +setup_binding() { + local key=$1 + local pattern_name=$2 + local window_name=$(tmux show-option -gqv @copyrat-window-name) + tmux bind-key -T ${keytable} $key new-window -d -n ${window_name} "${BINARY} --window-name ${window_name} --reverse --unique" +} + +# Search +setup_pattern_binding "space" "" +setup_pattern_binding "p" "--pattern-name path" +setup_pattern_binding "u" "--pattern-name url" +setup_pattern_binding "h" "--pattern-name sha" +setup_pattern_binding "d" "--pattern-name docker" +setup_pattern_binding "c" "--pattern-name hexcolor" +setup_pattern_binding "i" "--pattern-name ip" + +# DEFAULT_COPYRAT_KEY="space" +# COPYRAT_KEY=$(tmux show-option -gqv @copyrat-key) +# COPYRAT_KEY=${COPYRAT_KEY:-$DEFAULT_COPYRAT_KEY} + + +# tmux bind-key -T ${keytable} "/" command-prompt "search:" new-window -d -n ${COPYRAT_WINDOW_NAME} "${BINARY} --window-name ${COPYRAT_WINDOW_NAME} --reverse --unique --custom-regex '%%'" + +# if [ ! -f "$BINARY" ]; then +# cd "${CURRENT_DIR}" && cargo build --release +# fi From 7ce0b517dc7e8d02c78a50fdec796b9bc754bd61 Mon Sep 17 00:00:00 2001 From: graelo Date: Thu, 4 Jun 2020 00:09:59 +0200 Subject: [PATCH 03/50] fix: wrapping lines containing multibyte chars --- src/ui.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index 196cd3f..681621f 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,4 +1,5 @@ use std::char; +use std::cmp; use std::io; use std::str::FromStr; @@ -560,10 +561,18 @@ fn get_line_offsets(lines: &Vec<&str>, term_width: u16) -> Vec { lines .iter() .scan(0, |offset, &line| { + // Save the value to return (yield is in unstable). let value = *offset; - // amount of extra y space taken by this line - let extra = line.trim_end().len() / term_width as usize; + let line_width = line.trim_end().chars().count() as isize; + + // Amount of extra y space taken by this line. + // If the line has n chars, on a term of width n, this does not + // produce an extra line; it needs to exceed the width by 1 char. + // In case the width is 0, we need to clamp line_width - 1 first. + let extra = cmp::max(0, line_width - 1) as usize / term_width as usize; + + // Update the offset of the next line. *offset = *offset + 1 + extra; Some(value) From 0f66b6fbd9652459d12ffaa66c521b323b1cfc1a Mon Sep 17 00:00:00 2001 From: graelo Date: Thu, 4 Jun 2020 07:16:07 +0200 Subject: [PATCH 04/50] feat: match email addresses --- src/model.rs | 22 ++++++++++++++++++++++ src/regexes.rs | 3 ++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/model.rs b/src/model.rs index 4334fdc..4c40072 100644 --- a/src/model.rs +++ b/src/model.rs @@ -429,6 +429,28 @@ mod tests { assert_eq!(results.get(3).unwrap().pattern, "url"); } + #[test] + fn match_emails() { + let buffer = + "Lorem ipsum john@server.department.company.com lorem"; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + + assert_eq!(results.len(), 2); + assert_eq!(results.get(0).unwrap().pattern, "email"); + assert_eq!( + results.get(0).unwrap().text, + "first.last+social@example.com" + ); + assert_eq!(results.get(1).unwrap().pattern, "email"); + assert_eq!( + results.get(1).unwrap().text, + "john@server.department.company.com" + ); + } + #[test] fn match_addresses() { let buffer = "Lorem 0xfd70b5695 0x5246ddf lorem\n Lorem 0x973113tlorem"; diff --git a/src/regexes.rs b/src/regexes.rs index cca6679..86fdbd0 100644 --- a/src/regexes.rs +++ b/src/regexes.rs @@ -3,12 +3,13 @@ use crate::error; pub const EXCLUDE_PATTERNS: [(&'static str, &'static str); 1] = [("ansi_colors", r"[[:cntrl:]]\[([0-9]{1,2};)?([0-9]{1,2})?m")]; -pub const PATTERNS: [(&'static str, &'static str); 14] = [ +pub const PATTERNS: [(&'static str, &'static str); 15] = [ ("markdown_url", r"\[[^]]*\]\(([^)]+)\)"), ( "url", r"((https?://|git@|git://|ssh://|ftp://|file:///)[^ \(\)\[\]\{\}]+)", ), + ("email", r"\b[A-z0-9._%+-]+@[A-z0-9.-]+\.[A-z]{2,}\b"), ("diff_a", r"--- a/([^ ]+)"), ("diff_b", r"\+\+\+ b/([^ ]+)"), ("docker", r"sha256:([0-9a-f]{64})"), From c57e78bb66e1b297234f1305d5b39d4273326b8b Mon Sep 17 00:00:00 2001 From: graelo Date: Thu, 4 Jun 2020 07:18:30 +0200 Subject: [PATCH 05/50] refactor: kebab-style regex pattern names --- src/model.rs | 9 +++++++-- src/regexes.rs | 8 ++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/model.rs b/src/model.rs index 4c40072..a361327 100644 --- a/src/model.rs +++ b/src/model.rs @@ -401,9 +401,9 @@ mod tests { let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); assert_eq!(results.len(), 2); - assert_eq!(results.get(0).unwrap().pattern, "markdown_url"); + assert_eq!(results.get(0).unwrap().pattern, "markdown-url"); assert_eq!(results.get(0).unwrap().text, "https://github.io?foo=bar"); - assert_eq!(results.get(1).unwrap().pattern, "markdown_url"); + assert_eq!(results.get(1).unwrap().pattern, "markdown-url"); assert_eq!(results.get(1).unwrap().text, "http://cdn.com/img.jpg"); } @@ -460,8 +460,11 @@ mod tests { let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); assert_eq!(results.len(), 3); + assert_eq!(results.get(0).unwrap().pattern, "mem-address"); assert_eq!(results.get(0).unwrap().text, "0xfd70b5695"); + assert_eq!(results.get(1).unwrap().pattern, "mem-address"); assert_eq!(results.get(1).unwrap().text, "0x5246ddf"); + assert_eq!(results.get(2).unwrap().pattern, "mem-address"); assert_eq!(results.get(2).unwrap().text, "0x973113"); } @@ -515,6 +518,7 @@ mod tests { let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); assert_eq!(results.len(), 1); + assert_eq!(results.get(0).unwrap().pattern, "diff-a"); assert_eq!(results.get(0).unwrap().text, "src/main.rs"); } @@ -527,6 +531,7 @@ mod tests { let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); assert_eq!(results.len(), 1); + assert_eq!(results.get(0).unwrap().pattern, "diff-b"); assert_eq!(results.get(0).unwrap().text, "src/main.rs"); } diff --git a/src/regexes.rs b/src/regexes.rs index 86fdbd0..f8f3f87 100644 --- a/src/regexes.rs +++ b/src/regexes.rs @@ -4,14 +4,14 @@ pub const EXCLUDE_PATTERNS: [(&'static str, &'static str); 1] = [("ansi_colors", r"[[:cntrl:]]\[([0-9]{1,2};)?([0-9]{1,2})?m")]; pub const PATTERNS: [(&'static str, &'static str); 15] = [ - ("markdown_url", r"\[[^]]*\]\(([^)]+)\)"), + ("markdown-url", r"\[[^]]*\]\(([^)]+)\)"), ( "url", r"((https?://|git@|git://|ssh://|ftp://|file:///)[^ \(\)\[\]\{\}]+)", ), ("email", r"\b[A-z0-9._%+-]+@[A-z0-9.-]+\.[A-z]{2,}\b"), - ("diff_a", r"--- a/([^ ]+)"), - ("diff_b", r"\+\+\+ b/([^ ]+)"), + ("diff-a", r"--- a/([^ ]+)"), + ("diff-b", r"\+\+\+ b/([^ ]+)"), ("docker", r"sha256:([0-9a-f]{64})"), ("path", r"(([.\w\-@~]+)?(/[.\w\-@]+)+)"), ("hexcolor", r"#[0-9a-fA-F]{6}"), @@ -23,7 +23,7 @@ pub const PATTERNS: [(&'static str, &'static str); 15] = [ ("sha", r"[0-9a-f]{7,40}"), ("ip", r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"), ("ipv6", r"[A-f0-9:]+:+[A-f0-9:]+[%\w\d]+"), - ("address", r"0x[0-9a-fA-F]+"), + ("mem-address", r"0x[0-9a-fA-F]+"), ("number", r"[0-9]{4,}"), ]; From 75054d1200c373f98bdca1150ca2115ff80576ef Mon Sep 17 00:00:00 2001 From: graelo Date: Thu, 4 Jun 2020 07:30:10 +0200 Subject: [PATCH 06/50] refactor: pattern ip -> ipv4 --- src/model.rs | 5 ++++- src/regexes.rs | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/model.rs b/src/model.rs index a361327..7297ae6 100644 --- a/src/model.rs +++ b/src/model.rs @@ -360,7 +360,7 @@ mod tests { } #[test] - fn match_ips() { + fn match_ipv4s() { let buffer = "Lorem ipsum 127.0.0.1 lorem\n Lorem 255.255.10.255 lorem 127.0.0.1 lorem"; let named_pat = vec![]; let custom = vec![]; @@ -368,8 +368,11 @@ mod tests { let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); assert_eq!(results.len(), 3); + assert_eq!(results.get(0).unwrap().pattern, "ipv4"); assert_eq!(results.get(0).unwrap().text, "127.0.0.1"); + assert_eq!(results.get(1).unwrap().pattern, "ipv4"); assert_eq!(results.get(1).unwrap().text, "255.255.10.255"); + assert_eq!(results.get(2).unwrap().pattern, "ipv4"); assert_eq!(results.get(2).unwrap().text, "127.0.0.1"); } diff --git a/src/regexes.rs b/src/regexes.rs index f8f3f87..9bb81fa 100644 --- a/src/regexes.rs +++ b/src/regexes.rs @@ -3,6 +3,10 @@ use crate::error; pub const EXCLUDE_PATTERNS: [(&'static str, &'static str); 1] = [("ansi_colors", r"[[:cntrl:]]\[([0-9]{1,2};)?([0-9]{1,2})?m")]; +/// Holds all the regex patterns that are currently supported. +/// +/// The email address was obtained at https://www.regular-expressions.info/email.html. +/// Others were obtained from Ferran Basora. pub const PATTERNS: [(&'static str, &'static str); 15] = [ ("markdown-url", r"\[[^]]*\]\(([^)]+)\)"), ( @@ -21,7 +25,7 @@ pub const PATTERNS: [(&'static str, &'static str); 15] = [ ), ("ipfs", r"Qm[0-9a-zA-Z]{44}"), ("sha", r"[0-9a-f]{7,40}"), - ("ip", r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"), + ("ipv4", r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"), ("ipv6", r"[A-f0-9:]+:+[A-f0-9:]+[%\w\d]+"), ("mem-address", r"0x[0-9a-fA-F]+"), ("number", r"[0-9]{4,}"), From 0622ab7bf68416ccd7e559ca1b1507f0b32628b7 Mon Sep 17 00:00:00 2001 From: graelo Date: Thu, 4 Jun 2020 08:03:59 +0200 Subject: [PATCH 07/50] chore(docs): add doc to feat: copyrat.tmux --- copyrat.tmux | 66 +++++++++++++++++++++++++++++++++++--------------- src/regexes.rs | 2 +- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/copyrat.tmux b/copyrat.tmux index 45a6bf7..d6b3f1b 100755 --- a/copyrat.tmux +++ b/copyrat.tmux @@ -1,7 +1,28 @@ #!/usr/bin/env bash -# This script is run only once at tmux launch. -# It provides configuration based on the @copyrat-* options set in your `tmux.conf`. +# This scripts provides a default configuration for tmux-copyrat options and key bindings. +# It is run only once at tmux launch. +# +# Each option and binding can be overridden in your `tmux.conf` by defining options like +# +# set -g @copyrat-keytable "foobar" +# set -g @copyrat-keyswitch "z" +# set -g @copyrat-match-bg "magenta" +# +# and bindings like +# +# bind-key -T foobar h new-window -d -n "[copyrat]" '/path/to/tmux-copyrat --window-name "[copyrat]" --pattern-name urls' +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# changing this will probably break integration - +# +# Just make sure you first open a named window in the background and provide that name to tmux-copyrat. +# +# Don't even try to run tmux-copyrat with run-shell, this cannot work because Tmux launches these processes +# without attaching them to a pty. + +# You could also entirely ignore this file (not even source it) and define all options and bindings +# in your `tmux.conf`. CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" BINARY="${CURRENT_DIR}/tmux-copyrat" @@ -18,22 +39,13 @@ setup_option() { # Sets the window name when copyrat is run, providing a default if @copyrat-window-name was not defined. setup_option "window-name" "[copyrat]" -# DEFAULT_COPYRAT_WINDOW_NAME="[copyrat]" -# COPYRAT_WINDOW_NAME=$(tmux show-option -gqv @copyrat-window-name) -# COPYRAT_WINDOW_NAME=${COPYRAT_WINDOW_NAME:-$DEFAULT_COPYRAT_WINDOW_NAME} -# # overrides with the same value, I did not bother writing a test -# tmux set-option -g @copyrat-window-name ${COPYRAT_WINDOW_NAME} - # Sets the keytable for all bindings, providing a default if @copyrat-keytable was not defined. # Keytables open a new shortcut space: if 't' is the switcher (see below), prefix + t + setup_option "keytable" "cpyrt" -# DEFAULT_COPYRAT_KEYTABLE="cpyrt" -# COPYRAT_KEYTABLE=$(tmux show-option -gqv @copyrat-keytable) -# COPYRAT_KEYTABLE=${COPYRAT_KEYTABLE:-$DEFAULT_COPYRAT_KEYTABLE} # Sets the key to access the keytable: prefix + + # providing a default if @copyrat-keyswitch is not defined. -setup_option "keyswitch" "u" +setup_option "keyswitch" "t" local keyswitch=$(tmux show-option @copyrat-keyswitch) local keytable=$(tmux show-option @copyrat-keytable) @@ -46,22 +58,36 @@ setup_binding() { tmux bind-key -T ${keytable} $key new-window -d -n ${window_name} "${BINARY} --window-name ${window_name} --reverse --unique" } -# Search +# prefix + t + Space searches for all known regexes (noisy and slower) setup_pattern_binding "space" "" +# prefix + t + p searches for absolute & relative paths setup_pattern_binding "p" "--pattern-name path" +# prefix + t + u searches for URLs setup_pattern_binding "u" "--pattern-name url" +# prefix + t + m searches for Markdown URLs [...](matched.url) +setup_pattern_binding "m" "--pattern-name markdown-url" +# prefix + t + h searches for SHA1/2 (hashes) setup_pattern_binding "h" "--pattern-name sha" -setup_pattern_binding "d" "--pattern-name docker" +# prefix + t + e searches for email addresses (see https://www.regular-expressions.info/email.html) +setup_pattern_binding "e" "--pattern-name email" +# prefix + t + D searches for docker shas +setup_pattern_binding "D" "--pattern-name docker" +# prefix + t + c searches for hex colors #aa00f5 setup_pattern_binding "c" "--pattern-name hexcolor" -setup_pattern_binding "i" "--pattern-name ip" - -# DEFAULT_COPYRAT_KEY="space" -# COPYRAT_KEY=$(tmux show-option -gqv @copyrat-key) -# COPYRAT_KEY=${COPYRAT_KEY:-$DEFAULT_COPYRAT_KEY} - +# prefix + t + U searches for UUIDs +setup_pattern_binding "U" "--pattern-name uuid" +# prefix + t + d searches for any string of 4+ digits +setup_pattern_binding "d" "--pattern-name digits" +# prefix + t + m searches for hex numbers: 0xbedead +setup_pattern_binding "m" "--pattern-name mem-address" +# prefix + t + 4 searches for IPV4 +setup_pattern_binding "4" "--pattern-name ipv4" +# prefix + t + 6 searches for IPV6 +setup_pattern_binding "6" "--pattern-name ipv6" # tmux bind-key -T ${keytable} "/" command-prompt "search:" new-window -d -n ${COPYRAT_WINDOW_NAME} "${BINARY} --window-name ${COPYRAT_WINDOW_NAME} --reverse --unique --custom-regex '%%'" +# Auto-install is currently disabled as it requires the user to have cargo installed. # if [ ! -f "$BINARY" ]; then # cd "${CURRENT_DIR}" && cargo build --release # fi diff --git a/src/regexes.rs b/src/regexes.rs index 9bb81fa..89001b0 100644 --- a/src/regexes.rs +++ b/src/regexes.rs @@ -28,7 +28,7 @@ pub const PATTERNS: [(&'static str, &'static str); 15] = [ ("ipv4", r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"), ("ipv6", r"[A-f0-9:]+:+[A-f0-9:]+[%\w\d]+"), ("mem-address", r"0x[0-9a-fA-F]+"), - ("number", r"[0-9]{4,}"), + ("digits", r"[0-9]{4,}"), ]; /// Type-safe string Pattern Name (newtype). From 11e97353973df0044b8b2587eba9160dce98bb50 Mon Sep 17 00:00:00 2001 From: graelo Date: Thu, 4 Jun 2020 09:45:58 +0200 Subject: [PATCH 08/50] feat: can use or ignore all patterns --- copyrat.tmux | 12 ++- src/lib.rs | 29 +++--- src/model.rs | 268 +++++++++++++++++++++++++++++++++++++++++++++------ src/ui.rs | 35 +++++-- 4 files changed, 291 insertions(+), 53 deletions(-) diff --git a/copyrat.tmux b/copyrat.tmux index d6b3f1b..08c5c1b 100755 --- a/copyrat.tmux +++ b/copyrat.tmux @@ -53,13 +53,11 @@ tmux bind-key ${keyswitch} -T ${keytable} setup_binding() { local key=$1 - local pattern_name=$2 + local pattern_name="$2" local window_name=$(tmux show-option -gqv @copyrat-window-name) - tmux bind-key -T ${keytable} $key new-window -d -n ${window_name} "${BINARY} --window-name ${window_name} --reverse --unique" + tmux bind-key -T ${keytable} $key new-window -d -n ${window_name} "${BINARY} --window-name ${window_name} --reverse --unique ${pattern_name}" } -# prefix + t + Space searches for all known regexes (noisy and slower) -setup_pattern_binding "space" "" # prefix + t + p searches for absolute & relative paths setup_pattern_binding "p" "--pattern-name path" # prefix + t + u searches for URLs @@ -84,8 +82,12 @@ setup_pattern_binding "m" "--pattern-name mem-address" setup_pattern_binding "4" "--pattern-name ipv4" # prefix + t + 6 searches for IPV6 setup_pattern_binding "6" "--pattern-name ipv6" +# prefix + t + Space searches for all known patterns (noisy and potentially slower) +setup_pattern_binding "space" "--all-patterns" + +# prefix + t + / prompts for a pattern and search for it +tmux bind-key -T ${keytable} "/" command-prompt -p "search:" 'new-window -d -n ${COPYRAT_WINDOW_NAME} "${BINARY} --window-name ${COPYRAT_WINDOW_NAME} --reverse --unique --custom-pattern %%"' -# tmux bind-key -T ${keytable} "/" command-prompt "search:" new-window -d -n ${COPYRAT_WINDOW_NAME} "${BINARY} --window-name ${COPYRAT_WINDOW_NAME} --reverse --unique --custom-regex '%%'" # Auto-install is currently disabled as it requires the user to have cargo installed. # if [ ! -f "$BINARY" ]; then diff --git a/src/lib.rs b/src/lib.rs index 742ba30..9faf269 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,8 +20,9 @@ pub fn run(buffer: String, opt: &CliOpt) -> Option<(String, bool)> { let mut model = model::Model::new( &buffer, &opt.alphabet, - &opt.named_pattern, - &opt.custom_regex, + opt.use_all_patterns, + &opt.named_patterns, + &opt.custom_patterns, opt.reverse, ); @@ -71,13 +72,17 @@ pub struct CliOpt { parse(try_from_str = alphabets::parse_alphabet))] alphabet: alphabets::Alphabet, - /// Pattern names to use (all if not specified). - #[clap(short = "x", long = "--pattern-name", parse(try_from_str = regexes::parse_pattern_name))] - named_pattern: Vec, + /// Use all available regex patterns. + #[clap(short = "A", long = "--all-patterns")] + use_all_patterns: bool, - /// Additional regex patterns. - #[clap(short = "X", long)] - custom_regex: Vec, + /// Pattern names to use ("email", ... see doc). + #[clap(short = "x", long = "--pattern-name", parse(try_from_str = regexes::parse_pattern_name))] + named_patterns: Vec, + + /// Additional regex patterns ("foo*bar", etc). + #[clap(short = "X", long = "--custom-pattern")] + custom_patterns: Vec, /// Assign hints starting from the bottom of the screen. #[clap(short, long)] @@ -91,7 +96,7 @@ pub struct CliOpt { colors: ui::UiColors, /// Align hint with its match. - #[clap(short = "a", long, arg_enum, default_value = "leading")] + #[clap(long, arg_enum, default_value = "leading")] hint_alignment: ui::HintAlignment, /// Move focus back to first/last match. @@ -166,8 +171,10 @@ impl CliOpt { "@copyrat-alphabet" => { self.alphabet = alphabets::parse_alphabet(value)?; } - "@copyrat-regex-id" => (), // TODO - "@copyrat-custom-regex" => self.custom_regex = vec![String::from(value)], + "@copyrat-pattern-name" => { + self.named_patterns = vec![regexes::parse_pattern_name(value)?] + } + "@copyrat-custom-pattern" => self.custom_patterns = vec![String::from(value)], "@copyrat-reverse" => { self.reverse = value.parse::()?; } diff --git a/src/model.rs b/src/model.rs index 7297ae6..8d4d283 100644 --- a/src/model.rs +++ b/src/model.rs @@ -11,8 +11,9 @@ pub struct Model<'a> { // buffer: &'a str, pub lines: Vec<&'a str>, alphabet: &'a Alphabet, + use_all_patterns: bool, named_patterns: &'a Vec, - custom_regexes: &'a Vec, + custom_patterns: &'a Vec, pub reverse: bool, } @@ -20,8 +21,9 @@ impl<'a> Model<'a> { pub fn new( buffer: &'a str, alphabet: &'a Alphabet, + use_all_patterns: bool, named_patterns: &'a Vec, - custom_regexes: &'a Vec, + custom_patterns: &'a Vec, reverse: bool, ) -> Model<'a> { let lines = buffer.split('\n').collect(); @@ -30,8 +32,9 @@ impl<'a> Model<'a> { // buffer, lines, alphabet, + use_all_patterns, named_patterns, - custom_regexes, + custom_patterns, reverse, } } @@ -73,7 +76,7 @@ impl<'a> Model<'a> { .collect::>(); let custom_regexes = self - .custom_regexes + .custom_patterns .iter() .map(|pattern| { ( @@ -83,7 +86,7 @@ impl<'a> Model<'a> { }) .collect::>(); - let regexes = if self.named_patterns.is_empty() { + let regexes = if self.use_all_patterns { PATTERNS .iter() .map(|&(name, pattern)| (name, Regex::new(pattern).unwrap())) @@ -252,10 +255,20 @@ mod tests { #[test] fn match_reverse() { let buffer = "lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 3); assert_eq!(results.first().unwrap().hint, "a"); @@ -265,10 +278,20 @@ mod tests { #[test] fn match_unique() { let buffer = "lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(true); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(true); assert_eq!(results.len(), 3); assert_eq!(results.first().unwrap().hint, "a"); @@ -278,10 +301,20 @@ mod tests { #[test] fn match_docker() { let buffer = "latest sha256:30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 1); assert_eq!( @@ -293,10 +326,20 @@ mod tests { #[test] fn match_ansi_colors() { let buffer = "path: /var/log/nginx.log\npath: test/log/nginx-2.log:32folder/.nginx@4df2.log"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = true; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().text, "/var/log/nginx.log"); @@ -307,10 +350,20 @@ mod tests { #[test] fn match_paths() { let buffer = "Lorem /tmp/foo/bar_lol, lorem\n Lorem /var/log/boot-strap.log lorem ../log/kern.log lorem"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().text, "/tmp/foo/bar_lol"); @@ -321,10 +374,20 @@ mod tests { #[test] fn match_home() { let buffer = "Lorem ~/.gnu/.config.txt, lorem"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 1); assert_eq!(results.get(0).unwrap().text, "~/.gnu/.config.txt"); @@ -333,10 +396,20 @@ mod tests { #[test] fn match_uuids() { let buffer = "Lorem ipsum 123e4567-e89b-12d3-a456-426655440000 lorem\n Lorem lorem lorem"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 1); } @@ -344,10 +417,20 @@ mod tests { #[test] fn match_shas() { let buffer = "Lorem fd70b5695 5246ddf f924213 lorem\n Lorem 973113963b491874ab2e372ee60d4b4cb75f717c lorem"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text, "fd70b5695"); @@ -362,10 +445,20 @@ mod tests { #[test] fn match_ipv4s() { let buffer = "Lorem ipsum 127.0.0.1 lorem\n Lorem 255.255.10.255 lorem 127.0.0.1 lorem"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().pattern, "ipv4"); @@ -379,10 +472,20 @@ mod tests { #[test] fn match_ipv6s() { let buffer = "Lorem ipsum fe80::2:202:fe4 lorem\n Lorem 2001:67c:670:202:7ba8:5e41:1591:d723 lorem fe80::2:1 lorem ipsum fe80:22:312:fe::1%eth0"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text, "fe80::2:202:fe4"); @@ -398,10 +501,20 @@ mod tests { fn match_markdown_urls() { let buffer = "Lorem ipsum [link](https://github.io?foo=bar) ![](http://cdn.com/img.jpg) lorem"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 2); assert_eq!(results.get(0).unwrap().pattern, "markdown-url"); @@ -413,10 +526,20 @@ mod tests { #[test] fn match_urls() { let buffer = "Lorem ipsum https://www.rust-lang.org/tools lorem\n Lorem ipsumhttps://crates.io lorem https://github.io?foo=bar lorem ssh://github.io"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 4); assert_eq!( @@ -436,10 +559,20 @@ mod tests { fn match_emails() { let buffer = "Lorem ipsum john@server.department.company.com lorem"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 2); assert_eq!(results.get(0).unwrap().pattern, "email"); @@ -457,10 +590,20 @@ mod tests { #[test] fn match_addresses() { let buffer = "Lorem 0xfd70b5695 0x5246ddf lorem\n Lorem 0x973113tlorem"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().pattern, "mem-address"); @@ -474,10 +617,20 @@ mod tests { #[test] fn match_hex_colors() { let buffer = "Lorem #fd7b56 lorem #FF00FF\n Lorem #00fF05 lorem #abcd00 lorem #afRR00"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text, "#fd7b56"); @@ -489,10 +642,20 @@ mod tests { #[test] fn match_ipfs() { let buffer = "Lorem QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ lorem Qmfoobar"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 1); assert_eq!( @@ -504,10 +667,20 @@ mod tests { #[test] fn match_process_port() { let buffer = "Lorem 5695 52463 lorem\n Lorem 973113 lorem 99999 lorem 8888 lorem\n 23456 lorem 5432 lorem 23444"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 8); } @@ -515,10 +688,20 @@ mod tests { #[test] fn match_diff_a() { let buffer = "Lorem lorem\n--- a/src/main.rs"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 1); assert_eq!(results.get(0).unwrap().pattern, "diff-a"); @@ -528,10 +711,20 @@ mod tests { #[test] fn match_diff_b() { let buffer = "Lorem lorem\n+++ b/src/main.rs"; + let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 1); assert_eq!(results.get(0).unwrap().pattern, "diff-b"); @@ -539,16 +732,25 @@ mod tests { } #[test] - fn priority() { + fn priority_between_regexes() { let buffer = "Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem"; - + let use_all_patterns = true; let named_pat = vec![]; let custom: Vec = ["CUSTOM-[0-9]{4,}", "ISSUE-[0-9]{3}"] .iter() .map(|&s| s.to_string()) .collect(); let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 9); assert_eq!(results.get(0).unwrap().text, "http://foo.bar"); @@ -572,12 +774,22 @@ mod tests { fn named_patterns() { let buffer = "Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem"; + let use_all_patterns = false; use crate::regexes::parse_pattern_name; let named_pat = vec![parse_pattern_name("url").unwrap()]; let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); - let results = Model::new(buffer, &alphabet, &named_pat, &custom, false).matches(false); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); assert_eq!(results.len(), 2); assert_eq!(results.get(0).unwrap().text, "http://foo.bar"); diff --git a/src/ui.rs b/src/ui.rs index 681621f..dc402a9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -879,10 +879,19 @@ path: /usr/local/bin/cargo"; Barcelona https://en.wikipedia.org/wiki/Barcelona - "; + let use_all_patterns = true; let named_pat = vec![]; - let custom_regexes = vec![]; + let custom_patterns = vec![]; let alphabet = alphabets::Alphabet("abcd".to_string()); - let mut model = model::Model::new(content, &alphabet, &named_pat, &custom_regexes, false); + let reverse = false; + let mut model = model::Model::new( + content, + &alphabet, + use_all_patterns, + &named_pat, + &custom_patterns, + reverse, + ); let term_width: u16 = 80; let line_offsets = get_line_offsets(&model.lines, term_width); let rendering_colors = UiColors { @@ -944,11 +953,19 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; Barcelona https://en.wikipedia.org/wiki/Barcelona - "; + let use_all_patterns = true; let named_pat = vec![]; - let custom_regexes = vec![]; + let custom_patterns = vec![]; let alphabet = alphabets::Alphabet("abcd".to_string()); let reverse = true; - let mut model = model::Model::new(content, &alphabet, &named_pat, &custom_regexes, reverse); + let mut model = model::Model::new( + content, + &alphabet, + use_all_patterns, + &named_pat, + &custom_patterns, + reverse, + ); let unique_hint = false; let wrap_around = false; @@ -1071,11 +1088,11 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; } } -/// Holds color-related data, for clarity. -/// -/// - `focus_*` colors are used to render the currently focused matched text. -/// - `normal_*` colors are used to render other matched text. -/// - `hint_*` colors are used to render the hints. +// /// Holds color-related data, for clarity. +// /// +// /// - `focus_*` colors are used to render the currently focused matched text. +// /// - `normal_*` colors are used to render other matched text. +// /// - `hint_*` colors are used to render the hints. #[derive(Clap, Debug)] pub struct UiColors { /// Foreground color for base text. From 540eb6b71cfb96582c9497974251ec9c411c4a84 Mon Sep 17 00:00:00 2001 From: graelo Date: Thu, 4 Jun 2020 09:48:51 +0200 Subject: [PATCH 09/50] chore(docs): authors --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ab11bcb..27a9043 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "copyrat" version = "0.1.0" -authors = ["Ferran Basora ", "u0xy "] +authors = ["u0xy "] edition = "2018" description = "This is tmux-copycat on Rust steroids." repository = "https://github.com/fcsonline/tmux-thumbs" From 86edf20b3cf4a3e8f49d1fac1bbd8dd3c8d8b2e0 Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 13 Mar 2021 11:27:40 +0100 Subject: [PATCH 10/50] refactor: better feat: copyrat.tmux script --- copyrat.tmux | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/copyrat.tmux b/copyrat.tmux index 08c5c1b..eb2bb4c 100755 --- a/copyrat.tmux +++ b/copyrat.tmux @@ -14,15 +14,17 @@ # bind-key -T foobar h new-window -d -n "[copyrat]" '/path/to/tmux-copyrat --window-name "[copyrat]" --pattern-name urls' # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # -# changing this will probably break integration - +# changing this script may break integration with `tmux-copyrat`. # -# Just make sure you first open a named window in the background and provide that name to tmux-copyrat. -# -# Don't even try to run tmux-copyrat with run-shell, this cannot work because Tmux launches these processes -# without attaching them to a pty. -# You could also entirely ignore this file (not even source it) and define all options and bindings -# in your `tmux.conf`. +# Just make sure you first open a named window in the background and provide +# that name to the binary `tmux-copyrat`. +# +# Don't even try to run tmux-copyrat with run-shell, this cannot work because +# Tmux launches these processes without attaching them to a pty. + +# You can also entirely ignore this file (not even source it) and define all +# options and bindings in your `tmux.conf`. CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" BINARY="${CURRENT_DIR}/tmux-copyrat" @@ -36,7 +38,8 @@ setup_option() { } -# Sets the window name when copyrat is run, providing a default if @copyrat-window-name was not defined. +# Sets the window name when copyrat is run, providing a default in case +# @copyrat-window-name was not defined. setup_option "window-name" "[copyrat]" # Sets the keytable for all bindings, providing a default if @copyrat-keytable was not defined. From 9b186b50deb788f6b9ea7f87fdb21cbf2809a28d Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 13 Mar 2021 11:37:20 +0100 Subject: [PATCH 11/50] chore(deps): adopt clap-3.0.0-beta.2 --- Cargo.lock | 276 +++++++++++++++++++++-------------------------------- Cargo.toml | 6 +- src/lib.rs | 12 +-- 3 files changed, 118 insertions(+), 176 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51c7756..b9462bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,387 +4,329 @@ name = "aho-corasick" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", + "winapi", ] [[package]] name = "autocfg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "clap" -version = "3.0.0-beta.1" +version = "3.0.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" dependencies = [ - "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "clap_derive 3.0.0-beta.1 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "os_str_bytes 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "term_size 1.0.0-beta1 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "terminal_size", + "textwrap", + "unicode-width", + "vec_map", ] [[package]] name = "clap_derive" -version = "3.0.0-beta.1" +version = "3.0.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" dependencies = [ - "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-error 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "copyrat" version = "0.1.0" dependencies = [ - "clap 3.0.0-beta.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "sequence_trie 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "clap", + "regex", + "sequence_trie", + "termion", ] [[package]] name = "heck" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" dependencies = [ - "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation", ] [[package]] name = "hermit-abi" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" dependencies = [ - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "indexmap" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" [[package]] name = "memchr" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "numtoa" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "os_str_bytes" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06de47b848347d8c4c94219ad8ecd35eb90231704b067e67e6ae2e36ee023510" [[package]] name = "proc-macro-error" -version = "0.4.12" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ - "proc-macro-error-attr 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", ] [[package]] name = "proc-macro-error-attr" -version = "0.4.12" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", - "syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "version_check", ] [[package]] name = "proc-macro2" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de40dd4ff82d9c9bab6dae29dbab1167e515f8df9ed17d2987cb6012db206933" dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid", ] [[package]] name = "quote" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" dependencies = [ - "proc-macro2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "redox_termios" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" dependencies = [ - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall", ] [[package]] name = "regex" -version = "1.3.7" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54fd1046a3107eb58f42de31d656fee6853e5d276c455fd943742dce89fc3dd3" dependencies = [ - "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.17" +version = "0.6.23" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" [[package]] name = "sequence_trie" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee22067b7ccd072eeb64454b9c6e1b33b61cd0d49e895fd48676a184580e0c3" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" dependencies = [ - "proc-macro2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "syn-mid" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "term_size" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "term_size" -version = "1.0.0-beta1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] name = "termcolor" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" dependencies = [ - "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406" +dependencies = [ + "libc", + "winapi", ] [[package]] name = "termion" version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905" dependencies = [ - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "numtoa", + "redox_syscall", + "redox_termios", ] [[package]] name = "textwrap" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" dependencies = [ - "term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "thread_local" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "terminal_size", + "unicode-width", ] [[package]] name = "unicode-segmentation" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" [[package]] name = "unicode-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" [[package]] name = "version_check" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" -"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum clap 3.0.0-beta.1 (registry+https://github.com/rust-lang/crates.io-index)" = "860643c53f980f0d38a5e25dfab6c3c93b2cb3aa1fe192643d17a293c6c41936" -"checksum clap_derive 3.0.0-beta.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fb51c9e75b94452505acd21d929323f5a5c6c4735a852adbd39ef5fb1b014f30" -"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -"checksum hermit-abi 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" -"checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)" = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" -"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" -"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" -"checksum os_str_bytes 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "06de47b848347d8c4c94219ad8ecd35eb90231704b067e67e6ae2e36ee023510" -"checksum proc-macro-error 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" -"checksum proc-macro-error-attr 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" -"checksum proc-macro2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "de40dd4ff82d9c9bab6dae29dbab1167e515f8df9ed17d2987cb6012db206933" -"checksum quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" -"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -"checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" -"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" -"checksum sequence_trie 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1ee22067b7ccd072eeb64454b9c6e1b33b61cd0d49e895fd48676a184580e0c3" -"checksum strsim 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -"checksum syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" -"checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" -"checksum term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" -"checksum term_size 1.0.0-beta1 (registry+https://github.com/rust-lang/crates.io-index)" = "a8a17d8699e154863becdf18e4fd28bd0be27ca72856f54daf75c00f2566898f" -"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" -"checksum termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" -"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" -"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 27a9043..61b4148 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,14 +4,14 @@ version = "0.1.0" authors = ["u0xy "] edition = "2018" description = "This is tmux-copycat on Rust steroids." -repository = "https://github.com/fcsonline/tmux-thumbs" +repository = "https://github.com/graelo/tmux-copyrat" keywords = ["rust", "tmux", "tmux-plugin", "tmux-copycat"] license = "MIT" [dependencies] termion = "1.5" -regex = "1.3.1" -clap = { version = "3.0.0-beta.1", features = ["suggestions", "color", "wrap_help", "term_size"]} +regex = "1.4" +clap = { version = "3.0.0-beta.2", features = ["suggestions", "color", "wrap_help"]} sequence_trie = "0.3.6" [[bin]] diff --git a/src/lib.rs b/src/lib.rs index 9faf269..6ed2448 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,20 +68,20 @@ pub struct CliOpt { /// # Examples /// /// "qwerty", "dvorak-homerow", "azerty-right-hand". - #[clap(short = "k", long, default_value = "dvorak", + #[clap(short = 'k', long, default_value = "dvorak", parse(try_from_str = alphabets::parse_alphabet))] alphabet: alphabets::Alphabet, /// Use all available regex patterns. - #[clap(short = "A", long = "--all-patterns")] + #[clap(short = 'A', long = "--all-patterns")] use_all_patterns: bool, /// Pattern names to use ("email", ... see doc). - #[clap(short = "x", long = "--pattern-name", parse(try_from_str = regexes::parse_pattern_name))] + #[clap(short = 'x', long = "--pattern-name", parse(try_from_str = regexes::parse_pattern_name))] named_patterns: Vec, /// Additional regex patterns ("foo*bar", etc). - #[clap(short = "X", long = "--custom-pattern")] + #[clap(short = 'X', long = "--custom-pattern")] custom_patterns: Vec, /// Assign hints starting from the bottom of the screen. @@ -107,7 +107,7 @@ pub struct CliOpt { /// /// Underline or surround the hint for increased visibility. /// If not provided, only the hint colors will be used. - #[clap(short = "s", long, arg_enum)] + #[clap(short = 's', long, arg_enum)] hint_style: Option, /// Chars surrounding each hint, used with `Surround` style. @@ -116,7 +116,7 @@ pub struct CliOpt { hint_surroundings: (char, char), /// Optional target path where to store the selected matches. - #[clap(short = "o", long = "output", parse(from_os_str))] + #[clap(short = 'o', long = "output", parse(from_os_str))] pub target_path: Option, /// Describes if the uppercased marker should be added to the output, From 7e28931f6c8b6532be97bf4ed108a8bbdf242d91 Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 13 Mar 2021 17:34:05 +0100 Subject: [PATCH 12/50] chore(deps): cargo update --- Cargo.lock | 96 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9462bb..b1694b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "aho-corasick" -version = "0.7.10" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" dependencies = [ "memchr", ] @@ -22,9 +22,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" @@ -76,30 +76,37 @@ dependencies = [ ] [[package]] -name = "heck" -version = "0.3.1" +name = "hashbrown" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + +[[package]] +name = "heck" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" dependencies = [ "unicode-segmentation", ] [[package]] name = "hermit-abi" -version = "0.1.12" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ "libc", ] [[package]] name = "indexmap" -version = "1.3.2" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ "autocfg", + "hashbrown", ] [[package]] @@ -110,15 +117,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.69" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" +checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a" [[package]] name = "memchr" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "numtoa" @@ -128,9 +135,9 @@ checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "os_str_bytes" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06de47b848347d8c4c94219ad8ecd35eb90231704b067e67e6ae2e36ee023510" +checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" [[package]] name = "proc-macro-error" @@ -158,33 +165,36 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.14" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de40dd4ff82d9c9bab6dae29dbab1167e515f8df9ed17d2987cb6012db206933" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.1.56" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +dependencies = [ + "bitflags", +] [[package]] name = "redox_termios" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" dependencies = [ "redox_syscall", ] @@ -220,9 +230,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.23" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" +checksum = "8fd9bc7ccc2688b3344c2f48b9b546648b25ce0b20fc717ee7fa7981a8ca9717" dependencies = [ "proc-macro2", "quote", @@ -231,9 +241,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" dependencies = [ "winapi-util", ] @@ -250,9 +260,9 @@ dependencies = [ [[package]] name = "termion" -version = "1.5.5" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905" +checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ "libc", "numtoa", @@ -272,39 +282,39 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" [[package]] name = "unicode-width" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "vec_map" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", From ffd8e9b12d9e12a51c5858259d1fd07a29568620 Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 13 Mar 2021 18:51:31 +0100 Subject: [PATCH 13/50] refactor: follow clippy suggestions --- src/alphabets.rs | 4 ++-- src/bridge.rs | 51 ++++++++++++------------------------------------ src/main.rs | 4 ++-- src/model.rs | 20 +++++++++---------- src/process.rs | 2 +- src/regexes.rs | 4 ++-- src/ui.rs | 22 +++++++++------------ 7 files changed, 38 insertions(+), 69 deletions(-) diff --git a/src/alphabets.rs b/src/alphabets.rs index e1e7aaa..aa2d6ad 100644 --- a/src/alphabets.rs +++ b/src/alphabets.rs @@ -6,7 +6,7 @@ use crate::error; /// /// Keep in mind letters 'n' and 'y' are systematically removed at runtime to /// prevent conflict with navigation and yank/copy keys. -const ALPHABETS: [(&'static str, &'static str); 21] = [ +const ALPHABETS: [(&str, &str); 21] = [ // ("abcd", "abcd"), ("qwerty", "asdfqwerzxcvjklmiuopghtybn"), ("qwerty-homerow", "asdfjklgh"), @@ -47,7 +47,7 @@ pub fn parse_alphabet(src: &str) -> Result { match alphabet_pair { Some((_name, letters)) => { let letters = letters.replace(&['n', 'N', 'y', 'Y'][..], ""); - Ok(Alphabet(letters.to_string())) + Ok(Alphabet(letters)) } None => Err(error::ParseError::UnknownAlphabet), } diff --git a/src/bridge.rs b/src/bridge.rs index 45e4304..4cbd500 100644 --- a/src/bridge.rs +++ b/src/bridge.rs @@ -44,11 +44,8 @@ impl BridgeOpt { options: &HashMap, ) -> Result<(), error::ParseError> { for (name, value) in options { - match name.as_ref() { - "@copyrat-capture" => { - self.capture_region = tmux::CaptureRegion::from_str(&value)?; - } - _ => (), + if let "@copyrat-capture" = name.as_ref() { + self.capture_region = tmux::CaptureRegion::from_str(&value)?; } } @@ -94,41 +91,17 @@ fn main() -> Result<(), error::ParseError> { // buffer if it was uppercased. // TODO: consider getting rid of multi-selection mode. - // Execute a command on each group of selections (normal and uppercased). - let (normal_selections, uppercased_selections): (Vec<(String, bool)>, Vec<(String, bool)>) = - selections - .into_iter() - .partition(|(_text, uppercased)| !*uppercased); + match selections { + None => return Ok(()), + Some((text, uppercased)) => { + let args = vec!["set-buffer", &text]; + process::execute("tmux", &args)?; - let buffer_selections: String = normal_selections - .into_iter() - .map(|(text, _)| text) - .collect::>() - .join("\n"); - - if buffer_selections.len() > 0 { - let args = vec!["set-buffer", &buffer_selections]; - // Simply execute the command as is, and let the program crash on - // potential errors because it is not our responsibility. - process::execute("tmux", &args).unwrap(); - } - - let buffer_selections: String = uppercased_selections - .into_iter() - .map(|(text, _)| text) - .collect::>() - .join("\n"); - - if buffer_selections.len() > 0 { - let args = vec!["set-buffer", &buffer_selections]; - // Simply execute the command as is, and let the program crash on - // potential errors because it is not our responsibility. - process::execute("tmux", &args).unwrap(); - - let args = vec!["paste-buffer", "-t", active_pane.id.as_str()]; - // Simply execute the command as is, and let the program crash on - // potential errors because it is not our responsibility. - process::execute("tmux", &args).unwrap(); + if uppercased { + let args = vec!["paste-buffer", "-t", active_pane.id.as_str()]; + process::execute("tmux", &args)?; + } + } } Ok(()) diff --git a/src/main.rs b/src/main.rs index 2cee8da..ad08248 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ fn main() { handle.read_to_string(&mut buffer).unwrap(); // Execute copyrat over the buffer (will take control over stdout). - // This returns the selected matches. + // This returns the selected matche. let selection: Option<(String, bool)> = run(buffer, &opt); // Early exit, signaling no selections were found. @@ -37,7 +37,7 @@ fn main() { .open(target) .expect("Unable to open the target file"); - file.write(text.as_bytes()).unwrap(); + file.write_all(text.as_bytes()).unwrap(); } } } diff --git a/src/model.rs b/src/model.rs index 8d4d283..8338370 100644 --- a/src/model.rs +++ b/src/model.rs @@ -12,8 +12,8 @@ pub struct Model<'a> { pub lines: Vec<&'a str>, alphabet: &'a Alphabet, use_all_patterns: bool, - named_patterns: &'a Vec, - custom_patterns: &'a Vec, + named_patterns: &'a [NamedPattern], + custom_patterns: &'a [String], pub reverse: bool, } @@ -22,8 +22,8 @@ impl<'a> Model<'a> { buffer: &'a str, alphabet: &'a Alphabet, use_all_patterns: bool, - named_patterns: &'a Vec, - custom_patterns: &'a Vec, + named_patterns: &'a [NamedPattern], + custom_patterns: &'a [String], reverse: bool, ) -> Model<'a> { let lines = buffer.split('\n').collect(); @@ -112,7 +112,7 @@ impl<'a> Model<'a> { loop { let chunk_matches = all_regexes .iter() - .filter_map(|(&ref name, regex)| match regex.find_iter(chunk).nth(0) { + .filter_map(|(&ref name, regex)| match regex.find_iter(chunk).next() { Some(m) => Some((name, regex, m)), None => None, }) @@ -164,7 +164,7 @@ impl<'a> Model<'a> { /// If `unique` is `true`, all duplicate matches will have the same hint. /// For copying matched text, this seems easier and more natural. /// If `unique` is `false`, duplicate matches will have their own hint. - fn associate_hints(&self, raw_matches: &Vec>, unique: bool) -> Vec> { + fn associate_hints(&self, raw_matches: &[RawMatch<'a>], unique: bool) -> Vec> { let hints = self.alphabet.make_hints(raw_matches.len()); let mut hints_iter = hints.iter(); @@ -175,11 +175,11 @@ impl<'a> Model<'a> { let mut known: collections::HashMap<&str, &str> = collections::HashMap::new(); for raw_mat in raw_matches { - let hint: &str = known.entry(raw_mat.text).or_insert( + let hint: &str = known.entry(raw_mat.text).or_insert_with(|| { hints_iter .next() - .expect("We should have as many hints as necessary, even invisible ones."), - ); + .expect("We should have as many hints as necessary, even invisible ones.") + }); result.push(Match { x: raw_mat.x, @@ -211,7 +211,7 @@ impl<'a> Model<'a> { /// Builds a `SequenceTrie` that helps determine if a sequence of keys /// entered by the user corresponds to a match. This kind of lookup /// directly returns a reference to the corresponding `Match` if any. - pub fn build_lookup_trie(matches: &'a Vec>) -> SequenceTrie { + pub fn build_lookup_trie(matches: &'a [Match<'a>]) -> SequenceTrie { let mut trie = SequenceTrie::new(); for (index, mat) in matches.iter().enumerate() { diff --git a/src/process.rs b/src/process.rs index af6a5f1..5ef5825 100644 --- a/src/process.rs +++ b/src/process.rs @@ -4,7 +4,7 @@ use crate::error::ParseError; /// Execute an arbitrary Unix command and return the stdout as a `String` if /// successful. -pub fn execute(command: &str, args: &Vec<&str>) -> Result { +pub fn execute(command: &str, args: &[&str]) -> Result { let output = Command::new(command).args(args).output()?; if !output.status.success() { diff --git a/src/regexes.rs b/src/regexes.rs index 89001b0..4c0fd4d 100644 --- a/src/regexes.rs +++ b/src/regexes.rs @@ -1,13 +1,13 @@ use crate::error; -pub const EXCLUDE_PATTERNS: [(&'static str, &'static str); 1] = +pub const EXCLUDE_PATTERNS: [(&str, &str); 1] = [("ansi_colors", r"[[:cntrl:]]\[([0-9]{1,2};)?([0-9]{1,2})?m")]; /// Holds all the regex patterns that are currently supported. /// /// The email address was obtained at https://www.regular-expressions.info/email.html. /// Others were obtained from Ferran Basora. -pub const PATTERNS: [(&'static str, &'static str); 15] = [ +pub const PATTERNS: [(&str, &str); 15] = [ ("markdown-url", r"\[[^]]*\]\(([^)]+)\)"), ( "url", diff --git a/src/ui.rs b/src/ui.rs index dc402a9..06d3db1 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -82,10 +82,8 @@ impl<'a> Ui<'a> { } else { self.focus_index -= 1; } - } else { - if self.focus_index > 0 { - self.focus_index -= 1; - } + } else if self.focus_index > 0 { + self.focus_index -= 1; } let new_index = self.focus_index; (old_index, new_index) @@ -101,10 +99,8 @@ impl<'a> Ui<'a> { } else { self.focus_index += 1; } - } else { - if self.focus_index < self.matches.len() - 1 { - self.focus_index += 1; - } + } else if self.focus_index < self.matches.len() - 1 { + self.focus_index += 1; } let new_index = self.focus_index; (old_index, new_index) @@ -137,10 +133,10 @@ impl<'a> Ui<'a> { /// - This writes directly on the writer, avoiding extra allocation. fn render_base_text( stdout: &mut dyn io::Write, - lines: &Vec<&str>, - line_offsets: &Vec, + lines: &[&str], + line_offsets: &[usize], colors: &UiColors, - ) -> () { + ) { write!( stdout, "{bg_color}{fg_color}", @@ -361,7 +357,7 @@ impl<'a> Ui<'a> { /// # Note /// Multibyte characters are taken into account, so that the Match's `text` /// and `hint` are rendered in their proper position. - fn full_render(&self, stdout: &mut dyn io::Write) -> () { + fn full_render(&self, stdout: &mut dyn io::Write) { // 1. Trim all lines and render non-empty ones. Ui::render_base_text( stdout, @@ -557,7 +553,7 @@ impl<'a> Ui<'a> { /// Compute each line's actual y offset if displayed in a terminal of width /// `term_width`. -fn get_line_offsets(lines: &Vec<&str>, term_width: u16) -> Vec { +fn get_line_offsets(lines: &[&str], term_width: u16) -> Vec { lines .iter() .scan(0, |offset, &line| { From 8e7b1a2d051407f3fafb9056fcefcf338238276a Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 14 Mar 2021 12:15:36 +0100 Subject: [PATCH 14/50] fix(tmux): fix feat: copyrat.tmux --- copyrat.tmux | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/copyrat.tmux b/copyrat.tmux index eb2bb4c..c3ee0bd 100755 --- a/copyrat.tmux +++ b/copyrat.tmux @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env zsh # This scripts provides a default configuration for tmux-copyrat options and key bindings. # It is run only once at tmux launch. @@ -26,9 +26,14 @@ # You can also entirely ignore this file (not even source it) and define all # options and bindings in your `tmux.conf`. -CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +CURRENT_DIR="$( cd "$( dirname "$0" )" && pwd )" BINARY="${CURRENT_DIR}/tmux-copyrat" + +# +# Top-level options +# + setup_option() { local opt_name=$1 local default_value=$2 @@ -38,10 +43,13 @@ setup_option() { } -# Sets the window name when copyrat is run, providing a default in case -# @copyrat-window-name was not defined. +# Sets the window name which copyrat should use when running, providing a +# default value in case @copyrat-window-name was not defined. setup_option "window-name" "[copyrat]" +# Get that window name as a local variable for use in pattern bindings below. +window_name=$(tmux show-option -gqv @copyrat-window-name) + # Sets the keytable for all bindings, providing a default if @copyrat-keytable was not defined. # Keytables open a new shortcut space: if 't' is the switcher (see below), prefix + t + setup_option "keytable" "cpyrt" @@ -50,15 +58,21 @@ setup_option "keytable" "cpyrt" # providing a default if @copyrat-keyswitch is not defined. setup_option "keyswitch" "t" -local keyswitch=$(tmux show-option @copyrat-keyswitch) -local keytable=$(tmux show-option @copyrat-keytable) -tmux bind-key ${keyswitch} -T ${keytable} +keyswitch=$(tmux show-option -gv @copyrat-keyswitch) +keytable=$(tmux show-option -gv @copyrat-keytable) +tmux bind-key ${keyswitch} switch-client -T ${keytable} -setup_binding() { + +# +# Pattern bindings +# + +setup_pattern_binding() { local key=$1 - local pattern_name="$2" - local window_name=$(tmux show-option -gqv @copyrat-window-name) - tmux bind-key -T ${keytable} $key new-window -d -n ${window_name} "${BINARY} --window-name ${window_name} --reverse --unique ${pattern_name}" + local pattern_arg="$2" + # The default window name `[copyrat]` has to be single quoted because it is + # interpreted by the shell when launched by tmux. + tmux bind-key -T ${keytable} ${key} new-window -d -n ${window_name} "${BINARY} --window-name '"${window_name}"' --reverse --unique-hint ${pattern_arg}" } # prefix + t + p searches for absolute & relative paths @@ -89,7 +103,7 @@ setup_pattern_binding "6" "--pattern-name ipv6" setup_pattern_binding "space" "--all-patterns" # prefix + t + / prompts for a pattern and search for it -tmux bind-key -T ${keytable} "/" command-prompt -p "search:" 'new-window -d -n ${COPYRAT_WINDOW_NAME} "${BINARY} --window-name ${COPYRAT_WINDOW_NAME} --reverse --unique --custom-pattern %%"' +tmux bind-key -T ${keytable} "/" command-prompt -p "search:" "new-window -d -n '${window_name}' \"${BINARY}\" --window-name '${window_name}' --reverse --unique-hint --custom-pattern %%" # Auto-install is currently disabled as it requires the user to have cargo installed. From b5adef94e5a4effd7b9e0939c0c5a0d41c658fff Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 14 Mar 2021 20:53:50 +0100 Subject: [PATCH 15/50] feat: better uppercased management --- src/bridge.rs | 1 - src/ui.rs | 10 +++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/bridge.rs b/src/bridge.rs index 4cbd500..faebe5b 100644 --- a/src/bridge.rs +++ b/src/bridge.rs @@ -89,7 +89,6 @@ fn main() -> Result<(), error::ParseError> { // Finally copy selection to a tmux buffer, and paste it to the active // buffer if it was uppercased. - // TODO: consider getting rid of multi-selection mode. match selections { None => return Ok(()), diff --git a/src/ui.rs b/src/ui.rs index 06d3db1..1e35fbc 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -408,6 +408,7 @@ impl<'a> Ui<'a> { return Event::Exit; } + let mut uppercased = false; let mut typed_hint = String::new(); self.full_render(writer); @@ -468,7 +469,7 @@ impl<'a> Ui<'a> { } // Yank/copy - event::Key::Char(_ch @ 'y') => { + 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)); } @@ -477,13 +478,17 @@ impl<'a> Ui<'a> { return Event::Match((text.to_string(), true)); } - // TODO: use a Trie or another data structure to determine + // Use a Trie or another data structure to determine // if the entered key belongs to a longer hint. // Attempts at finding a match with a corresponding hint. + // + // If any of the typed character is caps, the typed hint is + // deemed as uppercased. event::Key::Char(ch) => { let key = ch.to_string(); let lower_key = key.to_lowercase(); + uppercased = uppercased || (key != lower_key); typed_hint.push_str(&lower_key); let node = self @@ -503,7 +508,6 @@ 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(); - let uppercased = key != lower_key; return Event::Match((text, uppercased)); } else { // The prefix of a hint was entered, but we From 849dd37b05a77b127e08c2bc04318b472a088138 Mon Sep 17 00:00:00 2001 From: graelo Date: Tue, 16 Mar 2021 09:03:51 +0100 Subject: [PATCH 16/50] fix: alphabets doc markup --- src/alphabets.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/alphabets.rs b/src/alphabets.rs index aa2d6ad..091583d 100644 --- a/src/alphabets.rs +++ b/src/alphabets.rs @@ -69,7 +69,7 @@ impl Alphabet { /// If more hints are needed, unfortunately, this will keep producing /// empty (`""`) hints. /// - /// ``` + /// ```text /// // The algorithm works as follows: /// // --- lead ---- /// // initial state | a b c d From 646ee8b9fb733a0d55856ebab6f23fb8d0902d2c Mon Sep 17 00:00:00 2001 From: graelo Date: Wed, 17 Mar 2021 07:55:24 +0100 Subject: [PATCH 17/50] feat: output to clipboard or tmux buffer --- Cargo.lock | 39 ++++++++++++++++++++++++ Cargo.toml | 3 +- src/lib.rs | 9 ++++-- src/main.rs | 6 ++-- src/output_destination.rs | 30 ++++++++++++++++++ src/selection.rs | 7 +++++ src/{bridge.rs => tmux_copyrat.rs} | 49 ++++++++++++++++++++++++------ src/ui.rs | 46 ++++++++++++++++++++++------ 8 files changed, 164 insertions(+), 25 deletions(-) create mode 100644 src/output_destination.rs create mode 100644 src/selection.rs rename src/{bridge.rs => tmux_copyrat.rs} (63%) diff --git a/Cargo.lock b/Cargo.lock index b1694b1..e4d74af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 61b4148..8020aab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/lib.rs b/src/lib.rs index 6ed2448..57527ac 100644 --- a/src/lib.rs +++ b/src/lib.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 { 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 = { 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, diff --git a/src/main.rs b/src/main.rs index ad08248..5f8eeb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 = 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 { diff --git a/src/output_destination.rs b/src/output_destination.rs new file mode 100644 index 0000000..e6fdbe9 --- /dev/null +++ b/src/output_destination.rs @@ -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"), + } + } +} diff --git a/src/selection.rs b/src/selection.rs new file mode 100644 index 0000000..bac4697 --- /dev/null +++ b/src/selection.rs @@ -0,0 +1,7 @@ +use crate::output_destination::OutputDestination; + +pub struct Selection { + pub text: String, + pub uppercased: bool, + pub output_destination: OutputDestination, +} diff --git a/src/bridge.rs b/src/tmux_copyrat.rs similarity index 63% rename from src/bridge.rs rename to src/tmux_copyrat.rs index faebe5b..3b05e08 100644 --- a/src/bridge.rs +++ b/src/tmux_copyrat.rs @@ -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()?; + } } } } diff --git a/src/ui.rs b/src/ui.rs index 1e35fbc..11ee285 100644 --- a/src/ui.rs +++ b/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, focus_index: usize, focus_wrap_around: bool, + default_output_destination: OutputDestination, rendering_colors: &'a UiColors, hint_alignment: &'a HintAlignment, hint_style: Option, @@ -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, @@ -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::>()); 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 { 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, From 0e0e581f86260fd6f9bfd64ee0fa919d77717f36 Mon Sep 17 00:00:00 2001 From: graelo Date: Wed, 17 Mar 2021 10:12:57 +0100 Subject: [PATCH 18/50] feat: match version numbers --- copyrat.tmux | 2 ++ src/output_destination.rs | 2 +- src/regexes.rs | 6 +++++- src/tmux_copyrat.rs | 19 ++++--------------- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/copyrat.tmux b/copyrat.tmux index c3ee0bd..3097848 100755 --- a/copyrat.tmux +++ b/copyrat.tmux @@ -91,6 +91,8 @@ setup_pattern_binding "D" "--pattern-name docker" setup_pattern_binding "c" "--pattern-name hexcolor" # prefix + t + U searches for UUIDs setup_pattern_binding "U" "--pattern-name uuid" +# prefix + t + v searches for version numbers +setup_pattern_binding "v" "--pattern-name version" # prefix + t + d searches for any string of 4+ digits setup_pattern_binding "d" "--pattern-name digits" # prefix + t + m searches for hex numbers: 0xbedead diff --git a/src/output_destination.rs b/src/output_destination.rs index e6fdbe9..4bbd858 100644 --- a/src/output_destination.rs +++ b/src/output_destination.rs @@ -23,7 +23,7 @@ impl OutputDestination { impl fmt::Display for OutputDestination { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Tmux => write!(f, "tmux"), + Self::Tmux => write!(f, "tmux buffer"), Self::Clipboard => write!(f, "clipboard"), } } diff --git a/src/regexes.rs b/src/regexes.rs index 4c0fd4d..aa90139 100644 --- a/src/regexes.rs +++ b/src/regexes.rs @@ -7,7 +7,7 @@ pub const EXCLUDE_PATTERNS: [(&str, &str); 1] = /// /// The email address was obtained at https://www.regular-expressions.info/email.html. /// Others were obtained from Ferran Basora. -pub const PATTERNS: [(&str, &str); 15] = [ +pub const PATTERNS: [(&str, &str); 16] = [ ("markdown-url", r"\[[^]]*\]\(([^)]+)\)"), ( "url", @@ -23,6 +23,10 @@ pub const PATTERNS: [(&str, &str); 15] = [ "uuid", r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", ), + ( + "version", + r"(v?\d{1,4}\.\d{1,4}(\.\d{1,4})?(-(alpha|beta|rc)(\.\d)?)?)[^.0-9s]", + ), ("ipfs", r"Qm[0-9a-zA-Z]{44}"), ("sha", r"[0-9a-f]{7,40}"), ("ipv4", r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"), diff --git a/src/tmux_copyrat.rs b/src/tmux_copyrat.rs index 3b05e08..fb9e908 100644 --- a/src/tmux_copyrat.rs +++ b/src/tmux_copyrat.rs @@ -33,8 +33,8 @@ struct BridgeOpt { /// Name of the copy-to-clipboard executable. /// - /// If the output destination is set to keyboard, copyrat will pipe the - /// selected text to this executable. + /// If during execution, the output destination is set to be clipboard, + /// then copyrat will pipe the selected text to this executable. #[clap(long, default_value = "pbcopy")] clipboard_exe: String, @@ -94,8 +94,8 @@ fn main() -> Result<(), error::ParseError> { 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. + // Finally copy selection to the output destination (tmux buffer or + // clipboard), and paste it to the active buffer if it was uppercased. match selection { None => return Ok(()), @@ -105,25 +105,14 @@ fn main() -> Result<(), error::ParseError> { output_destination, }) => { if uppercased { - // 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()?; From d2d3e812fc1d579607ffc24bc0c9aad860092ac8 Mon Sep 17 00:00:00 2001 From: graelo Date: Wed, 17 Mar 2021 22:13:13 +0100 Subject: [PATCH 19/50] refactor: refactor --- Cargo.toml | 4 ++-- src/{main.rs => bin/copyrat.rs} | 0 src/{ => bin}/tmux_copyrat.rs | 4 +--- src/lib.rs | 1 + src/tmux.rs | 6 +++--- 5 files changed, 7 insertions(+), 8 deletions(-) rename src/{main.rs => bin/copyrat.rs} (100%) rename src/{ => bin}/tmux_copyrat.rs (99%) diff --git a/Cargo.toml b/Cargo.toml index 8020aab..acfa05d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,8 @@ duct = "0.13" [[bin]] name = "copyrat" -path = "src/main.rs" +path = "src/bin/copyrat.rs" [[bin]] name = "tmux-copyrat" -path = "src/tmux_copyrat.rs" +path = "src/bin/tmux_copyrat.rs" diff --git a/src/main.rs b/src/bin/copyrat.rs similarity index 100% rename from src/main.rs rename to src/bin/copyrat.rs diff --git a/src/tmux_copyrat.rs b/src/bin/tmux_copyrat.rs similarity index 99% rename from src/tmux_copyrat.rs rename to src/bin/tmux_copyrat.rs index fb9e908..6aea8b0 100644 --- a/src/tmux_copyrat.rs +++ b/src/bin/tmux_copyrat.rs @@ -2,9 +2,7 @@ use clap::Clap; use std::collections::HashMap; use std::str::FromStr; -use copyrat::{error, output_destination::OutputDestination, selection::Selection, CliOpt}; - -mod tmux; +use copyrat::{error, output_destination::OutputDestination, selection::Selection, tmux, CliOpt}; /// Main configuration, parsed from command line. #[derive(Clap, Debug)] diff --git a/src/lib.rs b/src/lib.rs index 57527ac..df58a83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub mod output_destination; pub mod process; pub mod regexes; pub mod selection; +pub mod tmux; pub mod ui; /// Run copyrat on an input string `buffer`, configured by `Opt`. diff --git a/src/tmux.rs b/src/tmux.rs index acff280..529e74d 100644 --- a/src/tmux.rs +++ b/src/tmux.rs @@ -4,8 +4,8 @@ use std::collections::HashMap; use std::fmt; use std::str::FromStr; -use copyrat::error::ParseError; -use copyrat::process; +use crate::error::ParseError; +use crate::process; #[derive(Debug, PartialEq)] pub struct Pane { @@ -241,7 +241,7 @@ pub fn swap_pane_with(target_pane: &str) -> Result<(), ParseError> { mod tests { use super::Pane; use super::PaneId; - use copyrat::error; + use crate::error; use std::str::FromStr; #[test] From ec03a71bfda3609419d3d40b41ffe9c7edaa3abc Mon Sep 17 00:00:00 2001 From: graelo Date: Wed, 17 Mar 2021 23:32:38 +0100 Subject: [PATCH 20/50] refactor: remove process.rs --- src/lib.rs | 1 - src/process.rs | 21 --------------------- src/tmux.rs | 13 ++++--------- src/ui.rs | 6 +++--- 4 files changed, 7 insertions(+), 34 deletions(-) delete mode 100644 src/process.rs diff --git a/src/lib.rs b/src/lib.rs index df58a83..c51ebb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,6 @@ pub mod colors; pub mod error; pub mod model; pub mod output_destination; -pub mod process; pub mod regexes; pub mod selection; pub mod tmux; diff --git a/src/process.rs b/src/process.rs deleted file mode 100644 index 5ef5825..0000000 --- a/src/process.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::process::Command; - -use crate::error::ParseError; - -/// Execute an arbitrary Unix command and return the stdout as a `String` if -/// successful. -pub fn execute(command: &str, args: &[&str]) -> Result { - let output = Command::new(command).args(args).output()?; - - if !output.status.success() { - let msg = String::from_utf8_lossy(&output.stderr); - return Err(ParseError::ProcessFailure(format!( - "Process failure: {} {}, error {}", - command, - args.join(" "), - msg - ))); - } - - Ok(String::from_utf8_lossy(&output.stdout).to_string()) -} diff --git a/src/tmux.rs b/src/tmux.rs index 529e74d..1649af1 100644 --- a/src/tmux.rs +++ b/src/tmux.rs @@ -5,7 +5,6 @@ use std::fmt; use std::str::FromStr; use crate::error::ParseError; -use crate::process; #[derive(Debug, PartialEq)] pub struct Pane { @@ -145,7 +144,7 @@ pub fn list_panes() -> Result, ParseError> { "#{pane_id}:#{?pane_in_mode,true,false}:#{pane_height}:#{scroll_position}:#{?pane_active,true,false}", ]; - let output = process::execute("tmux", &args)?; + let output = duct::cmd("tmux", &args).read()?; // Each call to `Pane::parse` returns a `Result`. All results // are collected into a Result, _>, thanks to `collect()`. @@ -165,9 +164,7 @@ pub fn list_panes() -> Result, ParseError> { /// # Example /// ```get_options("@copyrat-")``` pub fn get_options(prefix: &str) -> Result, ParseError> { - let args = vec!["show", "-g"]; - - let output = process::execute("tmux", &args)?; + let output = duct::cmd!("tmux", "show", "-g").read()?; let lines: Vec<&str> = output.split('\n').collect(); let pattern = format!(r#"{prefix}([\w\-0-9]+) "?(\w+)"?"#, prefix = prefix); @@ -223,16 +220,14 @@ pub fn capture_pane(pane: &Pane, region: &CaptureRegion) -> Result = args.split(' ').collect(); - let output = process::execute("tmux", &args)?; + let output = duct::cmd("tmux", &args).read()?; Ok(output) } /// Ask tmux to swap the current Pane with the target_pane (uses Tmux format). pub fn swap_pane_with(target_pane: &str) -> Result<(), ParseError> { // -Z: keep the window zoomed if it was zoomed. - let args = vec!["swap-pane", "-Z", "-s", target_pane]; - - process::execute("tmux", &args)?; + duct::cmd!("tmux", "swap-pane", "-Z", "-s", target_pane).run()?; Ok(()) } diff --git a/src/ui.rs b/src/ui.rs index 11ee285..bcc0d6d 100644 --- a/src/ui.rs +++ b/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, output_destination::OutputDestination, process, selection::Selection}; +use crate::{colors, model, output_destination::OutputDestination, selection::Selection}; pub struct Ui<'a> { model: &'a mut model::Model<'a>, @@ -493,8 +493,8 @@ impl<'a> Ui<'a> { event::Key::Char(_ch @ ' ') => { output_destination.toggle(); let message = format!("output destination: `{}`", output_destination); - let args = vec!["display-message", &message]; - process::execute("tmux", &args) + duct::cmd!("tmux", "display-message", &message) + .run() .expect("could not make tmux display the message."); continue; } From de1aa3889cdc0a295dc7abc93a51abd332c31087 Mon Sep 17 00:00:00 2001 From: graelo Date: Fri, 19 Mar 2021 09:00:42 +0100 Subject: [PATCH 21/50] refactor: refactor ui -> vc --- src/lib.rs | 2 +- src/ui/mod.rs | 22 ++++++++++++++++++++++ src/{ui.rs => ui/vc.rs} | 30 +++++++++++++++--------------- 3 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 src/ui/mod.rs rename src/{ui.rs => ui/vc.rs} (98%) diff --git a/src/lib.rs b/src/lib.rs index c51ebb7..5dcc402 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ pub fn run(buffer: String, opt: &CliOpt) -> Option { let default_output_destination = output_destination::OutputDestination::Tmux; let selection: Option = { - let mut ui = ui::Ui::new( + let mut ui = ui::ViewController::new( &mut model, opt.unique_hint, opt.focus_wrap_around, diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..1dcac7f --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,22 @@ +//! The `ui` module is responsible for presenting information to the user and +//! handling keypresses. +//! +//! In particular, the `Ui` struct +//! +//! - renders text, matched text and hints from the structured buffer content +//! to the screen, +//! - listens for keypress events, +//! - and returns the user selection in the form of a `Selection` struct. +//! +//! Via keypresses the user can +//! +//! - navigate the buffer (in case it is larger than the number of lines in +//! the terminal) +//! - move the focus from one match to another +//! - select one of the matches +//! - toggle the output destination (tmux buffer or clipboard) +//! + +mod vc; +pub use vc::ViewController; +pub use vc::{HintAlignment, HintStyle, UiColors}; diff --git a/src/ui.rs b/src/ui/vc.rs similarity index 98% rename from src/ui.rs rename to src/ui/vc.rs index bcc0d6d..391e848 100644 --- a/src/ui.rs +++ b/src/ui/vc.rs @@ -10,7 +10,7 @@ use termion::{self, color, cursor, event, style}; use crate::error::ParseError; use crate::{colors, model, output_destination::OutputDestination, selection::Selection}; -pub struct Ui<'a> { +pub struct ViewController<'a> { model: &'a mut model::Model<'a>, term_width: u16, line_offsets: Vec, @@ -24,7 +24,7 @@ pub struct Ui<'a> { hint_style: Option, } -impl<'a> Ui<'a> { +impl<'a> ViewController<'a> { pub fn new( model: &'a mut model::Model<'a>, unique_hint: bool, @@ -33,7 +33,7 @@ impl<'a> Ui<'a> { rendering_colors: &'a UiColors, hint_alignment: &'a HintAlignment, hint_style: Option, - ) -> Ui<'a> { + ) -> ViewController<'a> { let matches = model.matches(unique_hint); let lookup_trie = model::Model::build_lookup_trie(&matches); let focus_index = if model.reverse { matches.len() - 1 } else { 0 }; @@ -41,7 +41,7 @@ impl<'a> Ui<'a> { let (term_width, _) = termion::terminal_size().unwrap_or((80u16, 30u16)); // .expect("Cannot read the terminal size."); let line_offsets = get_line_offsets(&model.lines, term_width); - Ui { + ViewController { model, term_width, line_offsets, @@ -319,7 +319,7 @@ impl<'a> Ui<'a> { let (offset_x, offset_y) = self.match_offsets(mat); let (offset_x, offset_y) = self.map_coords_to_wrapped_space(offset_x, offset_y); - Ui::render_matched_text( + ViewController::render_matched_text( stdout, text, focused, @@ -336,7 +336,7 @@ impl<'a> Ui<'a> { HintAlignment::Trailing => text.len() - mat.hint.len(), }; - Ui::render_matched_hint( + ViewController::render_matched_hint( stdout, &mat.hint, (offset_x + extra_offset, offset_y), @@ -362,7 +362,7 @@ impl<'a> Ui<'a> { /// and `hint` are rendered in their proper position. fn full_render(&self, stdout: &mut dyn io::Write) { // 1. Trim all lines and render non-empty ones. - Ui::render_base_text( + ViewController::render_base_text( stdout, &self.model.lines, &self.line_offsets, @@ -679,7 +679,7 @@ path: /usr/local/bin/cargo"; }; let mut writer = vec![]; - Ui::render_base_text(&mut writer, &lines, &line_offsets, &colors); + ViewController::render_base_text(&mut writer, &lines, &line_offsets, &colors); let goto1 = cursor::Goto(1, 1); let goto2 = cursor::Goto(1, 2); @@ -716,7 +716,7 @@ path: /usr/local/bin/cargo"; hint_bg: Box::new(color::Cyan), }; - Ui::render_matched_text(&mut writer, text, focused, offset, &colors); + ViewController::render_matched_text(&mut writer, text, focused, offset, &colors); assert_eq!( writer, @@ -750,7 +750,7 @@ path: /usr/local/bin/cargo"; hint_bg: Box::new(color::Cyan), }; - Ui::render_matched_text(&mut writer, text, focused, offset, &colors); + ViewController::render_matched_text(&mut writer, text, focused, offset, &colors); assert_eq!( writer, @@ -786,7 +786,7 @@ path: /usr/local/bin/cargo"; let extra_offset = 0; let hint_style = None; - Ui::render_matched_hint( + ViewController::render_matched_hint( &mut writer, hint_text, (offset.0 + extra_offset, offset.1), @@ -828,7 +828,7 @@ path: /usr/local/bin/cargo"; let extra_offset = 0; let hint_style = Some(HintStyle::Underline); - Ui::render_matched_hint( + ViewController::render_matched_hint( &mut writer, hint_text, (offset.0 + extra_offset, offset.1), @@ -872,7 +872,7 @@ path: /usr/local/bin/cargo"; let extra_offset = 0; let hint_style = Some(HintStyle::Surround('{', '}')); - Ui::render_matched_hint( + ViewController::render_matched_hint( &mut writer, hint_text, (offset.0 + extra_offset, offset.1), @@ -932,7 +932,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let hint_alignment = HintAlignment::Leading; // create a Ui without any match - let ui = Ui { + let ui = ViewController { model: &mut model, term_width, line_offsets, @@ -1009,7 +1009,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let hint_alignment = HintAlignment::Leading; let hint_style = None; - let ui = Ui::new( + let ui = ViewController::new( &mut model, unique_hint, wrap_around, From d119ce6b0de738a9ceba79eff4e04683b94333a9 Mon Sep 17 00:00:00 2001 From: graelo Date: Fri, 19 Mar 2021 09:39:30 +0100 Subject: [PATCH 22/50] refactor: UiColors -> colors module --- src/colors.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- src/ui/mod.rs | 2 +- src/ui/vc.rs | 48 +----------------------------------------------- 4 files changed, 50 insertions(+), 49 deletions(-) diff --git a/src/colors.rs b/src/colors.rs index c3614db..19bf50e 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -1,4 +1,5 @@ use crate::error; +use clap::Clap; use termion::color; pub fn parse_color(src: &str) -> Result, error::ParseError> { @@ -45,3 +46,49 @@ mod tests { assert!(parse_color("wat").is_err(), "this color should not exist"); } } + +/// Holds color-related data, for clarity. +/// +/// - `focus_*` colors are used to render the currently focused matched text. +/// - `normal_*` colors are used to render other matched text. +/// - `hint_*` colors are used to render the hints. +#[derive(Clap, Debug)] +pub struct UiColors { + /// Foreground color for base text. + #[clap(long, default_value = "bright-cyan", parse(try_from_str = parse_color))] + pub text_fg: Box, + + /// Background color for base text. + #[clap(long, default_value = "bright-white", parse(try_from_str = parse_color))] + pub text_bg: Box, + + /// Foreground color for matches. + #[clap(long, default_value = "yellow", + parse(try_from_str = parse_color))] + pub match_fg: Box, + + /// Background color for matches. + #[clap(long, default_value = "bright-white", + parse(try_from_str = parse_color))] + pub match_bg: Box, + + /// Foreground color for the focused match. + #[clap(long, default_value = "magenta", + parse(try_from_str = parse_color))] + pub focused_fg: Box, + + /// Background color for the focused match. + #[clap(long, default_value = "bright-white", + parse(try_from_str = parse_color))] + pub focused_bg: Box, + + /// Foreground color for hints. + #[clap(long, default_value = "white", + parse(try_from_str = parse_color))] + pub hint_fg: Box, + + /// Background color for hints. + #[clap(long, default_value = "magenta", + parse(try_from_str = parse_color))] + pub hint_bg: Box, +} diff --git a/src/lib.rs b/src/lib.rs index 5dcc402..d1ceb77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,7 +98,7 @@ pub struct CliOpt { unique_hint: bool, #[clap(flatten)] - colors: ui::UiColors, + colors: colors::UiColors, /// Align hint with its match. #[clap(long, arg_enum, default_value = "leading")] diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 1dcac7f..8501623 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -19,4 +19,4 @@ mod vc; pub use vc::ViewController; -pub use vc::{HintAlignment, HintStyle, UiColors}; +pub use vc::{HintAlignment, HintStyle}; diff --git a/src/ui/vc.rs b/src/ui/vc.rs index 391e848..e3dcff6 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -8,7 +8,7 @@ use sequence_trie::SequenceTrie; use termion::{self, color, cursor, event, style}; use crate::error::ParseError; -use crate::{colors, model, output_destination::OutputDestination, selection::Selection}; +use crate::{colors::UiColors, model, output_destination::OutputDestination, selection::Selection}; pub struct ViewController<'a> { model: &'a mut model::Model<'a>, @@ -1115,49 +1115,3 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; assert_eq!(writer, expected.as_bytes()); } } - -// /// Holds color-related data, for clarity. -// /// -// /// - `focus_*` colors are used to render the currently focused matched text. -// /// - `normal_*` colors are used to render other matched text. -// /// - `hint_*` colors are used to render the hints. -#[derive(Clap, Debug)] -pub struct UiColors { - /// Foreground color for base text. - #[clap(long, default_value = "bright-cyan", parse(try_from_str = colors::parse_color))] - pub text_fg: Box, - - /// Background color for base text. - #[clap(long, default_value = "bright-white", parse(try_from_str = colors::parse_color))] - pub text_bg: Box, - - /// Foreground color for matches. - #[clap(long, default_value = "yellow", - parse(try_from_str = colors::parse_color))] - pub match_fg: Box, - - /// Background color for matches. - #[clap(long, default_value = "bright-white", - parse(try_from_str = colors::parse_color))] - pub match_bg: Box, - - /// Foreground color for the focused match. - #[clap(long, default_value = "magenta", - parse(try_from_str = colors::parse_color))] - pub focused_fg: Box, - - /// Background color for the focused match. - #[clap(long, default_value = "bright-white", - parse(try_from_str = colors::parse_color))] - pub focused_bg: Box, - - /// Foreground color for hints. - #[clap(long, default_value = "white", - parse(try_from_str = colors::parse_color))] - pub hint_fg: Box, - - /// Background color for hints. - #[clap(long, default_value = "magenta", - parse(try_from_str = colors::parse_color))] - pub hint_bg: Box, -} From 748da3ae72633d44bff6cb05d28e32f98e99693d Mon Sep 17 00:00:00 2001 From: graelo Date: Fri, 19 Mar 2021 23:07:08 +0100 Subject: [PATCH 23/50] refactor: colors.rs -> ui/colors.rs --- src/lib.rs | 15 +++++++-------- src/{ => ui}/colors.rs | 0 src/ui/mod.rs | 2 ++ src/ui/vc.rs | 3 ++- 4 files changed, 11 insertions(+), 9 deletions(-) rename src/{ => ui}/colors.rs (100%) diff --git a/src/lib.rs b/src/lib.rs index d1ceb77..a63641d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,6 @@ use std::path; use std::str::FromStr; pub mod alphabets; -pub mod colors; pub mod error; pub mod model; pub mod output_destination; @@ -98,7 +97,7 @@ pub struct CliOpt { unique_hint: bool, #[clap(flatten)] - colors: colors::UiColors, + colors: ui::colors::UiColors, /// Align hint with its match. #[clap(long, arg_enum, default_value = "leading")] @@ -187,12 +186,12 @@ impl CliOpt { self.unique_hint = value.parse::()?; } - "@copyrat-match-fg" => self.colors.match_fg = colors::parse_color(value)?, - "@copyrat-match-bg" => self.colors.match_bg = colors::parse_color(value)?, - "@copyrat-focused-fg" => self.colors.focused_fg = colors::parse_color(value)?, - "@copyrat-focused-bg" => self.colors.focused_bg = colors::parse_color(value)?, - "@copyrat-hint-fg" => self.colors.hint_fg = colors::parse_color(value)?, - "@copyrat-hint-bg" => self.colors.hint_bg = colors::parse_color(value)?, + "@copyrat-match-fg" => self.colors.match_fg = ui::colors::parse_color(value)?, + "@copyrat-match-bg" => self.colors.match_bg = ui::colors::parse_color(value)?, + "@copyrat-focused-fg" => self.colors.focused_fg = ui::colors::parse_color(value)?, + "@copyrat-focused-bg" => self.colors.focused_bg = ui::colors::parse_color(value)?, + "@copyrat-hint-fg" => self.colors.hint_fg = ui::colors::parse_color(value)?, + "@copyrat-hint-bg" => self.colors.hint_bg = ui::colors::parse_color(value)?, "@copyrat-hint-alignment" => { self.hint_alignment = ui::HintAlignment::from_str(&value)? diff --git a/src/colors.rs b/src/ui/colors.rs similarity index 100% rename from src/colors.rs rename to src/ui/colors.rs diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 8501623..b740123 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -17,6 +17,8 @@ //! - toggle the output destination (tmux buffer or clipboard) //! +pub mod colors; mod vc; + pub use vc::ViewController; pub use vc::{HintAlignment, HintStyle}; diff --git a/src/ui/vc.rs b/src/ui/vc.rs index e3dcff6..220d0e2 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -7,8 +7,9 @@ use clap::Clap; use sequence_trie::SequenceTrie; use termion::{self, color, cursor, event, style}; +use super::colors::UiColors; use crate::error::ParseError; -use crate::{colors::UiColors, model, output_destination::OutputDestination, selection::Selection}; +use crate::{model, output_destination::OutputDestination, selection::Selection}; pub struct ViewController<'a> { model: &'a mut model::Model<'a>, From a5e3ed263c1d36045d9fef6e4e5ee22ea12229ae Mon Sep 17 00:00:00 2001 From: graelo Date: Fri, 19 Mar 2021 23:17:41 +0100 Subject: [PATCH 24/50] refactor: selection.rs -> ui/selection.rs --- src/bin/copyrat.rs | 2 +- src/bin/tmux_copyrat.rs | 2 +- src/lib.rs | 5 ++--- src/ui/mod.rs | 2 ++ src/{ => ui}/selection.rs | 2 ++ src/ui/vc.rs | 3 ++- 6 files changed, 10 insertions(+), 6 deletions(-) rename src/{ => ui}/selection.rs (55%) diff --git a/src/bin/copyrat.rs b/src/bin/copyrat.rs index 5f8eeb6..4b0ab6f 100644 --- a/src/bin/copyrat.rs +++ b/src/bin/copyrat.rs @@ -3,7 +3,7 @@ use std::fs::OpenOptions; use std::io::prelude::*; use std::io::{self, Read}; -use copyrat::{run, selection::Selection, CliOpt}; +use copyrat::{run, ui::Selection, CliOpt}; fn main() { let opt = CliOpt::parse(); diff --git a/src/bin/tmux_copyrat.rs b/src/bin/tmux_copyrat.rs index 6aea8b0..b523baa 100644 --- a/src/bin/tmux_copyrat.rs +++ b/src/bin/tmux_copyrat.rs @@ -2,7 +2,7 @@ use clap::Clap; use std::collections::HashMap; use std::str::FromStr; -use copyrat::{error, output_destination::OutputDestination, selection::Selection, tmux, CliOpt}; +use copyrat::{error, output_destination::OutputDestination, tmux, ui::Selection, CliOpt}; /// Main configuration, parsed from command line. #[derive(Clap, Debug)] diff --git a/src/lib.rs b/src/lib.rs index a63641d..c591c71 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,6 @@ pub mod error; pub mod model; pub mod output_destination; pub mod regexes; -pub mod selection; pub mod tmux; pub mod ui; @@ -17,7 +16,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 { +pub fn run(buffer: String, opt: &CliOpt) -> Option { let mut model = model::Model::new( &buffer, &opt.alphabet, @@ -42,7 +41,7 @@ pub fn run(buffer: String, opt: &CliOpt) -> Option { let default_output_destination = output_destination::OutputDestination::Tmux; - let selection: Option = { + let selection: Option = { let mut ui = ui::ViewController::new( &mut model, opt.unique_hint, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index b740123..753c9f9 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -18,7 +18,9 @@ //! pub mod colors; +mod selection; mod vc; +pub use selection::Selection; pub use vc::ViewController; pub use vc::{HintAlignment, HintStyle}; diff --git a/src/selection.rs b/src/ui/selection.rs similarity index 55% rename from src/selection.rs rename to src/ui/selection.rs index bac4697..374a59d 100644 --- a/src/selection.rs +++ b/src/ui/selection.rs @@ -1,5 +1,7 @@ use crate::output_destination::OutputDestination; +/// Represents the text selected by the user, along with if it was uppercased +/// and the output destination (Tmux buffer or Clipboard). pub struct Selection { pub text: String, pub uppercased: bool, diff --git a/src/ui/vc.rs b/src/ui/vc.rs index 220d0e2..cab1e37 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -8,8 +8,9 @@ use sequence_trie::SequenceTrie; use termion::{self, color, cursor, event, style}; use super::colors::UiColors; +use super::Selection; use crate::error::ParseError; -use crate::{model, output_destination::OutputDestination, selection::Selection}; +use crate::{model, output_destination::OutputDestination}; pub struct ViewController<'a> { model: &'a mut model::Model<'a>, From 9afdcf3db20f258753d905833fc3a9ce73f309b6 Mon Sep 17 00:00:00 2001 From: graelo Date: Fri, 19 Mar 2021 23:29:36 +0100 Subject: [PATCH 25/50] refactor: model.rs -> textbuf/model.rs --- src/lib.rs | 4 ++-- src/textbuf/mod.rs | 3 +++ src/{ => textbuf}/model.rs | 0 src/ui/vc.rs | 18 +++++++++--------- 4 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 src/textbuf/mod.rs rename src/{ => textbuf}/model.rs (100%) diff --git a/src/lib.rs b/src/lib.rs index c591c71..a08b072 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,9 +5,9 @@ use std::str::FromStr; pub mod alphabets; pub mod error; -pub mod model; pub mod output_destination; pub mod regexes; +pub mod textbuf; pub mod tmux; pub mod ui; @@ -17,7 +17,7 @@ pub mod ui; /// /// Maybe the decision to take ownership of the buffer is a bit bold. pub fn run(buffer: String, opt: &CliOpt) -> Option { - let mut model = model::Model::new( + let mut model = textbuf::Model::new( &buffer, &opt.alphabet, opt.use_all_patterns, diff --git a/src/textbuf/mod.rs b/src/textbuf/mod.rs new file mode 100644 index 0000000..4dfc15e --- /dev/null +++ b/src/textbuf/mod.rs @@ -0,0 +1,3 @@ +mod model; + +pub use model::{Match, Model}; diff --git a/src/model.rs b/src/textbuf/model.rs similarity index 100% rename from src/model.rs rename to src/textbuf/model.rs diff --git a/src/ui/vc.rs b/src/ui/vc.rs index cab1e37..acba916 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -10,13 +10,13 @@ use termion::{self, color, cursor, event, style}; use super::colors::UiColors; use super::Selection; use crate::error::ParseError; -use crate::{model, output_destination::OutputDestination}; +use crate::{output_destination::OutputDestination, textbuf}; pub struct ViewController<'a> { - model: &'a mut model::Model<'a>, + model: &'a mut textbuf::Model<'a>, term_width: u16, line_offsets: Vec, - matches: Vec>, + matches: Vec>, lookup_trie: SequenceTrie, focus_index: usize, focus_wrap_around: bool, @@ -28,7 +28,7 @@ pub struct ViewController<'a> { impl<'a> ViewController<'a> { pub fn new( - model: &'a mut model::Model<'a>, + model: &'a mut textbuf::Model<'a>, unique_hint: bool, focus_wrap_around: bool, default_output_destination: OutputDestination, @@ -37,7 +37,7 @@ impl<'a> ViewController<'a> { hint_style: Option, ) -> ViewController<'a> { let matches = model.matches(unique_hint); - let lookup_trie = model::Model::build_lookup_trie(&matches); + let lookup_trie = textbuf::Model::build_lookup_trie(&matches); let focus_index = if model.reverse { matches.len() - 1 } else { 0 }; let (term_width, _) = termion::terminal_size().unwrap_or((80u16, 30u16)); // .expect("Cannot read the terminal size."); @@ -117,7 +117,7 @@ impl<'a> ViewController<'a> { /// their compouding takes less space on screen when printed: for /// instance ´ + e = é. Consequently the hint offset has to be adjusted /// to the left. - fn match_offsets(&self, mat: &model::Match<'a>) -> (usize, usize) { + fn match_offsets(&self, mat: &textbuf::Match<'a>) -> (usize, usize) { let offset_x = { let line = &self.model.lines[mat.y as usize]; let prefix = &line[0..mat.x as usize]; @@ -315,7 +315,7 @@ impl<'a> ViewController<'a> { /// Convenience function that renders both the matched text and its hint, /// if focused. - fn render_match(&self, stdout: &mut dyn io::Write, mat: &model::Match<'a>, focused: bool) { + fn render_match(&self, stdout: &mut dyn io::Write, mat: &textbuf::Match<'a>, focused: bool) { let text = mat.text; let (offset_x, offset_y) = self.match_offsets(mat); @@ -911,7 +911,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let custom_patterns = vec![]; let alphabet = alphabets::Alphabet("abcd".to_string()); let reverse = false; - let mut model = model::Model::new( + let mut model = textbuf::Model::new( content, &alphabet, use_all_patterns, @@ -986,7 +986,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let custom_patterns = vec![]; let alphabet = alphabets::Alphabet("abcd".to_string()); let reverse = true; - let mut model = model::Model::new( + let mut model = textbuf::Model::new( content, &alphabet, use_all_patterns, From f76ed75f5b1eac2cee50e4e5f0b07cdcecb3fcef Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 20 Mar 2021 07:01:23 +0100 Subject: [PATCH 26/50] refactor: RawMatch -> textbuf/raw_match.rs --- src/textbuf/mod.rs | 1 + src/textbuf/model.rs | 10 +--------- src/textbuf/raw_match.rs | 8 ++++++++ 3 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 src/textbuf/raw_match.rs diff --git a/src/textbuf/mod.rs b/src/textbuf/mod.rs index 4dfc15e..646ee07 100644 --- a/src/textbuf/mod.rs +++ b/src/textbuf/mod.rs @@ -1,3 +1,4 @@ mod model; +mod raw_match; pub use model::{Match, Model}; diff --git a/src/textbuf/model.rs b/src/textbuf/model.rs index 8338370..f0cb2f7 100644 --- a/src/textbuf/model.rs +++ b/src/textbuf/model.rs @@ -3,6 +3,7 @@ use std::collections; use regex::Regex; use sequence_trie::SequenceTrie; +use super::raw_match::RawMatch; use crate::alphabets::Alphabet; use crate::regexes::{NamedPattern, EXCLUDE_PATTERNS, PATTERNS}; @@ -238,15 +239,6 @@ pub struct Match<'a> { pub hint: String, } -/// Internal surrogate for `Match`, before a Hint has been associated. -#[derive(Debug)] -struct RawMatch<'a> { - pub x: i32, - pub y: i32, - pub pattern: &'a str, - pub text: &'a str, -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/textbuf/raw_match.rs b/src/textbuf/raw_match.rs new file mode 100644 index 0000000..7da45ce --- /dev/null +++ b/src/textbuf/raw_match.rs @@ -0,0 +1,8 @@ +/// Internal surrogate for `Match`, before a Hint has been associated. +#[derive(Debug)] +pub(super) struct RawMatch<'a> { + pub x: i32, + pub y: i32, + pub pattern: &'a str, + pub text: &'a str, +} From 7d4d9a8824f5154e590cd70063418ade71d3e8dd Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 20 Mar 2021 07:06:33 +0100 Subject: [PATCH 27/50] refactor: Match -> textbuf/matches.rs --- src/textbuf/matches.rs | 10 ++++++++++ src/textbuf/mod.rs | 4 +++- src/textbuf/model.rs | 12 +----------- 3 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 src/textbuf/matches.rs diff --git a/src/textbuf/matches.rs b/src/textbuf/matches.rs new file mode 100644 index 0000000..1695605 --- /dev/null +++ b/src/textbuf/matches.rs @@ -0,0 +1,10 @@ +/// Represents matched text, its location on screen, the pattern that created +/// it, and the associated hint. +#[derive(Debug)] +pub struct Match<'a> { + pub x: i32, + pub y: i32, + pub pattern: &'a str, + pub text: &'a str, + pub hint: String, +} diff --git a/src/textbuf/mod.rs b/src/textbuf/mod.rs index 646ee07..6d912bd 100644 --- a/src/textbuf/mod.rs +++ b/src/textbuf/mod.rs @@ -1,4 +1,6 @@ +mod matches; mod model; mod raw_match; -pub use model::{Match, Model}; +pub use matches::Match; +pub use model::Model; diff --git a/src/textbuf/model.rs b/src/textbuf/model.rs index f0cb2f7..a8ee610 100644 --- a/src/textbuf/model.rs +++ b/src/textbuf/model.rs @@ -3,6 +3,7 @@ use std::collections; use regex::Regex; use sequence_trie::SequenceTrie; +use super::matches::Match; use super::raw_match::RawMatch; use crate::alphabets::Alphabet; use crate::regexes::{NamedPattern, EXCLUDE_PATTERNS, PATTERNS}; @@ -228,17 +229,6 @@ impl<'a> Model<'a> { } } -/// Represents matched text, its location on screen, the pattern that created -/// it, and the associated hint. -#[derive(Debug)] -pub struct Match<'a> { - pub x: i32, - pub y: i32, - pub pattern: &'a str, - pub text: &'a str, - pub hint: String, -} - #[cfg(test)] mod tests { use super::*; From 2c3c7a54560acc49f7789795b704964b17778be1 Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 20 Mar 2021 07:46:43 +0100 Subject: [PATCH 28/50] refactor: alphabets.rs -> textbuf/alphabet.rs --- src/lib.rs | 19 ++++++++++--------- src/{alphabets.rs => textbuf/alphabet.rs} | 0 src/textbuf/mod.rs | 1 + src/textbuf/model.rs | 4 ++-- src/ui/vc.rs | 6 +++--- 5 files changed, 16 insertions(+), 14 deletions(-) rename src/{alphabets.rs => textbuf/alphabet.rs} (100%) diff --git a/src/lib.rs b/src/lib.rs index a08b072..b0a3d18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,3 @@ -use clap::Clap; -use std::collections::HashMap; -use std::path; -use std::str::FromStr; - -pub mod alphabets; pub mod error; pub mod output_destination; pub mod regexes; @@ -11,6 +5,13 @@ pub mod textbuf; pub mod tmux; pub mod ui; +use clap::Clap; +use std::collections::HashMap; +use std::path; +use std::str::FromStr; + +use crate::textbuf::alphabet; + /// Run copyrat on an input string `buffer`, configured by `Opt`. /// /// # Note @@ -72,8 +73,8 @@ pub struct CliOpt { /// /// "qwerty", "dvorak-homerow", "azerty-right-hand". #[clap(short = 'k', long, default_value = "dvorak", - parse(try_from_str = alphabets::parse_alphabet))] - alphabet: alphabets::Alphabet, + parse(try_from_str = alphabet::parse_alphabet))] + alphabet: alphabet::Alphabet, /// Use all available regex patterns. #[clap(short = 'A', long = "--all-patterns")] @@ -172,7 +173,7 @@ impl CliOpt { for (name, value) in options { match name.as_ref() { "@copyrat-alphabet" => { - self.alphabet = alphabets::parse_alphabet(value)?; + self.alphabet = alphabet::parse_alphabet(value)?; } "@copyrat-pattern-name" => { self.named_patterns = vec![regexes::parse_pattern_name(value)?] diff --git a/src/alphabets.rs b/src/textbuf/alphabet.rs similarity index 100% rename from src/alphabets.rs rename to src/textbuf/alphabet.rs diff --git a/src/textbuf/mod.rs b/src/textbuf/mod.rs index 6d912bd..6d0b615 100644 --- a/src/textbuf/mod.rs +++ b/src/textbuf/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod alphabet; mod matches; mod model; mod raw_match; diff --git a/src/textbuf/model.rs b/src/textbuf/model.rs index a8ee610..0dc31f4 100644 --- a/src/textbuf/model.rs +++ b/src/textbuf/model.rs @@ -3,9 +3,9 @@ use std::collections; use regex::Regex; use sequence_trie::SequenceTrie; +use super::alphabet::Alphabet; use super::matches::Match; use super::raw_match::RawMatch; -use crate::alphabets::Alphabet; use crate::regexes::{NamedPattern, EXCLUDE_PATTERNS, PATTERNS}; /// Holds data for the `Ui`. @@ -232,7 +232,7 @@ impl<'a> Model<'a> { #[cfg(test)] mod tests { use super::*; - use crate::alphabets::Alphabet; + use crate::textbuf::alphabet::Alphabet; #[test] fn match_reverse() { diff --git a/src/ui/vc.rs b/src/ui/vc.rs index acba916..0cd0c54 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -656,7 +656,7 @@ enum Event { #[cfg(test)] mod tests { use super::*; - use crate::alphabets; + use crate::textbuf::alphabet; #[test] fn test_render_all_lines() { @@ -909,7 +909,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let use_all_patterns = true; let named_pat = vec![]; let custom_patterns = vec![]; - let alphabet = alphabets::Alphabet("abcd".to_string()); + let alphabet = alphabet::Alphabet("abcd".to_string()); let reverse = false; let mut model = textbuf::Model::new( content, @@ -984,7 +984,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let use_all_patterns = true; let named_pat = vec![]; let custom_patterns = vec![]; - let alphabet = alphabets::Alphabet("abcd".to_string()); + let alphabet = alphabet::Alphabet("abcd".to_string()); let reverse = true; let mut model = textbuf::Model::new( content, From 5904bb1df58da96252b583a3973311fb34eebd48 Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 20 Mar 2021 09:30:38 +0100 Subject: [PATCH 29/50] refactor: regexes.rs -> textbuf/regexes.rs --- src/lib.rs | 2 +- src/textbuf/mod.rs | 1 + src/textbuf/model.rs | 4 ++-- src/{ => textbuf}/regexes.rs | 6 +++--- 4 files changed, 7 insertions(+), 6 deletions(-) rename src/{ => textbuf}/regexes.rs (88%) diff --git a/src/lib.rs b/src/lib.rs index b0a3d18..a8ae97c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ pub mod error; pub mod output_destination; -pub mod regexes; pub mod textbuf; pub mod tmux; pub mod ui; @@ -11,6 +10,7 @@ use std::path; use std::str::FromStr; use crate::textbuf::alphabet; +use crate::textbuf::regexes; /// Run copyrat on an input string `buffer`, configured by `Opt`. /// diff --git a/src/textbuf/mod.rs b/src/textbuf/mod.rs index 6d0b615..841d319 100644 --- a/src/textbuf/mod.rs +++ b/src/textbuf/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod alphabet; mod matches; mod model; mod raw_match; +pub(crate) mod regexes; pub use matches::Match; pub use model::Model; diff --git a/src/textbuf/model.rs b/src/textbuf/model.rs index 0dc31f4..470b08c 100644 --- a/src/textbuf/model.rs +++ b/src/textbuf/model.rs @@ -6,7 +6,7 @@ use sequence_trie::SequenceTrie; use super::alphabet::Alphabet; use super::matches::Match; use super::raw_match::RawMatch; -use crate::regexes::{NamedPattern, EXCLUDE_PATTERNS, PATTERNS}; +use super::regexes::{NamedPattern, EXCLUDE_PATTERNS, PATTERNS}; /// Holds data for the `Ui`. pub struct Model<'a> { @@ -757,7 +757,7 @@ mod tests { let buffer = "Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem"; let use_all_patterns = false; - use crate::regexes::parse_pattern_name; + use crate::textbuf::regexes::parse_pattern_name; let named_pat = vec![parse_pattern_name("url").unwrap()]; let custom = vec![]; diff --git a/src/regexes.rs b/src/textbuf/regexes.rs similarity index 88% rename from src/regexes.rs rename to src/textbuf/regexes.rs index aa90139..6b823b5 100644 --- a/src/regexes.rs +++ b/src/textbuf/regexes.rs @@ -1,13 +1,13 @@ use crate::error; -pub const EXCLUDE_PATTERNS: [(&str, &str); 1] = +pub(super) const EXCLUDE_PATTERNS: [(&str, &str); 1] = [("ansi_colors", r"[[:cntrl:]]\[([0-9]{1,2};)?([0-9]{1,2})?m")]; /// Holds all the regex patterns that are currently supported. /// /// The email address was obtained at https://www.regular-expressions.info/email.html. /// Others were obtained from Ferran Basora. -pub const PATTERNS: [(&str, &str); 16] = [ +pub(super) const PATTERNS: [(&str, &str); 16] = [ ("markdown-url", r"\[[^]]*\]\(([^)]+)\)"), ( "url", @@ -40,7 +40,7 @@ pub const PATTERNS: [(&str, &str); 16] = [ pub struct NamedPattern(pub String, pub String); /// Parse a name string into `NamedPattern`, used during CLI parsing. -pub fn parse_pattern_name(src: &str) -> Result { +pub(crate) fn parse_pattern_name(src: &str) -> Result { match PATTERNS.iter().find(|&(name, _pattern)| name == &src) { Some((name, pattern)) => Ok(NamedPattern(name.to_string(), pattern.to_string())), None => Err(error::ParseError::UnknownPatternName), From dcd92230e11e08816cc2463eb22487680ec2aefc Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 20 Mar 2021 10:14:33 +0100 Subject: [PATCH 30/50] refactor: move model tests -> textbuf/mod.rs --- src/textbuf/mod.rs | 555 +++++++++++++++++++++++++++++++++++++++++++ src/textbuf/model.rs | 553 ------------------------------------------ 2 files changed, 555 insertions(+), 553 deletions(-) diff --git a/src/textbuf/mod.rs b/src/textbuf/mod.rs index 841d319..92c9382 100644 --- a/src/textbuf/mod.rs +++ b/src/textbuf/mod.rs @@ -6,3 +6,558 @@ pub(crate) mod regexes; pub use matches::Match; pub use model::Model; + +#[cfg(test)] +mod tests { + use super::alphabet::Alphabet; + use super::model::Model; + + #[test] + fn match_reverse() { + let buffer = "lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 3); + assert_eq!(results.first().unwrap().hint, "a"); + assert_eq!(results.last().unwrap().hint, "c"); + } + + #[test] + fn match_unique() { + let buffer = "lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(true); + + assert_eq!(results.len(), 3); + assert_eq!(results.first().unwrap().hint, "a"); + assert_eq!(results.last().unwrap().hint, "a"); + } + + #[test] + fn match_docker() { + let buffer = "latest sha256:30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 1); + assert_eq!( + results.get(0).unwrap().text, + "30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4" + ); + } + + #[test] + fn match_ansi_colors() { + let buffer = + "path: /var/log/nginx.log\npath: test/log/nginx-2.log:32folder/.nginx@4df2.log"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = true; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 3); + assert_eq!(results.get(0).unwrap().text, "/var/log/nginx.log"); + assert_eq!(results.get(1).unwrap().text, "test/log/nginx-2.log"); + assert_eq!(results.get(2).unwrap().text, "folder/.nginx@4df2.log"); + } + + #[test] + fn match_paths() { + let buffer = + "Lorem /tmp/foo/bar_lol, lorem\n Lorem /var/log/boot-strap.log lorem ../log/kern.log lorem"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 3); + assert_eq!(results.get(0).unwrap().text, "/tmp/foo/bar_lol"); + assert_eq!(results.get(1).unwrap().text, "/var/log/boot-strap.log"); + assert_eq!(results.get(2).unwrap().text, "../log/kern.log"); + } + + #[test] + fn match_home() { + let buffer = "Lorem ~/.gnu/.config.txt, lorem"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 1); + assert_eq!(results.get(0).unwrap().text, "~/.gnu/.config.txt"); + } + + #[test] + fn match_uuids() { + let buffer = "Lorem ipsum 123e4567-e89b-12d3-a456-426655440000 lorem\n Lorem lorem lorem"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 1); + } + + #[test] + fn match_shas() { + let buffer = "Lorem fd70b5695 5246ddf f924213 lorem\n Lorem 973113963b491874ab2e372ee60d4b4cb75f717c lorem"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 4); + assert_eq!(results.get(0).unwrap().text, "fd70b5695"); + assert_eq!(results.get(1).unwrap().text, "5246ddf"); + assert_eq!(results.get(2).unwrap().text, "f924213"); + assert_eq!( + results.get(3).unwrap().text, + "973113963b491874ab2e372ee60d4b4cb75f717c" + ); + } + + #[test] + fn match_ipv4s() { + let buffer = "Lorem ipsum 127.0.0.1 lorem\n Lorem 255.255.10.255 lorem 127.0.0.1 lorem"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 3); + assert_eq!(results.get(0).unwrap().pattern, "ipv4"); + assert_eq!(results.get(0).unwrap().text, "127.0.0.1"); + assert_eq!(results.get(1).unwrap().pattern, "ipv4"); + assert_eq!(results.get(1).unwrap().text, "255.255.10.255"); + assert_eq!(results.get(2).unwrap().pattern, "ipv4"); + assert_eq!(results.get(2).unwrap().text, "127.0.0.1"); + } + + #[test] + fn match_ipv6s() { + let buffer = "Lorem ipsum fe80::2:202:fe4 lorem\n Lorem 2001:67c:670:202:7ba8:5e41:1591:d723 lorem fe80::2:1 lorem ipsum fe80:22:312:fe::1%eth0"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 4); + assert_eq!(results.get(0).unwrap().text, "fe80::2:202:fe4"); + assert_eq!( + results.get(1).unwrap().text, + "2001:67c:670:202:7ba8:5e41:1591:d723" + ); + assert_eq!(results.get(2).unwrap().text, "fe80::2:1"); + assert_eq!(results.get(3).unwrap().text, "fe80:22:312:fe::1%eth0"); + } + + #[test] + fn match_markdown_urls() { + let buffer = + "Lorem ipsum [link](https://github.io?foo=bar) ![](http://cdn.com/img.jpg) lorem"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 2); + assert_eq!(results.get(0).unwrap().pattern, "markdown-url"); + assert_eq!(results.get(0).unwrap().text, "https://github.io?foo=bar"); + assert_eq!(results.get(1).unwrap().pattern, "markdown-url"); + assert_eq!(results.get(1).unwrap().text, "http://cdn.com/img.jpg"); + } + + #[test] + fn match_urls() { + let buffer = "Lorem ipsum https://www.rust-lang.org/tools lorem\n Lorem ipsumhttps://crates.io lorem https://github.io?foo=bar lorem ssh://github.io"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 4); + assert_eq!( + results.get(0).unwrap().text, + "https://www.rust-lang.org/tools" + ); + assert_eq!(results.get(0).unwrap().pattern, "url"); + assert_eq!(results.get(1).unwrap().text, "https://crates.io"); + assert_eq!(results.get(1).unwrap().pattern, "url"); + assert_eq!(results.get(2).unwrap().text, "https://github.io?foo=bar"); + assert_eq!(results.get(2).unwrap().pattern, "url"); + assert_eq!(results.get(3).unwrap().text, "ssh://github.io"); + assert_eq!(results.get(3).unwrap().pattern, "url"); + } + + #[test] + fn match_emails() { + let buffer = + "Lorem ipsum john@server.department.company.com lorem"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 2); + assert_eq!(results.get(0).unwrap().pattern, "email"); + assert_eq!( + results.get(0).unwrap().text, + "first.last+social@example.com" + ); + assert_eq!(results.get(1).unwrap().pattern, "email"); + assert_eq!( + results.get(1).unwrap().text, + "john@server.department.company.com" + ); + } + + #[test] + fn match_addresses() { + let buffer = "Lorem 0xfd70b5695 0x5246ddf lorem\n Lorem 0x973113tlorem"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 3); + assert_eq!(results.get(0).unwrap().pattern, "mem-address"); + assert_eq!(results.get(0).unwrap().text, "0xfd70b5695"); + assert_eq!(results.get(1).unwrap().pattern, "mem-address"); + assert_eq!(results.get(1).unwrap().text, "0x5246ddf"); + assert_eq!(results.get(2).unwrap().pattern, "mem-address"); + assert_eq!(results.get(2).unwrap().text, "0x973113"); + } + + #[test] + fn match_hex_colors() { + let buffer = "Lorem #fd7b56 lorem #FF00FF\n Lorem #00fF05 lorem #abcd00 lorem #afRR00"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 4); + assert_eq!(results.get(0).unwrap().text, "#fd7b56"); + assert_eq!(results.get(1).unwrap().text, "#FF00FF"); + assert_eq!(results.get(2).unwrap().text, "#00fF05"); + assert_eq!(results.get(3).unwrap().text, "#abcd00"); + } + + #[test] + fn match_ipfs() { + let buffer = "Lorem QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ lorem Qmfoobar"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 1); + assert_eq!( + results.get(0).unwrap().text, + "QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ" + ); + } + + #[test] + fn match_process_port() { + let buffer = "Lorem 5695 52463 lorem\n Lorem 973113 lorem 99999 lorem 8888 lorem\n 23456 lorem 5432 lorem 23444"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 8); + } + + #[test] + fn match_diff_a() { + let buffer = "Lorem lorem\n--- a/src/main.rs"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 1); + assert_eq!(results.get(0).unwrap().pattern, "diff-a"); + assert_eq!(results.get(0).unwrap().text, "src/main.rs"); + } + + #[test] + fn match_diff_b() { + let buffer = "Lorem lorem\n+++ b/src/main.rs"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 1); + assert_eq!(results.get(0).unwrap().pattern, "diff-b"); + assert_eq!(results.get(0).unwrap().text, "src/main.rs"); + } + + #[test] + fn priority_between_regexes() { + let buffer = "Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem"; + let use_all_patterns = true; + let named_pat = vec![]; + let custom: Vec = ["CUSTOM-[0-9]{4,}", "ISSUE-[0-9]{3}"] + .iter() + .map(|&s| s.to_string()) + .collect(); + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 9); + assert_eq!(results.get(0).unwrap().text, "http://foo.bar"); + assert_eq!(results.get(1).unwrap().text, "CUSTOM-52463"); + assert_eq!(results.get(2).unwrap().text, "ISSUE-123"); + assert_eq!(results.get(3).unwrap().text, "/var/fd70b569/9999.log"); + assert_eq!(results.get(4).unwrap().text, "52463"); + assert_eq!(results.get(5).unwrap().text, "973113"); + assert_eq!( + results.get(6).unwrap().text, + "123e4567-e89b-12d3-a456-426655440000" + ); + assert_eq!(results.get(7).unwrap().text, "8888"); + assert_eq!( + results.get(8).unwrap().text, + "https://crates.io/23456/fd70b569" + ); + } + + #[test] + fn named_patterns() { + let buffer = "Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem"; + + let use_all_patterns = false; + use crate::textbuf::regexes::parse_pattern_name; + let named_pat = vec![parse_pattern_name("url").unwrap()]; + + let custom = vec![]; + let alphabet = Alphabet("abcd".to_string()); + let reverse = false; + let results = Model::new( + buffer, + &alphabet, + use_all_patterns, + &named_pat, + &custom, + reverse, + ) + .matches(false); + + assert_eq!(results.len(), 2); + assert_eq!(results.get(0).unwrap().text, "http://foo.bar"); + assert_eq!( + results.get(1).unwrap().text, + "https://crates.io/23456/fd70b569" + ); + } +} diff --git a/src/textbuf/model.rs b/src/textbuf/model.rs index 470b08c..6fe444c 100644 --- a/src/textbuf/model.rs +++ b/src/textbuf/model.rs @@ -228,556 +228,3 @@ impl<'a> Model<'a> { trie } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::textbuf::alphabet::Alphabet; - - #[test] - fn match_reverse() { - let buffer = "lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 3); - assert_eq!(results.first().unwrap().hint, "a"); - assert_eq!(results.last().unwrap().hint, "c"); - } - - #[test] - fn match_unique() { - let buffer = "lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(true); - - assert_eq!(results.len(), 3); - assert_eq!(results.first().unwrap().hint, "a"); - assert_eq!(results.last().unwrap().hint, "a"); - } - - #[test] - fn match_docker() { - let buffer = "latest sha256:30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 1); - assert_eq!( - results.get(0).unwrap().text, - "30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4" - ); - } - - #[test] - fn match_ansi_colors() { - let buffer = "path: /var/log/nginx.log\npath: test/log/nginx-2.log:32folder/.nginx@4df2.log"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = true; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 3); - assert_eq!(results.get(0).unwrap().text, "/var/log/nginx.log"); - assert_eq!(results.get(1).unwrap().text, "test/log/nginx-2.log"); - assert_eq!(results.get(2).unwrap().text, "folder/.nginx@4df2.log"); - } - - #[test] - fn match_paths() { - let buffer = "Lorem /tmp/foo/bar_lol, lorem\n Lorem /var/log/boot-strap.log lorem ../log/kern.log lorem"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 3); - assert_eq!(results.get(0).unwrap().text, "/tmp/foo/bar_lol"); - assert_eq!(results.get(1).unwrap().text, "/var/log/boot-strap.log"); - assert_eq!(results.get(2).unwrap().text, "../log/kern.log"); - } - - #[test] - fn match_home() { - let buffer = "Lorem ~/.gnu/.config.txt, lorem"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 1); - assert_eq!(results.get(0).unwrap().text, "~/.gnu/.config.txt"); - } - - #[test] - fn match_uuids() { - let buffer = "Lorem ipsum 123e4567-e89b-12d3-a456-426655440000 lorem\n Lorem lorem lorem"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 1); - } - - #[test] - fn match_shas() { - let buffer = "Lorem fd70b5695 5246ddf f924213 lorem\n Lorem 973113963b491874ab2e372ee60d4b4cb75f717c lorem"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 4); - assert_eq!(results.get(0).unwrap().text, "fd70b5695"); - assert_eq!(results.get(1).unwrap().text, "5246ddf"); - assert_eq!(results.get(2).unwrap().text, "f924213"); - assert_eq!( - results.get(3).unwrap().text, - "973113963b491874ab2e372ee60d4b4cb75f717c" - ); - } - - #[test] - fn match_ipv4s() { - let buffer = "Lorem ipsum 127.0.0.1 lorem\n Lorem 255.255.10.255 lorem 127.0.0.1 lorem"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 3); - assert_eq!(results.get(0).unwrap().pattern, "ipv4"); - assert_eq!(results.get(0).unwrap().text, "127.0.0.1"); - assert_eq!(results.get(1).unwrap().pattern, "ipv4"); - assert_eq!(results.get(1).unwrap().text, "255.255.10.255"); - assert_eq!(results.get(2).unwrap().pattern, "ipv4"); - assert_eq!(results.get(2).unwrap().text, "127.0.0.1"); - } - - #[test] - fn match_ipv6s() { - let buffer = "Lorem ipsum fe80::2:202:fe4 lorem\n Lorem 2001:67c:670:202:7ba8:5e41:1591:d723 lorem fe80::2:1 lorem ipsum fe80:22:312:fe::1%eth0"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 4); - assert_eq!(results.get(0).unwrap().text, "fe80::2:202:fe4"); - assert_eq!( - results.get(1).unwrap().text, - "2001:67c:670:202:7ba8:5e41:1591:d723" - ); - assert_eq!(results.get(2).unwrap().text, "fe80::2:1"); - assert_eq!(results.get(3).unwrap().text, "fe80:22:312:fe::1%eth0"); - } - - #[test] - fn match_markdown_urls() { - let buffer = - "Lorem ipsum [link](https://github.io?foo=bar) ![](http://cdn.com/img.jpg) lorem"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 2); - assert_eq!(results.get(0).unwrap().pattern, "markdown-url"); - assert_eq!(results.get(0).unwrap().text, "https://github.io?foo=bar"); - assert_eq!(results.get(1).unwrap().pattern, "markdown-url"); - assert_eq!(results.get(1).unwrap().text, "http://cdn.com/img.jpg"); - } - - #[test] - fn match_urls() { - let buffer = "Lorem ipsum https://www.rust-lang.org/tools lorem\n Lorem ipsumhttps://crates.io lorem https://github.io?foo=bar lorem ssh://github.io"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 4); - assert_eq!( - results.get(0).unwrap().text, - "https://www.rust-lang.org/tools" - ); - assert_eq!(results.get(0).unwrap().pattern, "url"); - assert_eq!(results.get(1).unwrap().text, "https://crates.io"); - assert_eq!(results.get(1).unwrap().pattern, "url"); - assert_eq!(results.get(2).unwrap().text, "https://github.io?foo=bar"); - assert_eq!(results.get(2).unwrap().pattern, "url"); - assert_eq!(results.get(3).unwrap().text, "ssh://github.io"); - assert_eq!(results.get(3).unwrap().pattern, "url"); - } - - #[test] - fn match_emails() { - let buffer = - "Lorem ipsum john@server.department.company.com lorem"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 2); - assert_eq!(results.get(0).unwrap().pattern, "email"); - assert_eq!( - results.get(0).unwrap().text, - "first.last+social@example.com" - ); - assert_eq!(results.get(1).unwrap().pattern, "email"); - assert_eq!( - results.get(1).unwrap().text, - "john@server.department.company.com" - ); - } - - #[test] - fn match_addresses() { - let buffer = "Lorem 0xfd70b5695 0x5246ddf lorem\n Lorem 0x973113tlorem"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 3); - assert_eq!(results.get(0).unwrap().pattern, "mem-address"); - assert_eq!(results.get(0).unwrap().text, "0xfd70b5695"); - assert_eq!(results.get(1).unwrap().pattern, "mem-address"); - assert_eq!(results.get(1).unwrap().text, "0x5246ddf"); - assert_eq!(results.get(2).unwrap().pattern, "mem-address"); - assert_eq!(results.get(2).unwrap().text, "0x973113"); - } - - #[test] - fn match_hex_colors() { - let buffer = "Lorem #fd7b56 lorem #FF00FF\n Lorem #00fF05 lorem #abcd00 lorem #afRR00"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 4); - assert_eq!(results.get(0).unwrap().text, "#fd7b56"); - assert_eq!(results.get(1).unwrap().text, "#FF00FF"); - assert_eq!(results.get(2).unwrap().text, "#00fF05"); - assert_eq!(results.get(3).unwrap().text, "#abcd00"); - } - - #[test] - fn match_ipfs() { - let buffer = "Lorem QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ lorem Qmfoobar"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 1); - assert_eq!( - results.get(0).unwrap().text, - "QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ" - ); - } - - #[test] - fn match_process_port() { - let buffer = "Lorem 5695 52463 lorem\n Lorem 973113 lorem 99999 lorem 8888 lorem\n 23456 lorem 5432 lorem 23444"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 8); - } - - #[test] - fn match_diff_a() { - let buffer = "Lorem lorem\n--- a/src/main.rs"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 1); - assert_eq!(results.get(0).unwrap().pattern, "diff-a"); - assert_eq!(results.get(0).unwrap().text, "src/main.rs"); - } - - #[test] - fn match_diff_b() { - let buffer = "Lorem lorem\n+++ b/src/main.rs"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 1); - assert_eq!(results.get(0).unwrap().pattern, "diff-b"); - assert_eq!(results.get(0).unwrap().text, "src/main.rs"); - } - - #[test] - fn priority_between_regexes() { - let buffer = "Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem"; - let use_all_patterns = true; - let named_pat = vec![]; - let custom: Vec = ["CUSTOM-[0-9]{4,}", "ISSUE-[0-9]{3}"] - .iter() - .map(|&s| s.to_string()) - .collect(); - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 9); - assert_eq!(results.get(0).unwrap().text, "http://foo.bar"); - assert_eq!(results.get(1).unwrap().text, "CUSTOM-52463"); - assert_eq!(results.get(2).unwrap().text, "ISSUE-123"); - assert_eq!(results.get(3).unwrap().text, "/var/fd70b569/9999.log"); - assert_eq!(results.get(4).unwrap().text, "52463"); - assert_eq!(results.get(5).unwrap().text, "973113"); - assert_eq!( - results.get(6).unwrap().text, - "123e4567-e89b-12d3-a456-426655440000" - ); - assert_eq!(results.get(7).unwrap().text, "8888"); - assert_eq!( - results.get(8).unwrap().text, - "https://crates.io/23456/fd70b569" - ); - } - - #[test] - fn named_patterns() { - let buffer = "Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem"; - - let use_all_patterns = false; - use crate::textbuf::regexes::parse_pattern_name; - let named_pat = vec![parse_pattern_name("url").unwrap()]; - - let custom = vec![]; - let alphabet = Alphabet("abcd".to_string()); - let reverse = false; - let results = Model::new( - buffer, - &alphabet, - use_all_patterns, - &named_pat, - &custom, - reverse, - ) - .matches(false); - - assert_eq!(results.len(), 2); - assert_eq!(results.get(0).unwrap().text, "http://foo.bar"); - assert_eq!( - results.get(1).unwrap().text, - "https://crates.io/23456/fd70b569" - ); - } -} From da23c89b80e8cfbd531fb371a6a9b1bb78838bab Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 20 Mar 2021 19:47:00 +0100 Subject: [PATCH 31/50] refactor: model: better matches --- src/textbuf/model.rs | 62 ++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/textbuf/model.rs b/src/textbuf/model.rs index 6fe444c..45b7a46 100644 --- a/src/textbuf/model.rs +++ b/src/textbuf/model.rs @@ -70,8 +70,6 @@ impl<'a> Model<'a> { /// If no named patterns were specified, it will search for all available /// patterns from the `PATTERNS` catalog. fn raw_matches(&self) -> Vec> { - let mut matches = Vec::new(); - let exclude_regexes = EXCLUDE_PATTERNS .iter() .map(|&(name, pattern)| (name, Regex::new(pattern).unwrap())) @@ -102,20 +100,25 @@ impl<'a> Model<'a> { let all_regexes = [exclude_regexes, custom_regexes, regexes].concat(); + let mut raw_matches = Vec::new(); + for (index, line) in self.lines.iter().enumerate() { - // Remainder of the line to be searched for matches. + // Chunk is the remainder of the line to be searched for matches. // This advances iteratively, until no matches can be found. let mut chunk: &str = line; let mut offset: i32 = 0; // Use all avail regexes to match the chunk and select the match // occuring the earliest on the chunk. Save its matched text and - // position in a `Match` struct. + // position in a `RawMatch` struct. loop { + // For each avalable regex, use the `find_iter` iterator to + // get the first non-overlapping match in the chunk, returning + // the start and end byte indices with respect to the chunk. let chunk_matches = all_regexes .iter() - .filter_map(|(&ref name, regex)| match regex.find_iter(chunk).next() { - Some(m) => Some((name, regex, m)), + .filter_map(|(&ref pat_name, reg)| match reg.find_iter(chunk).next() { + Some(reg_match) => Some((pat_name, reg, reg_match)), None => None, }) .collect::>(); @@ -125,40 +128,43 @@ impl<'a> Model<'a> { } // First match on the chunk. - let (name, pattern, matching) = chunk_matches + let (pat_name, reg, reg_match) = chunk_matches .iter() - .min_by(|x, y| x.2.start().cmp(&y.2.start())) + .min_by_key(|element| element.2.start()) .unwrap(); - let text = matching.as_str(); - - let captures = pattern - .captures(text) - .expect("At this stage the regex must have matched."); - - // Handle both capturing and non-capturing patterns. - let (subtext, substart) = if let Some(capture) = captures.get(1) { - (capture.as_str(), capture.start()) - } else { - (text, 0) - }; - // Never hint or break ansi color sequences. - if *name != "ansi_colors" { - matches.push(RawMatch { - x: offset + matching.start() as i32 + substart as i32, + if *pat_name != "ansi_colors" { + let text = reg_match.as_str(); + + // In case the pattern has a capturing group, try obtaining + // that text and start offset, else use the entire match. + let (subtext, substart) = match reg + .captures_iter(text) + .next() + .expect("This regex is guaranteed to match.") + .get(1) + { + Some(capture) => (capture.as_str(), capture.start()), + None => (text, 0), + }; + + raw_matches.push(RawMatch { + x: offset + reg_match.start() as i32 + substart as i32, y: index as i32, - pattern: name, + pattern: pat_name, text: subtext, }); } - chunk = chunk.get(matching.end()..).expect("Unknown chunk"); - offset += matching.end() as i32; + chunk = chunk + .get(reg_match.end()..) + .expect("The chunk must be larger than the regex match."); + offset += reg_match.end() as i32; } } - matches + raw_matches } /// Associate a hint to each `RawMatch`, returning a vector of `Match`es. From 6ba1bd94540e8a17fa0d202a21a643e02815356f Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 20 Mar 2021 19:53:38 +0100 Subject: [PATCH 32/50] refactor: HintStyle -> ui/hint_style.rs --- src/ui/hint_style.rs | 15 +++++++++++++++ src/ui/mod.rs | 4 +++- src/ui/vc.rs | 17 +---------------- 3 files changed, 19 insertions(+), 17 deletions(-) create mode 100644 src/ui/hint_style.rs diff --git a/src/ui/hint_style.rs b/src/ui/hint_style.rs new file mode 100644 index 0000000..84cf943 --- /dev/null +++ b/src/ui/hint_style.rs @@ -0,0 +1,15 @@ +/// Describes the style of contrast to be used during rendering of the hint's +/// text. +/// +/// # Note +/// In practice, this is wrapped in an `Option`, so that the hint's text can be rendered with no style. +pub enum HintStyle { + /// The hint's text will be bold (leveraging `termion::style::Bold`). + Bold, + /// The hint's text will be italicized (leveraging `termion::style::Italic`). + Italic, + /// The hint's text will be underlined (leveraging `termion::style::Underline`). + Underline, + /// The hint's text will be surrounded by these chars. + Surround(char, char), +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 753c9f9..154cd0e 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -18,9 +18,11 @@ //! pub mod colors; +pub mod hint_style; mod selection; mod vc; +pub use hint_style::HintStyle; pub use selection::Selection; +pub use vc::HintAlignment; pub use vc::ViewController; -pub use vc::{HintAlignment, HintStyle}; diff --git a/src/ui/vc.rs b/src/ui/vc.rs index 0cd0c54..c2ffca9 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -8,6 +8,7 @@ use sequence_trie::SequenceTrie; use termion::{self, color, cursor, event, style}; use super::colors::UiColors; +use super::HintStyle; use super::Selection; use crate::error::ParseError; use crate::{output_destination::OutputDestination, textbuf}; @@ -629,22 +630,6 @@ impl FromStr for HintAlignment { } } -/// Describes the style of contrast to be used during rendering of the hint's -/// text. -/// -/// # Note -/// In practice, this is wrapped in an `Option`, so that the hint's text can be rendered with no style. -pub enum HintStyle { - /// The hint's text will be bold (leveraging `termion::style::Bold`). - Bold, - /// The hint's text will be italicized (leveraging `termion::style::Italic`). - Italic, - /// The hint's text will be underlined (leveraging `termion::style::Underline`). - Underline, - /// The hint's text will be surrounded by these chars. - Surround(char, char), -} - /// Returned value after the `Ui` has finished listening to events. enum Event { /// Exit with no selected matches, From 7c7799ffbdbd26e456655e7b08c5243a6f9d3a8f Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 20 Mar 2021 19:58:31 +0100 Subject: [PATCH 33/50] refactor: HintAlignment -> ui/hint_alignment.rs --- src/ui/hint_alignment.rs | 26 ++++++++++++++++++++++++++ src/ui/mod.rs | 3 ++- src/ui/vc.rs | 27 +-------------------------- 3 files changed, 29 insertions(+), 27 deletions(-) create mode 100644 src/ui/hint_alignment.rs diff --git a/src/ui/hint_alignment.rs b/src/ui/hint_alignment.rs new file mode 100644 index 0000000..610bdb5 --- /dev/null +++ b/src/ui/hint_alignment.rs @@ -0,0 +1,26 @@ +use clap::Clap; +use std::str::FromStr; + +use crate::error::ParseError; + +/// Describes if, during rendering, a hint should aligned to the leading edge of +/// the matched text, or to its trailing edge. +#[derive(Debug, Clap)] +pub enum HintAlignment { + Leading, + Trailing, +} + +impl FromStr for HintAlignment { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + match s { + "leading" => Ok(HintAlignment::Leading), + "trailing" => Ok(HintAlignment::Trailing), + _ => Err(ParseError::ExpectedString(String::from( + "leading or trailing", + ))), + } + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 154cd0e..2db4d8d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -18,11 +18,12 @@ //! pub mod colors; +pub mod hint_alignment; pub mod hint_style; mod selection; mod vc; +pub use hint_alignment::HintAlignment; pub use hint_style::HintStyle; pub use selection::Selection; -pub use vc::HintAlignment; pub use vc::ViewController; diff --git a/src/ui/vc.rs b/src/ui/vc.rs index c2ffca9..8ec32be 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -1,16 +1,13 @@ use std::char; use std::cmp; use std::io; -use std::str::FromStr; -use clap::Clap; use sequence_trie::SequenceTrie; use termion::{self, color, cursor, event, style}; use super::colors::UiColors; -use super::HintStyle; use super::Selection; -use crate::error::ParseError; +use super::{HintAlignment, HintStyle}; use crate::{output_destination::OutputDestination, textbuf}; pub struct ViewController<'a> { @@ -608,28 +605,6 @@ fn get_line_offsets(lines: &[&str], term_width: u16) -> Vec { .collect() } -/// Describes if, during rendering, a hint should aligned to the leading edge of -/// the matched text, or to its trailing edge. -#[derive(Debug, Clap)] -pub enum HintAlignment { - Leading, - Trailing, -} - -impl FromStr for HintAlignment { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - match s { - "leading" => Ok(HintAlignment::Leading), - "trailing" => Ok(HintAlignment::Trailing), - _ => Err(ParseError::ExpectedString(String::from( - "leading or trailing", - ))), - } - } -} - /// Returned value after the `Ui` has finished listening to events. enum Event { /// Exit with no selected matches, From 9426d8bfee44477a7feec02dc02e9d5881288f70 Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 20 Mar 2021 22:54:14 +0100 Subject: [PATCH 34/50] refactor: tmux -> comm/tmux.rs --- src/bin/tmux_copyrat.rs | 2 +- src/comm/mod.rs | 1 + src/{ => comm}/tmux.rs | 0 src/lib.rs | 2 +- 4 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/comm/mod.rs rename src/{ => comm}/tmux.rs (100%) diff --git a/src/bin/tmux_copyrat.rs b/src/bin/tmux_copyrat.rs index b523baa..702ad41 100644 --- a/src/bin/tmux_copyrat.rs +++ b/src/bin/tmux_copyrat.rs @@ -2,7 +2,7 @@ use clap::Clap; use std::collections::HashMap; use std::str::FromStr; -use copyrat::{error, output_destination::OutputDestination, tmux, ui::Selection, CliOpt}; +use copyrat::{comm::tmux, error, output_destination::OutputDestination, ui::Selection, CliOpt}; /// Main configuration, parsed from command line. #[derive(Clap, Debug)] diff --git a/src/comm/mod.rs b/src/comm/mod.rs new file mode 100644 index 0000000..ee3d575 --- /dev/null +++ b/src/comm/mod.rs @@ -0,0 +1 @@ +pub mod tmux; diff --git a/src/tmux.rs b/src/comm/tmux.rs similarity index 100% rename from src/tmux.rs rename to src/comm/tmux.rs diff --git a/src/lib.rs b/src/lib.rs index a8ae97c..d8825b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ +pub mod comm; pub mod error; pub mod output_destination; pub mod textbuf; -pub mod tmux; pub mod ui; use clap::Clap; From 16fa4d45c1e18d699d02c07df4265495251bd9b7 Mon Sep 17 00:00:00 2001 From: graelo Date: Sat, 20 Mar 2021 22:58:09 +0100 Subject: [PATCH 35/50] refactor: outputdestination.rs -> comm/output_destination.rs --- src/bin/tmux_copyrat.rs | 7 ++++++- src/comm/mod.rs | 3 +++ src/{ => comm}/output_destination.rs | 0 src/lib.rs | 3 +-- src/ui/selection.rs | 2 +- src/ui/vc.rs | 2 +- 6 files changed, 12 insertions(+), 5 deletions(-) rename src/{ => comm}/output_destination.rs (100%) diff --git a/src/bin/tmux_copyrat.rs b/src/bin/tmux_copyrat.rs index 702ad41..8cb4299 100644 --- a/src/bin/tmux_copyrat.rs +++ b/src/bin/tmux_copyrat.rs @@ -2,7 +2,12 @@ use clap::Clap; use std::collections::HashMap; use std::str::FromStr; -use copyrat::{comm::tmux, error, output_destination::OutputDestination, ui::Selection, CliOpt}; +use copyrat::{ + comm::{tmux, OutputDestination}, + error, + ui::Selection, + CliOpt, +}; /// Main configuration, parsed from command line. #[derive(Clap, Debug)] diff --git a/src/comm/mod.rs b/src/comm/mod.rs index ee3d575..288979e 100644 --- a/src/comm/mod.rs +++ b/src/comm/mod.rs @@ -1 +1,4 @@ +mod output_destination; pub mod tmux; + +pub use output_destination::OutputDestination; diff --git a/src/output_destination.rs b/src/comm/output_destination.rs similarity index 100% rename from src/output_destination.rs rename to src/comm/output_destination.rs diff --git a/src/lib.rs b/src/lib.rs index d8825b9..7ef5227 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ pub mod comm; pub mod error; -pub mod output_destination; pub mod textbuf; pub mod ui; @@ -40,7 +39,7 @@ pub fn run(buffer: String, opt: &CliOpt) -> Option { }, }; - let default_output_destination = output_destination::OutputDestination::Tmux; + let default_output_destination = comm::OutputDestination::Tmux; let selection: Option = { let mut ui = ui::ViewController::new( diff --git a/src/ui/selection.rs b/src/ui/selection.rs index 374a59d..efa8ed8 100644 --- a/src/ui/selection.rs +++ b/src/ui/selection.rs @@ -1,4 +1,4 @@ -use crate::output_destination::OutputDestination; +use crate::comm::OutputDestination; /// Represents the text selected by the user, along with if it was uppercased /// and the output destination (Tmux buffer or Clipboard). diff --git a/src/ui/vc.rs b/src/ui/vc.rs index 8ec32be..8966701 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -8,7 +8,7 @@ use termion::{self, color, cursor, event, style}; use super::colors::UiColors; use super::Selection; use super::{HintAlignment, HintStyle}; -use crate::{output_destination::OutputDestination, textbuf}; +use crate::{comm::OutputDestination, textbuf}; pub struct ViewController<'a> { model: &'a mut textbuf::Model<'a>, From ee14666333f7af56eedfcd00132442422077d3e6 Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 21 Mar 2021 07:12:14 +0100 Subject: [PATCH 36/50] refactor: CliOpt -> config/core.rs --- src/bin/copyrat.rs | 2 +- src/bin/tmux_copyrat.rs | 2 +- src/config/core.rs | 158 +++++++++++++++++++++++++++++++++++++ src/config/mod.rs | 3 + src/lib.rs | 167 ++-------------------------------------- 5 files changed, 169 insertions(+), 163 deletions(-) create mode 100644 src/config/core.rs create mode 100644 src/config/mod.rs diff --git a/src/bin/copyrat.rs b/src/bin/copyrat.rs index 4b0ab6f..592f19b 100644 --- a/src/bin/copyrat.rs +++ b/src/bin/copyrat.rs @@ -3,7 +3,7 @@ use std::fs::OpenOptions; use std::io::prelude::*; use std::io::{self, Read}; -use copyrat::{run, ui::Selection, CliOpt}; +use copyrat::{config::CliOpt, run, ui::Selection}; fn main() { let opt = CliOpt::parse(); diff --git a/src/bin/tmux_copyrat.rs b/src/bin/tmux_copyrat.rs index 8cb4299..b8faddb 100644 --- a/src/bin/tmux_copyrat.rs +++ b/src/bin/tmux_copyrat.rs @@ -4,9 +4,9 @@ use std::str::FromStr; use copyrat::{ comm::{tmux, OutputDestination}, + config::CliOpt, error, ui::Selection, - CliOpt, }; /// Main configuration, parsed from command line. diff --git a/src/config/core.rs b/src/config/core.rs new file mode 100644 index 0000000..9022f56 --- /dev/null +++ b/src/config/core.rs @@ -0,0 +1,158 @@ +use clap::Clap; +use std::collections::HashMap; +use std::path; +use std::str::FromStr; + +use crate::{ + error, + textbuf::{alphabet, regexes}, + ui, +}; + +/// Main configuration, parsed from command line. +#[derive(Clap, Debug)] +#[clap(author, about, version)] +pub struct CliOpt { + /// Alphabet to draw hints from. + /// + /// Possible values are "{A}", "{A}-homerow", "{A}-left-hand", + /// "{A}-right-hand", where "{A}" is one of "qwerty", "azerty", "qwertz" + /// "dvorak", "colemak". + /// + /// # Examples + /// + /// "qwerty", "dvorak-homerow", "azerty-right-hand". + #[clap(short = 'k', long, default_value = "dvorak", + parse(try_from_str = alphabet::parse_alphabet))] + pub alphabet: alphabet::Alphabet, + + /// Use all available regex patterns. + #[clap(short = 'A', long = "--all-patterns")] + pub use_all_patterns: bool, + + /// Pattern names to use ("email", ... see doc). + #[clap(short = 'x', long = "--pattern-name", parse(try_from_str = regexes::parse_pattern_name))] + pub named_patterns: Vec, + + /// Additional regex patterns ("foo*bar", etc). + #[clap(short = 'X', long = "--custom-pattern")] + pub custom_patterns: Vec, + + /// Assign hints starting from the bottom of the screen. + #[clap(short, long)] + pub reverse: bool, + + /// Keep the same hint for identical matches. + #[clap(short, long)] + pub unique_hint: bool, + + #[clap(flatten)] + pub colors: ui::colors::UiColors, + + /// Align hint with its match. + #[clap(long, arg_enum, default_value = "leading")] + pub hint_alignment: ui::HintAlignment, + + /// Move focus back to first/last match. + #[clap(long)] + pub focus_wrap_around: bool, + + /// Optional hint styling. + /// + /// Underline or surround the hint for increased visibility. + /// If not provided, only the hint colors will be used. + #[clap(short = 's', long, arg_enum)] + pub hint_style: Option, + + /// Chars surrounding each hint, used with `Surround` style. + #[clap(long, default_value = "{}", + parse(try_from_str = parse_chars))] + pub hint_surroundings: (char, char), + + /// Optional target path where to store the selected matches. + #[clap(short = 'o', long = "output", parse(from_os_str))] + pub target_path: Option, + + /// Describes if the uppercased marker should be added to the output, + /// indicating if hint key was uppercased. This is only used by + /// tmux-copyrat, so it is hidden (skipped) from the CLI. + #[clap(skip)] + pub uppercased_marker: bool, +} + +/// Type introduced due to parsing limitation, +/// as we cannot directly parse into ui::HintStyle. +#[derive(Debug, Clap)] +pub enum HintStyleCli { + Bold, + Italic, + Underline, + Surround, +} + +impl FromStr for HintStyleCli { + type Err = error::ParseError; + + fn from_str(s: &str) -> Result { + match s { + "leading" => Ok(HintStyleCli::Underline), + "trailing" => Ok(HintStyleCli::Surround), + _ => Err(error::ParseError::ExpectedString(String::from( + "underline or surround", + ))), + } + } +} + +/// Try to parse a `&str` into a tuple of `char`s. +fn parse_chars(src: &str) -> Result<(char, char), error::ParseError> { + if src.len() != 2 { + return Err(error::ParseError::ExpectedSurroundingPair); + } + + let chars: Vec = src.chars().collect(); + Ok((chars[0], chars[1])) +} + +impl CliOpt { + /// Try parsing provided options, and update self with the valid values. + pub fn merge_map( + &mut self, + options: &HashMap, + ) -> Result<(), error::ParseError> { + for (name, value) in options { + match name.as_ref() { + "@copyrat-alphabet" => { + self.alphabet = alphabet::parse_alphabet(value)?; + } + "@copyrat-pattern-name" => { + self.named_patterns = vec![regexes::parse_pattern_name(value)?] + } + "@copyrat-custom-pattern" => self.custom_patterns = vec![String::from(value)], + "@copyrat-reverse" => { + self.reverse = value.parse::()?; + } + "@copyrat-unique-hint" => { + self.unique_hint = value.parse::()?; + } + + "@copyrat-match-fg" => self.colors.match_fg = ui::colors::parse_color(value)?, + "@copyrat-match-bg" => self.colors.match_bg = ui::colors::parse_color(value)?, + "@copyrat-focused-fg" => self.colors.focused_fg = ui::colors::parse_color(value)?, + "@copyrat-focused-bg" => self.colors.focused_bg = ui::colors::parse_color(value)?, + "@copyrat-hint-fg" => self.colors.hint_fg = ui::colors::parse_color(value)?, + "@copyrat-hint-bg" => self.colors.hint_bg = ui::colors::parse_color(value)?, + + "@copyrat-hint-alignment" => { + self.hint_alignment = ui::HintAlignment::from_str(&value)? + } + "@copyrat-hint-style" => self.hint_style = Some(HintStyleCli::from_str(&value)?), + + // Ignore unknown options. + _ => (), + } + } + + Ok(()) + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..b834c28 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,3 @@ +mod core; + +pub use self::core::{CliOpt, HintStyleCli}; diff --git a/src/lib.rs b/src/lib.rs index 7ef5227..42535d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,22 +1,15 @@ pub mod comm; +pub mod config; pub mod error; pub mod textbuf; pub mod ui; -use clap::Clap; -use std::collections::HashMap; -use std::path; -use std::str::FromStr; - -use crate::textbuf::alphabet; -use crate::textbuf::regexes; - /// Run copyrat on an input string `buffer`, configured by `Opt`. /// /// # Note /// /// Maybe the decision to take ownership of the buffer is a bit bold. -pub fn run(buffer: String, opt: &CliOpt) -> Option { +pub fn run(buffer: String, opt: &config::CliOpt) -> Option { let mut model = textbuf::Model::new( &buffer, &opt.alphabet, @@ -29,10 +22,10 @@ pub fn run(buffer: String, opt: &CliOpt) -> Option { let hint_style = match &opt.hint_style { None => None, Some(style) => match style { - HintStyleCli::Bold => Some(ui::HintStyle::Bold), - HintStyleCli::Italic => Some(ui::HintStyle::Italic), - HintStyleCli::Underline => Some(ui::HintStyle::Underline), - HintStyleCli::Surround => { + config::HintStyleCli::Bold => Some(ui::HintStyle::Bold), + config::HintStyleCli::Italic => Some(ui::HintStyle::Italic), + config::HintStyleCli::Underline => Some(ui::HintStyle::Underline), + config::HintStyleCli::Surround => { let (open, close) = opt.hint_surroundings; Some(ui::HintStyle::Surround(open, close)) } @@ -57,151 +50,3 @@ pub fn run(buffer: String, opt: &CliOpt) -> Option { selection } - -/// Main configuration, parsed from command line. -#[derive(Clap, Debug)] -#[clap(author, about, version)] -pub struct CliOpt { - /// Alphabet to draw hints from. - /// - /// Possible values are "{A}", "{A}-homerow", "{A}-left-hand", - /// "{A}-right-hand", where "{A}" is one of "qwerty", "azerty", "qwertz" - /// "dvorak", "colemak". - /// - /// # Examples - /// - /// "qwerty", "dvorak-homerow", "azerty-right-hand". - #[clap(short = 'k', long, default_value = "dvorak", - parse(try_from_str = alphabet::parse_alphabet))] - alphabet: alphabet::Alphabet, - - /// Use all available regex patterns. - #[clap(short = 'A', long = "--all-patterns")] - use_all_patterns: bool, - - /// Pattern names to use ("email", ... see doc). - #[clap(short = 'x', long = "--pattern-name", parse(try_from_str = regexes::parse_pattern_name))] - named_patterns: Vec, - - /// Additional regex patterns ("foo*bar", etc). - #[clap(short = 'X', long = "--custom-pattern")] - custom_patterns: Vec, - - /// Assign hints starting from the bottom of the screen. - #[clap(short, long)] - reverse: bool, - - /// Keep the same hint for identical matches. - #[clap(short, long)] - unique_hint: bool, - - #[clap(flatten)] - colors: ui::colors::UiColors, - - /// Align hint with its match. - #[clap(long, arg_enum, default_value = "leading")] - hint_alignment: ui::HintAlignment, - - /// Move focus back to first/last match. - #[clap(long)] - focus_wrap_around: bool, - - /// Optional hint styling. - /// - /// Underline or surround the hint for increased visibility. - /// If not provided, only the hint colors will be used. - #[clap(short = 's', long, arg_enum)] - hint_style: Option, - - /// Chars surrounding each hint, used with `Surround` style. - #[clap(long, default_value = "{}", - parse(try_from_str = parse_chars))] - hint_surroundings: (char, char), - - /// Optional target path where to store the selected matches. - #[clap(short = 'o', long = "output", parse(from_os_str))] - pub target_path: Option, - - /// Describes if the uppercased marker should be added to the output, - /// indicating if hint key was uppercased. This is only used by - /// tmux-copyrat, so it is hidden (skipped) from the CLI. - #[clap(skip)] - pub uppercased_marker: bool, -} - -/// Type introduced due to parsing limitation, -/// as we cannot directly parse into ui::HintStyle. -#[derive(Debug, Clap)] -enum HintStyleCli { - Bold, - Italic, - Underline, - Surround, -} - -impl FromStr for HintStyleCli { - type Err = error::ParseError; - - fn from_str(s: &str) -> Result { - match s { - "leading" => Ok(HintStyleCli::Underline), - "trailing" => Ok(HintStyleCli::Surround), - _ => Err(error::ParseError::ExpectedString(String::from( - "underline or surround", - ))), - } - } -} - -/// Try to parse a `&str` into a tuple of `char`s. -fn parse_chars(src: &str) -> Result<(char, char), error::ParseError> { - if src.len() != 2 { - return Err(error::ParseError::ExpectedSurroundingPair); - } - - let chars: Vec = src.chars().collect(); - Ok((chars[0], chars[1])) -} - -impl CliOpt { - /// Try parsing provided options, and update self with the valid values. - pub fn merge_map( - &mut self, - options: &HashMap, - ) -> Result<(), error::ParseError> { - for (name, value) in options { - match name.as_ref() { - "@copyrat-alphabet" => { - self.alphabet = alphabet::parse_alphabet(value)?; - } - "@copyrat-pattern-name" => { - self.named_patterns = vec![regexes::parse_pattern_name(value)?] - } - "@copyrat-custom-pattern" => self.custom_patterns = vec![String::from(value)], - "@copyrat-reverse" => { - self.reverse = value.parse::()?; - } - "@copyrat-unique-hint" => { - self.unique_hint = value.parse::()?; - } - - "@copyrat-match-fg" => self.colors.match_fg = ui::colors::parse_color(value)?, - "@copyrat-match-bg" => self.colors.match_bg = ui::colors::parse_color(value)?, - "@copyrat-focused-fg" => self.colors.focused_fg = ui::colors::parse_color(value)?, - "@copyrat-focused-bg" => self.colors.focused_bg = ui::colors::parse_color(value)?, - "@copyrat-hint-fg" => self.colors.hint_fg = ui::colors::parse_color(value)?, - "@copyrat-hint-bg" => self.colors.hint_bg = ui::colors::parse_color(value)?, - - "@copyrat-hint-alignment" => { - self.hint_alignment = ui::HintAlignment::from_str(&value)? - } - "@copyrat-hint-style" => self.hint_style = Some(HintStyleCli::from_str(&value)?), - - // Ignore unknown options. - _ => (), - } - } - - Ok(()) - } -} From 92507e66ce378d1b213f2a732dbd239ff8db4026 Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 21 Mar 2021 07:18:35 +0100 Subject: [PATCH 37/50] refactor: remove tmux-specific option in core_config --- src/config/core.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/config/core.rs b/src/config/core.rs index 9022f56..9e3795e 100644 --- a/src/config/core.rs +++ b/src/config/core.rs @@ -72,12 +72,6 @@ pub struct CliOpt { /// Optional target path where to store the selected matches. #[clap(short = 'o', long = "output", parse(from_os_str))] pub target_path: Option, - - /// Describes if the uppercased marker should be added to the output, - /// indicating if hint key was uppercased. This is only used by - /// tmux-copyrat, so it is hidden (skipped) from the CLI. - #[clap(skip)] - pub uppercased_marker: bool, } /// Type introduced due to parsing limitation, From 5d4f19d7baa04f5153957d0e0e8a1240e18e6465 Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 21 Mar 2021 07:59:31 +0100 Subject: [PATCH 38/50] refactor: BridgeOpt -> config/bridge.rs --- src/bin/tmux_copyrat.rs | 60 +------------------------------------- src/config/bridge.rs | 64 +++++++++++++++++++++++++++++++++++++++++ src/config/mod.rs | 2 ++ 3 files changed, 67 insertions(+), 59 deletions(-) create mode 100644 src/config/bridge.rs diff --git a/src/bin/tmux_copyrat.rs b/src/bin/tmux_copyrat.rs index b8faddb..22bc01e 100644 --- a/src/bin/tmux_copyrat.rs +++ b/src/bin/tmux_copyrat.rs @@ -1,71 +1,13 @@ use clap::Clap; use std::collections::HashMap; -use std::str::FromStr; use copyrat::{ comm::{tmux, OutputDestination}, - config::CliOpt, + config::BridgeOpt, error, ui::Selection, }; -/// Main configuration, parsed from command line. -#[derive(Clap, Debug)] -#[clap(author, about, version)] -struct BridgeOpt { - /// Don't read options from Tmux. - /// - /// By default, options formatted like `copyrat-*` are read from tmux. - /// However, you should consider reading them from the config file (the - /// default option) as this saves both a command call (about 10ms) and a - /// Regex compilation. - #[clap(long)] - ignore_options_from_tmux: bool, - - /// Name of the copyrat temporary window. - /// - /// Copyrat is launched in a temporary window of that name. The only pane - /// in this temp window gets swapped with the current active one for - /// in-place searching, then swapped back and killed after we exit. - #[clap(long, default_value = "[copyrat]")] - window_name: String, - - /// Capture visible area or entire pane history. - #[clap(long, arg_enum, default_value = "visible-area")] - capture_region: tmux::CaptureRegion, - - /// Name of the copy-to-clipboard executable. - /// - /// If during execution, the output destination is set to be clipboard, - /// then 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, -} - -impl BridgeOpt { - /// Try parsing provided options, and update self with the valid values. - /// Unknown options are simply ignored. - pub fn merge_map( - &mut self, - options: &HashMap, - ) -> Result<(), error::ParseError> { - for (name, value) in options { - if let "@copyrat-capture" = name.as_ref() { - self.capture_region = tmux::CaptureRegion::from_str(&value)?; - } - } - - // Pass the call to cli_options. - self.cli_options.merge_map(options)?; - - Ok(()) - } -} - /// fn main() -> Result<(), error::ParseError> { let mut opt = BridgeOpt::parse(); diff --git a/src/config/bridge.rs b/src/config/bridge.rs new file mode 100644 index 0000000..80a9561 --- /dev/null +++ b/src/config/bridge.rs @@ -0,0 +1,64 @@ +use clap::Clap; +use std::collections::HashMap; +use std::str::FromStr; + +use super::CliOpt; +use crate::comm::tmux; +use crate::error; + +/// Main configuration, parsed from command line. +#[derive(Clap, Debug)] +#[clap(author, about, version)] +pub struct BridgeOpt { + /// Don't read options from Tmux. + /// + /// By default, options formatted like `copyrat-*` are read from tmux. + /// However, you should consider reading them from the config file (the + /// default option) as this saves both a command call (about 10ms) and a + /// Regex compilation. + #[clap(long)] + pub ignore_options_from_tmux: bool, + + /// Name of the copyrat temporary window. + /// + /// Copyrat is launched in a temporary window of that name. The only pane + /// in this temp window gets swapped with the current active one for + /// in-place searching, then swapped back and killed after we exit. + #[clap(long, default_value = "[copyrat]")] + pub window_name: String, + + /// Capture visible area or entire pane history. + #[clap(long, arg_enum, default_value = "visible-area")] + pub capture_region: tmux::CaptureRegion, + + /// Name of the copy-to-clipboard executable. + /// + /// If during execution, the output destination is set to be clipboard, + /// then copyrat will pipe the selected text to this executable. + #[clap(long, default_value = "pbcopy")] + pub clipboard_exe: String, + + // Include CLI Options + #[clap(flatten)] + pub cli_options: CliOpt, +} + +impl BridgeOpt { + /// Try parsing provided options, and update self with the valid values. + /// Unknown options are simply ignored. + pub fn merge_map( + &mut self, + options: &HashMap, + ) -> Result<(), error::ParseError> { + for (name, value) in options { + if let "@copyrat-capture" = name.as_ref() { + self.capture_region = tmux::CaptureRegion::from_str(&value)?; + } + } + + // Pass the call to cli_options. + self.cli_options.merge_map(options)?; + + Ok(()) + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs index b834c28..13b14a4 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,3 +1,5 @@ +mod bridge; mod core; +pub use self::bridge::BridgeOpt; pub use self::core::{CliOpt, HintStyleCli}; From f539545eddb014205225b0f960469846c43b9f04 Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 21 Mar 2021 08:03:40 +0100 Subject: [PATCH 39/50] refactor: HintStyleCli -> HintStyleArg --- src/config/core.rs | 12 ++++++------ src/config/mod.rs | 2 +- src/lib.rs | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/config/core.rs b/src/config/core.rs index 9e3795e..7f94e4c 100644 --- a/src/config/core.rs +++ b/src/config/core.rs @@ -62,7 +62,7 @@ pub struct CliOpt { /// Underline or surround the hint for increased visibility. /// If not provided, only the hint colors will be used. #[clap(short = 's', long, arg_enum)] - pub hint_style: Option, + pub hint_style: Option, /// Chars surrounding each hint, used with `Surround` style. #[clap(long, default_value = "{}", @@ -77,20 +77,20 @@ pub struct CliOpt { /// Type introduced due to parsing limitation, /// as we cannot directly parse into ui::HintStyle. #[derive(Debug, Clap)] -pub enum HintStyleCli { +pub enum HintStyleArg { Bold, Italic, Underline, Surround, } -impl FromStr for HintStyleCli { +impl FromStr for HintStyleArg { type Err = error::ParseError; fn from_str(s: &str) -> Result { match s { - "leading" => Ok(HintStyleCli::Underline), - "trailing" => Ok(HintStyleCli::Surround), + "leading" => Ok(HintStyleArg::Underline), + "trailing" => Ok(HintStyleArg::Surround), _ => Err(error::ParseError::ExpectedString(String::from( "underline or surround", ))), @@ -140,7 +140,7 @@ impl CliOpt { "@copyrat-hint-alignment" => { self.hint_alignment = ui::HintAlignment::from_str(&value)? } - "@copyrat-hint-style" => self.hint_style = Some(HintStyleCli::from_str(&value)?), + "@copyrat-hint-style" => self.hint_style = Some(HintStyleArg::from_str(&value)?), // Ignore unknown options. _ => (), diff --git a/src/config/mod.rs b/src/config/mod.rs index 13b14a4..7163ce7 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,4 +2,4 @@ mod bridge; mod core; pub use self::bridge::BridgeOpt; -pub use self::core::{CliOpt, HintStyleCli}; +pub use self::core::{CliOpt, HintStyleArg}; diff --git a/src/lib.rs b/src/lib.rs index 42535d6..944a499 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,10 +22,10 @@ pub fn run(buffer: String, opt: &config::CliOpt) -> Option { let hint_style = match &opt.hint_style { None => None, Some(style) => match style { - config::HintStyleCli::Bold => Some(ui::HintStyle::Bold), - config::HintStyleCli::Italic => Some(ui::HintStyle::Italic), - config::HintStyleCli::Underline => Some(ui::HintStyle::Underline), - config::HintStyleCli::Surround => { + config::HintStyleArg::Bold => Some(ui::HintStyle::Bold), + config::HintStyleArg::Italic => Some(ui::HintStyle::Italic), + config::HintStyleArg::Underline => Some(ui::HintStyle::Underline), + config::HintStyleArg::Surround => { let (open, close) = opt.hint_surroundings; Some(ui::HintStyle::Surround(open, close)) } From d558c8118336fbc058e4d90e039b0dc2cd2359d1 Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 21 Mar 2021 08:14:02 +0100 Subject: [PATCH 40/50] refactor: config/core.rs -> config/basic.rs --- src/config/{core.rs => basic.rs} | 0 src/config/mod.rs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/config/{core.rs => basic.rs} (100%) diff --git a/src/config/core.rs b/src/config/basic.rs similarity index 100% rename from src/config/core.rs rename to src/config/basic.rs diff --git a/src/config/mod.rs b/src/config/mod.rs index 7163ce7..eeacadc 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,5 +1,5 @@ +mod basic; mod bridge; -mod core; +pub use self::basic::{CliOpt, HintStyleArg}; pub use self::bridge::BridgeOpt; -pub use self::core::{CliOpt, HintStyleArg}; From 0ee29303c6c6215fda4958665e998ac89b181dff Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 21 Mar 2021 08:51:45 +0100 Subject: [PATCH 41/50] refactor: config names --- src/bin/copyrat.rs | 4 ++-- src/bin/tmux_copyrat.rs | 16 ++++++++-------- src/config/basic.rs | 4 ++-- src/config/mod.rs | 7 ++----- src/config/{bridge.rs => tmux_bridge.rs} | 8 ++++---- src/lib.rs | 10 +++++----- 6 files changed, 23 insertions(+), 26 deletions(-) rename src/config/{bridge.rs => tmux_bridge.rs} (95%) diff --git a/src/bin/copyrat.rs b/src/bin/copyrat.rs index 592f19b..011e336 100644 --- a/src/bin/copyrat.rs +++ b/src/bin/copyrat.rs @@ -3,10 +3,10 @@ use std::fs::OpenOptions; use std::io::prelude::*; use std::io::{self, Read}; -use copyrat::{config::CliOpt, run, ui::Selection}; +use copyrat::{config::basic, run, ui::Selection}; fn main() { - let opt = CliOpt::parse(); + let opt = basic::Config::parse(); // Copy the pane contents (piped in via stdin) into a buffer, and split lines. let stdin = io::stdin(); diff --git a/src/bin/tmux_copyrat.rs b/src/bin/tmux_copyrat.rs index 22bc01e..01583f6 100644 --- a/src/bin/tmux_copyrat.rs +++ b/src/bin/tmux_copyrat.rs @@ -3,20 +3,20 @@ use std::collections::HashMap; use copyrat::{ comm::{tmux, OutputDestination}, - config::BridgeOpt, + config::tmux_bridge::Config, error, ui::Selection, }; /// fn main() -> Result<(), error::ParseError> { - let mut opt = BridgeOpt::parse(); + let mut config = Config::parse(); - if !opt.ignore_options_from_tmux { + if !config.ignore_options_from_tmux { let tmux_options: HashMap = tmux::get_options("@copyrat-")?; // Override default values with those coming from tmux. - opt.merge_map(&tmux_options)?; + config.merge_map(&tmux_options)?; } // Identify active pane and capture its content. @@ -27,15 +27,15 @@ fn main() -> Result<(), error::ParseError> { .find(|p| p.is_active) .expect("Exactly one tmux pane should be active in the current window."); - let buffer = tmux::capture_pane(&active_pane, &opt.capture_region)?; + let buffer = tmux::capture_pane(&active_pane, &config.capture_region)?; // 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); + let temp_pane_spec = format!("{}.0", config.window_name); tmux::swap_pane_with(&temp_pane_spec)?; - let selection = copyrat::run(buffer, &opt.cli_options); + let selection = copyrat::run(buffer, &config.cli_options); tmux::swap_pane_with(&temp_pane_spec)?; @@ -59,7 +59,7 @@ fn main() -> Result<(), error::ParseError> { } OutputDestination::Clipboard => { duct::cmd!("echo", "-n", &text) - .pipe(duct::cmd!(opt.clipboard_exe)) + .pipe(duct::cmd!(config.clipboard_exe)) .read()?; } } diff --git a/src/config/basic.rs b/src/config/basic.rs index 7f94e4c..c42a117 100644 --- a/src/config/basic.rs +++ b/src/config/basic.rs @@ -12,7 +12,7 @@ use crate::{ /// Main configuration, parsed from command line. #[derive(Clap, Debug)] #[clap(author, about, version)] -pub struct CliOpt { +pub struct Config { /// Alphabet to draw hints from. /// /// Possible values are "{A}", "{A}-homerow", "{A}-left-hand", @@ -108,7 +108,7 @@ fn parse_chars(src: &str) -> Result<(char, char), error::ParseError> { Ok((chars[0], chars[1])) } -impl CliOpt { +impl Config { /// Try parsing provided options, and update self with the valid values. pub fn merge_map( &mut self, diff --git a/src/config/mod.rs b/src/config/mod.rs index eeacadc..774706f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,5 +1,2 @@ -mod basic; -mod bridge; - -pub use self::basic::{CliOpt, HintStyleArg}; -pub use self::bridge::BridgeOpt; +pub mod basic; +pub mod tmux_bridge; diff --git a/src/config/bridge.rs b/src/config/tmux_bridge.rs similarity index 95% rename from src/config/bridge.rs rename to src/config/tmux_bridge.rs index 80a9561..f07bbe7 100644 --- a/src/config/bridge.rs +++ b/src/config/tmux_bridge.rs @@ -2,14 +2,14 @@ use clap::Clap; use std::collections::HashMap; use std::str::FromStr; -use super::CliOpt; +use super::basic; use crate::comm::tmux; use crate::error; /// Main configuration, parsed from command line. #[derive(Clap, Debug)] #[clap(author, about, version)] -pub struct BridgeOpt { +pub struct Config { /// Don't read options from Tmux. /// /// By default, options formatted like `copyrat-*` are read from tmux. @@ -40,10 +40,10 @@ pub struct BridgeOpt { // Include CLI Options #[clap(flatten)] - pub cli_options: CliOpt, + pub cli_options: basic::Config, } -impl BridgeOpt { +impl Config { /// Try parsing provided options, and update self with the valid values. /// Unknown options are simply ignored. pub fn merge_map( diff --git a/src/lib.rs b/src/lib.rs index 944a499..9089912 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ pub mod ui; /// # Note /// /// Maybe the decision to take ownership of the buffer is a bit bold. -pub fn run(buffer: String, opt: &config::CliOpt) -> Option { +pub fn run(buffer: String, opt: &config::basic::Config) -> Option { let mut model = textbuf::Model::new( &buffer, &opt.alphabet, @@ -22,10 +22,10 @@ pub fn run(buffer: String, opt: &config::CliOpt) -> Option { let hint_style = match &opt.hint_style { None => None, Some(style) => match style { - config::HintStyleArg::Bold => Some(ui::HintStyle::Bold), - config::HintStyleArg::Italic => Some(ui::HintStyle::Italic), - config::HintStyleArg::Underline => Some(ui::HintStyle::Underline), - config::HintStyleArg::Surround => { + config::basic::HintStyleArg::Bold => Some(ui::HintStyle::Bold), + config::basic::HintStyleArg::Italic => Some(ui::HintStyle::Italic), + config::basic::HintStyleArg::Underline => Some(ui::HintStyle::Underline), + config::basic::HintStyleArg::Surround => { let (open, close) = opt.hint_surroundings; Some(ui::HintStyle::Surround(open, close)) } From d598d5962e2061eb0ebe5bd0cd769a670007e093 Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 21 Mar 2021 09:03:22 +0100 Subject: [PATCH 42/50] refactor: refactor config --- src/bin/tmux_copyrat.rs | 14 ++------------ src/config/tmux_bridge.rs | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/bin/tmux_copyrat.rs b/src/bin/tmux_copyrat.rs index 01583f6..caf1200 100644 --- a/src/bin/tmux_copyrat.rs +++ b/src/bin/tmux_copyrat.rs @@ -1,6 +1,3 @@ -use clap::Clap; -use std::collections::HashMap; - use copyrat::{ comm::{tmux, OutputDestination}, config::tmux_bridge::Config, @@ -10,14 +7,7 @@ use copyrat::{ /// fn main() -> Result<(), error::ParseError> { - let mut config = Config::parse(); - - if !config.ignore_options_from_tmux { - let tmux_options: HashMap = tmux::get_options("@copyrat-")?; - - // Override default values with those coming from tmux. - config.merge_map(&tmux_options)?; - } + let config = Config::initialize()?; // Identify active pane and capture its content. let panes: Vec = tmux::list_panes()?; @@ -35,7 +25,7 @@ fn main() -> Result<(), error::ParseError> { let temp_pane_spec = format!("{}.0", config.window_name); tmux::swap_pane_with(&temp_pane_spec)?; - let selection = copyrat::run(buffer, &config.cli_options); + let selection = copyrat::run(buffer, &config.basic_config); tmux::swap_pane_with(&temp_pane_spec)?; diff --git a/src/config/tmux_bridge.rs b/src/config/tmux_bridge.rs index f07bbe7..3b6b8d6 100644 --- a/src/config/tmux_bridge.rs +++ b/src/config/tmux_bridge.rs @@ -38,12 +38,24 @@ pub struct Config { #[clap(long, default_value = "pbcopy")] pub clipboard_exe: String, - // Include CLI Options + // Include fields from the basic config #[clap(flatten)] - pub cli_options: basic::Config, + pub basic_config: basic::Config, } impl Config { + pub fn initialize() -> Result { + let mut config = Config::parse(); + + if !config.ignore_options_from_tmux { + let tmux_options: HashMap = tmux::get_options("@copyrat-")?; + + // Override default values with those coming from tmux. + config.merge_map(&tmux_options)?; + } + + Ok(config) + } /// Try parsing provided options, and update self with the valid values. /// Unknown options are simply ignored. pub fn merge_map( @@ -57,7 +69,7 @@ impl Config { } // Pass the call to cli_options. - self.cli_options.merge_map(options)?; + self.basic_config.merge_map(options)?; Ok(()) } From 2b331c2862e1d297fee170471e6814f34a03b91a Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 21 Mar 2021 09:20:34 +0100 Subject: [PATCH 43/50] refactor: CaptureRegion -> config/tmux_bridge.rs --- src/comm/tmux.rs | 34 +++------------------------------- src/config/tmux_bridge.rs | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/comm/tmux.rs b/src/comm/tmux.rs index 1649af1..1cc1fce 100644 --- a/src/comm/tmux.rs +++ b/src/comm/tmux.rs @@ -1,9 +1,9 @@ -use clap::Clap; use regex::Regex; use std::collections::HashMap; use std::fmt; use std::str::FromStr; +use crate::config::tmux_bridge::CaptureRegion; use crate::error::ParseError; #[derive(Debug, PartialEq)] @@ -108,34 +108,6 @@ impl fmt::Display for PaneId { } } -#[derive(Clap, Debug)] -pub enum CaptureRegion { - /// The entire history. - /// - /// This will end up sending `-S - -E -` to `tmux capture-pane`. - EntireHistory, - /// The visible area. - VisibleArea, - ///// Region from start line to end line - ///// - ///// This works as defined in tmux's docs (order does not matter). - //Region(i32, i32), -} - -impl FromStr for CaptureRegion { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - match s { - "leading" => Ok(CaptureRegion::EntireHistory), - "trailing" => Ok(CaptureRegion::VisibleArea), - _ => Err(ParseError::ExpectedString(String::from( - "entire-history or visible-area", - ))), - } - } -} - /// Returns a list of `Pane` from the current tmux session. pub fn list_panes() -> Result, ParseError> { let args = vec![ @@ -187,8 +159,8 @@ pub fn get_options(prefix: &str) -> Result, ParseError> /// Returns the entire Pane content as a `String`. /// -/// `CaptureRegion` specifies if the visible area is captured, or the entire -/// history. +/// The provided `region` specifies if the visible area is captured, or the +/// entire history. /// /// # TODO /// diff --git a/src/config/tmux_bridge.rs b/src/config/tmux_bridge.rs index 3b6b8d6..d4e329f 100644 --- a/src/config/tmux_bridge.rs +++ b/src/config/tmux_bridge.rs @@ -29,7 +29,7 @@ pub struct Config { /// Capture visible area or entire pane history. #[clap(long, arg_enum, default_value = "visible-area")] - pub capture_region: tmux::CaptureRegion, + pub capture_region: CaptureRegion, /// Name of the copy-to-clipboard executable. /// @@ -64,7 +64,7 @@ impl Config { ) -> Result<(), error::ParseError> { for (name, value) in options { if let "@copyrat-capture" = name.as_ref() { - self.capture_region = tmux::CaptureRegion::from_str(&value)?; + self.capture_region = CaptureRegion::from_str(&value)?; } } @@ -74,3 +74,31 @@ impl Config { Ok(()) } } + +#[derive(Clap, Debug)] +pub enum CaptureRegion { + /// The entire history. + /// + /// This will end up sending `-S - -E -` to `tmux capture-pane`. + EntireHistory, + /// The visible area. + VisibleArea, + ///// Region from start line to end line + ///// + ///// This works as defined in tmux's docs (order does not matter). + //Region(i32, i32), +} + +impl FromStr for CaptureRegion { + type Err = error::ParseError; + + fn from_str(s: &str) -> Result { + match s { + "leading" => Ok(CaptureRegion::EntireHistory), + "trailing" => Ok(CaptureRegion::VisibleArea), + _ => Err(error::ParseError::ExpectedString(String::from( + "entire-history or visible-area", + ))), + } + } +} From a26a4a56d85214c7ae3d07a2982c6443aababb91 Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 21 Mar 2021 11:32:57 +0100 Subject: [PATCH 44/50] refactor: refactor config --- src/bin/copyrat.rs | 18 +-------- src/config/basic.rs | 59 +++------------------------- src/config/tmux_bridge.rs | 81 +++++++++++++++++++++++++++------------ src/ui/colors.rs | 3 +- 4 files changed, 65 insertions(+), 96 deletions(-) diff --git a/src/bin/copyrat.rs b/src/bin/copyrat.rs index 011e336..5a3f565 100644 --- a/src/bin/copyrat.rs +++ b/src/bin/copyrat.rs @@ -1,6 +1,4 @@ use clap::Clap; -use std::fs::OpenOptions; -use std::io::prelude::*; use std::io::{self, Read}; use copyrat::{config::basic, run, ui::Selection}; @@ -25,19 +23,5 @@ fn main() { } let Selection { text, .. } = selection.unwrap(); - - // Write output to a target_path if provided, else print to original stdout. - match opt.target_path { - None => println!("{}", text), - Some(target) => { - let mut file = OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(target) - .expect("Unable to open the target file"); - - file.write_all(text.as_bytes()).unwrap(); - } - } + println!("{}", text); } diff --git a/src/config/basic.rs b/src/config/basic.rs index c42a117..3859d6e 100644 --- a/src/config/basic.rs +++ b/src/config/basic.rs @@ -1,6 +1,4 @@ use clap::Clap; -use std::collections::HashMap; -use std::path; use std::str::FromStr; use crate::{ @@ -46,6 +44,10 @@ pub struct Config { #[clap(short, long)] pub unique_hint: bool, + /// Move focus back to first/last match. + #[clap(short = 'w', long)] + pub focus_wrap_around: bool, + #[clap(flatten)] pub colors: ui::colors::UiColors, @@ -53,10 +55,6 @@ pub struct Config { #[clap(long, arg_enum, default_value = "leading")] pub hint_alignment: ui::HintAlignment, - /// Move focus back to first/last match. - #[clap(long)] - pub focus_wrap_around: bool, - /// Optional hint styling. /// /// Underline or surround the hint for increased visibility. @@ -68,10 +66,6 @@ pub struct Config { #[clap(long, default_value = "{}", parse(try_from_str = parse_chars))] pub hint_surroundings: (char, char), - - /// Optional target path where to store the selected matches. - #[clap(short = 'o', long = "output", parse(from_os_str))] - pub target_path: Option, } /// Type introduced due to parsing limitation, @@ -100,53 +94,10 @@ impl FromStr for HintStyleArg { /// Try to parse a `&str` into a tuple of `char`s. fn parse_chars(src: &str) -> Result<(char, char), error::ParseError> { - if src.len() != 2 { + if src.chars().count() != 2 { return Err(error::ParseError::ExpectedSurroundingPair); } let chars: Vec = src.chars().collect(); Ok((chars[0], chars[1])) } - -impl Config { - /// Try parsing provided options, and update self with the valid values. - pub fn merge_map( - &mut self, - options: &HashMap, - ) -> Result<(), error::ParseError> { - for (name, value) in options { - match name.as_ref() { - "@copyrat-alphabet" => { - self.alphabet = alphabet::parse_alphabet(value)?; - } - "@copyrat-pattern-name" => { - self.named_patterns = vec![regexes::parse_pattern_name(value)?] - } - "@copyrat-custom-pattern" => self.custom_patterns = vec![String::from(value)], - "@copyrat-reverse" => { - self.reverse = value.parse::()?; - } - "@copyrat-unique-hint" => { - self.unique_hint = value.parse::()?; - } - - "@copyrat-match-fg" => self.colors.match_fg = ui::colors::parse_color(value)?, - "@copyrat-match-bg" => self.colors.match_bg = ui::colors::parse_color(value)?, - "@copyrat-focused-fg" => self.colors.focused_fg = ui::colors::parse_color(value)?, - "@copyrat-focused-bg" => self.colors.focused_bg = ui::colors::parse_color(value)?, - "@copyrat-hint-fg" => self.colors.hint_fg = ui::colors::parse_color(value)?, - "@copyrat-hint-bg" => self.colors.hint_bg = ui::colors::parse_color(value)?, - - "@copyrat-hint-alignment" => { - self.hint_alignment = ui::HintAlignment::from_str(&value)? - } - "@copyrat-hint-style" => self.hint_style = Some(HintStyleArg::from_str(&value)?), - - // Ignore unknown options. - _ => (), - } - } - - Ok(()) - } -} diff --git a/src/config/tmux_bridge.rs b/src/config/tmux_bridge.rs index d4e329f..de9cb01 100644 --- a/src/config/tmux_bridge.rs +++ b/src/config/tmux_bridge.rs @@ -3,8 +3,12 @@ use std::collections::HashMap; use std::str::FromStr; use super::basic; -use crate::comm::tmux; -use crate::error; +use crate::{ + comm::tmux, + error, + textbuf::{alphabet, regexes}, + ui, +}; /// Main configuration, parsed from command line. #[derive(Clap, Debug)] @@ -16,15 +20,15 @@ pub struct Config { /// However, you should consider reading them from the config file (the /// default option) as this saves both a command call (about 10ms) and a /// Regex compilation. - #[clap(long)] - pub ignore_options_from_tmux: bool, + #[clap(short = 'n', long)] + pub ignore_tmux_options: bool, - /// Name of the copyrat temporary window. + /// Name of the copyrat temporary Tmux window. /// /// Copyrat is launched in a temporary window of that name. The only pane /// in this temp window gets swapped with the current active one for /// in-place searching, then swapped back and killed after we exit. - #[clap(long, default_value = "[copyrat]")] + #[clap(short = 'W', long, default_value = "[copyrat]")] pub window_name: String, /// Capture visible area or entire pane history. @@ -47,31 +51,60 @@ impl Config { pub fn initialize() -> Result { let mut config = Config::parse(); - if !config.ignore_options_from_tmux { + if !config.ignore_tmux_options { let tmux_options: HashMap = tmux::get_options("@copyrat-")?; // Override default values with those coming from tmux. - config.merge_map(&tmux_options)?; - } + let wrapped = &mut config.basic_config; - Ok(config) - } - /// Try parsing provided options, and update self with the valid values. - /// Unknown options are simply ignored. - pub fn merge_map( - &mut self, - options: &HashMap, - ) -> Result<(), error::ParseError> { - for (name, value) in options { - if let "@copyrat-capture" = name.as_ref() { - self.capture_region = CaptureRegion::from_str(&value)?; + for (name, value) in &tmux_options { + match name.as_ref() { + "@copyrat-capture" => config.capture_region = CaptureRegion::from_str(&value)?, + "@copyrat-alphabet" => { + wrapped.alphabet = alphabet::parse_alphabet(value)?; + } + "@copyrat-pattern-name" => { + wrapped.named_patterns = vec![regexes::parse_pattern_name(value)?] + } + "@copyrat-custom-pattern" => { + wrapped.custom_patterns = vec![String::from(value)] + } + "@copyrat-reverse" => { + wrapped.reverse = value.parse::()?; + } + "@copyrat-unique-hint" => { + wrapped.unique_hint = value.parse::()?; + } + + "@copyrat-match-fg" => { + wrapped.colors.match_fg = ui::colors::parse_color(value)? + } + "@copyrat-match-bg" => { + wrapped.colors.match_bg = ui::colors::parse_color(value)? + } + "@copyrat-focused-fg" => { + wrapped.colors.focused_fg = ui::colors::parse_color(value)? + } + "@copyrat-focused-bg" => { + wrapped.colors.focused_bg = ui::colors::parse_color(value)? + } + "@copyrat-hint-fg" => wrapped.colors.hint_fg = ui::colors::parse_color(value)?, + "@copyrat-hint-bg" => wrapped.colors.hint_bg = ui::colors::parse_color(value)?, + + "@copyrat-hint-alignment" => { + wrapped.hint_alignment = ui::HintAlignment::from_str(&value)? + } + "@copyrat-hint-style" => { + wrapped.hint_style = Some(basic::HintStyleArg::from_str(&value)?) + } + + // Ignore unknown options. + _ => (), + } } } - // Pass the call to cli_options. - self.basic_config.merge_map(options)?; - - Ok(()) + Ok(config) } } diff --git a/src/ui/colors.rs b/src/ui/colors.rs index 19bf50e..daa8674 100644 --- a/src/ui/colors.rs +++ b/src/ui/colors.rs @@ -47,12 +47,13 @@ mod tests { } } -/// Holds color-related data, for clarity. +/// Holds color-related data. /// /// - `focus_*` colors are used to render the currently focused matched text. /// - `normal_*` colors are used to render other matched text. /// - `hint_*` colors are used to render the hints. #[derive(Clap, Debug)] +#[clap(about)] // Needed to avoid this doc comment to be used as overall `about`. pub struct UiColors { /// Foreground color for base text. #[clap(long, default_value = "bright-cyan", parse(try_from_str = parse_color))] From d4fb6c417be11dcd8436c4503c4bdec54b8d0ccb Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 21 Mar 2021 11:49:17 +0100 Subject: [PATCH 45/50] refactor: OutputDestination -> config/tmux_bridge.rs --- src/bin/tmux_copyrat.rs | 5 ++--- src/comm/mod.rs | 4 ---- src/comm/output_destination.rs | 30 ------------------------------ src/config/tmux_bridge.rs | 33 +++++++++++++++++++++++++++++++-- src/lib.rs | 4 ++-- src/{comm => }/tmux.rs | 5 +++++ src/ui/selection.rs | 2 +- src/ui/vc.rs | 2 +- 8 files changed, 42 insertions(+), 43 deletions(-) delete mode 100644 src/comm/mod.rs delete mode 100644 src/comm/output_destination.rs rename src/{comm => }/tmux.rs (98%) diff --git a/src/bin/tmux_copyrat.rs b/src/bin/tmux_copyrat.rs index caf1200..f0cf8da 100644 --- a/src/bin/tmux_copyrat.rs +++ b/src/bin/tmux_copyrat.rs @@ -1,7 +1,6 @@ use copyrat::{ - comm::{tmux, OutputDestination}, - config::tmux_bridge::Config, - error, + config::tmux_bridge::{Config, OutputDestination}, + error, tmux, ui::Selection, }; diff --git a/src/comm/mod.rs b/src/comm/mod.rs deleted file mode 100644 index 288979e..0000000 --- a/src/comm/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod output_destination; -pub mod tmux; - -pub use output_destination::OutputDestination; diff --git a/src/comm/output_destination.rs b/src/comm/output_destination.rs deleted file mode 100644 index 4bbd858..0000000 --- a/src/comm/output_destination.rs +++ /dev/null @@ -1,30 +0,0 @@ -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 buffer"), - Self::Clipboard => write!(f, "clipboard"), - } - } -} diff --git a/src/config/tmux_bridge.rs b/src/config/tmux_bridge.rs index de9cb01..61c5ca1 100644 --- a/src/config/tmux_bridge.rs +++ b/src/config/tmux_bridge.rs @@ -1,13 +1,13 @@ use clap::Clap; use std::collections::HashMap; +use std::fmt; use std::str::FromStr; use super::basic; use crate::{ - comm::tmux, error, textbuf::{alphabet, regexes}, - ui, + tmux, ui, }; /// Main configuration, parsed from command line. @@ -135,3 +135,32 @@ impl FromStr for CaptureRegion { } } } + +/// 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 buffer"), + Self::Clipboard => write!(f, "clipboard"), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 9089912..6f2f041 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ -pub mod comm; pub mod config; pub mod error; pub mod textbuf; +pub mod tmux; pub mod ui; /// Run copyrat on an input string `buffer`, configured by `Opt`. @@ -32,7 +32,7 @@ pub fn run(buffer: String, opt: &config::basic::Config) -> Option }, }; - let default_output_destination = comm::OutputDestination::Tmux; + let default_output_destination = config::tmux_bridge::OutputDestination::Tmux; let selection: Option = { let mut ui = ui::ViewController::new( diff --git a/src/comm/tmux.rs b/src/tmux.rs similarity index 98% rename from src/comm/tmux.rs rename to src/tmux.rs index 1cc1fce..4c96bad 100644 --- a/src/comm/tmux.rs +++ b/src/tmux.rs @@ -1,3 +1,8 @@ +//! This module provides types and functions to use Tmux. +//! +//! The main use cases are running Tmux commands & parsing Tmux panes +//! information. + use regex::Regex; use std::collections::HashMap; use std::fmt; diff --git a/src/ui/selection.rs b/src/ui/selection.rs index efa8ed8..55491b3 100644 --- a/src/ui/selection.rs +++ b/src/ui/selection.rs @@ -1,4 +1,4 @@ -use crate::comm::OutputDestination; +use crate::config::tmux_bridge::OutputDestination; /// Represents the text selected by the user, along with if it was uppercased /// and the output destination (Tmux buffer or Clipboard). diff --git a/src/ui/vc.rs b/src/ui/vc.rs index 8966701..07693c9 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -8,7 +8,7 @@ use termion::{self, color, cursor, event, style}; use super::colors::UiColors; use super::Selection; use super::{HintAlignment, HintStyle}; -use crate::{comm::OutputDestination, textbuf}; +use crate::{config::tmux_bridge::OutputDestination, textbuf}; pub struct ViewController<'a> { model: &'a mut textbuf::Model<'a>, From 863cd2e082aeff5ae6edfdfccc2c4d3daa0b0b31 Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 21 Mar 2021 15:50:58 +0100 Subject: [PATCH 46/50] refactor: tmux_bridge -> extended --- src/bin/tmux_copyrat.rs | 4 ++-- src/config/{tmux_bridge.rs => extended.rs} | 22 +++++++++++++--------- src/config/mod.rs | 2 +- src/lib.rs | 2 +- src/tmux.rs | 2 +- src/ui/selection.rs | 2 +- src/ui/vc.rs | 2 +- 7 files changed, 20 insertions(+), 16 deletions(-) rename src/config/{tmux_bridge.rs => extended.rs} (89%) diff --git a/src/bin/tmux_copyrat.rs b/src/bin/tmux_copyrat.rs index f0cf8da..2c3375d 100644 --- a/src/bin/tmux_copyrat.rs +++ b/src/bin/tmux_copyrat.rs @@ -1,12 +1,12 @@ use copyrat::{ - config::tmux_bridge::{Config, OutputDestination}, + config::extended::{ConfigExt, OutputDestination}, error, tmux, ui::Selection, }; /// fn main() -> Result<(), error::ParseError> { - let config = Config::initialize()?; + let config = ConfigExt::initialize()?; // Identify active pane and capture its content. let panes: Vec = tmux::list_panes()?; diff --git a/src/config/tmux_bridge.rs b/src/config/extended.rs similarity index 89% rename from src/config/tmux_bridge.rs rename to src/config/extended.rs index 61c5ca1..52f077a 100644 --- a/src/config/tmux_bridge.rs +++ b/src/config/extended.rs @@ -10,10 +10,12 @@ use crate::{ tmux, ui, }; -/// Main configuration, parsed from command line. +/// Extended configuration for handling Tmux-specific configuration (options +/// and outputs). This is only used by `tmux-copyrat` and parsed from command +/// line.. #[derive(Clap, Debug)] #[clap(author, about, version)] -pub struct Config { +pub struct ConfigExt { /// Don't read options from Tmux. /// /// By default, options formatted like `copyrat-*` are read from tmux. @@ -47,19 +49,21 @@ pub struct Config { pub basic_config: basic::Config, } -impl Config { - pub fn initialize() -> Result { - let mut config = Config::parse(); +impl ConfigExt { + pub fn initialize() -> Result { + let mut config_ext = ConfigExt::parse(); - if !config.ignore_tmux_options { + if !config_ext.ignore_tmux_options { let tmux_options: HashMap = tmux::get_options("@copyrat-")?; // Override default values with those coming from tmux. - let wrapped = &mut config.basic_config; + let wrapped = &mut config_ext.basic_config; for (name, value) in &tmux_options { match name.as_ref() { - "@copyrat-capture" => config.capture_region = CaptureRegion::from_str(&value)?, + "@copyrat-capture" => { + config_ext.capture_region = CaptureRegion::from_str(&value)? + } "@copyrat-alphabet" => { wrapped.alphabet = alphabet::parse_alphabet(value)?; } @@ -104,7 +108,7 @@ impl Config { } } - Ok(config) + Ok(config_ext) } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 774706f..26919fd 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,2 +1,2 @@ pub mod basic; -pub mod tmux_bridge; +pub mod extended; diff --git a/src/lib.rs b/src/lib.rs index 6f2f041..d7ac72f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ pub fn run(buffer: String, opt: &config::basic::Config) -> Option }, }; - let default_output_destination = config::tmux_bridge::OutputDestination::Tmux; + let default_output_destination = config::extended::OutputDestination::Tmux; let selection: Option = { let mut ui = ui::ViewController::new( diff --git a/src/tmux.rs b/src/tmux.rs index 4c96bad..b87375a 100644 --- a/src/tmux.rs +++ b/src/tmux.rs @@ -8,7 +8,7 @@ use std::collections::HashMap; use std::fmt; use std::str::FromStr; -use crate::config::tmux_bridge::CaptureRegion; +use crate::config::extended::CaptureRegion; use crate::error::ParseError; #[derive(Debug, PartialEq)] diff --git a/src/ui/selection.rs b/src/ui/selection.rs index 55491b3..042016a 100644 --- a/src/ui/selection.rs +++ b/src/ui/selection.rs @@ -1,4 +1,4 @@ -use crate::config::tmux_bridge::OutputDestination; +use crate::config::extended::OutputDestination; /// Represents the text selected by the user, along with if it was uppercased /// and the output destination (Tmux buffer or Clipboard). diff --git a/src/ui/vc.rs b/src/ui/vc.rs index 07693c9..1e944fa 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -8,7 +8,7 @@ use termion::{self, color, cursor, event, style}; use super::colors::UiColors; use super::Selection; use super::{HintAlignment, HintStyle}; -use crate::{config::tmux_bridge::OutputDestination, textbuf}; +use crate::{config::extended::OutputDestination, textbuf}; pub struct ViewController<'a> { model: &'a mut textbuf::Model<'a>, From 9611b8a36d72e67755347a3bbdc680c14e08276b Mon Sep 17 00:00:00 2001 From: graelo Date: Mon, 22 Mar 2021 06:48:09 +0100 Subject: [PATCH 47/50] refactor: ui add fold markers --- src/ui/vc.rs | 55 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/src/ui/vc.rs b/src/ui/vc.rs index 1e944fa..586767a 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -25,6 +25,8 @@ pub struct ViewController<'a> { } impl<'a> ViewController<'a> { + // Initialize {{{1 + pub fn new( model: &'a mut textbuf::Model<'a>, unique_hint: bool, @@ -56,6 +58,9 @@ impl<'a> ViewController<'a> { } } + // }}} + // Coordinates {{{1 + /// Convert the `Match` text into the coordinates of the wrapped lines. /// /// Compute the new x offset of the text as the remainder of the line width @@ -65,7 +70,7 @@ impl<'a> ViewController<'a> { /// Compute the new y offset of the text as the initial y offset plus any /// additional offset due to previous split lines. This is obtained thanks to /// the `offset_per_line` member. - pub fn map_coords_to_wrapped_space(&self, offset_x: usize, offset_y: usize) -> (usize, usize) { + fn map_coords_to_wrapped_space(&self, offset_x: usize, offset_y: usize) -> (usize, usize) { let line_width = self.term_width as usize; let new_offset_x = offset_x % line_width; @@ -75,6 +80,27 @@ impl<'a> ViewController<'a> { (new_offset_x, new_offset_y) } + /// Returns screen offset of a given `Match`. + /// + /// If multibyte characters occur before the hint (in the "prefix"), then + /// their compouding takes less space on screen when printed: for + /// instance ´ + e = é. Consequently the hint offset has to be adjusted + /// to the left. + fn match_offsets(&self, mat: &textbuf::Match<'a>) -> (usize, usize) { + let offset_x = { + let line = &self.model.lines[mat.y as usize]; + let prefix = &line[0..mat.x as usize]; + let adjust = prefix.len() - prefix.chars().count(); + (mat.x as usize) - (adjust) + }; + let offset_y = mat.y as usize; + + (offset_x, offset_y) + } + + // }}} + // Focus management {{{1 + /// Move focus onto the previous hint, returning both the index of the /// previously focused match, and the index of the newly focused one. fn prev_focus_index(&mut self) -> (usize, usize) { @@ -109,23 +135,8 @@ impl<'a> ViewController<'a> { (old_index, new_index) } - /// Returns screen offset of a given `Match`. - /// - /// If multibyte characters occur before the hint (in the "prefix"), then - /// their compouding takes less space on screen when printed: for - /// instance ´ + e = é. Consequently the hint offset has to be adjusted - /// to the left. - fn match_offsets(&self, mat: &textbuf::Match<'a>) -> (usize, usize) { - let offset_x = { - let line = &self.model.lines[mat.y as usize]; - let prefix = &line[0..mat.x as usize]; - let adjust = prefix.len() - prefix.chars().count(); - (mat.x as usize) - (adjust) - }; - let offset_y = mat.y as usize; - - (offset_x, offset_y) - } + // }}} + // Rendering {{{1 /// Render entire model lines on provided writer. /// @@ -398,6 +409,9 @@ impl<'a> ViewController<'a> { stdout.flush().unwrap(); } + // }}} + // Listening {{{1 + /// Listen to keys entered on stdin, moving focus accordingly, or /// selecting one match. /// @@ -551,6 +565,9 @@ impl<'a> ViewController<'a> { Event::Exit } + // }}} + // Presenting {{{1 + /// Configure the terminal and display the `Ui`. /// /// - Setup steps: switch to alternate screen, switch to raw mode, hide the cursor. @@ -578,6 +595,8 @@ impl<'a> ViewController<'a> { selection } + + // }}} } /// Compute each line's actual y offset if displayed in a terminal of width From 893f80a1e203d0b87c6014e79085587ff94e6683 Mon Sep 17 00:00:00 2001 From: graelo Date: Mon, 22 Mar 2021 07:17:54 +0100 Subject: [PATCH 48/50] refactor: unique_hint -> textbuf::Model --- src/lib.rs | 2 +- src/textbuf/mod.rs | 84 +++++++++++++++++++++++++++++++++----------- src/textbuf/model.rs | 7 ++-- src/ui/vc.rs | 9 ++--- 4 files changed, 74 insertions(+), 28 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d7ac72f..d134a09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ pub fn run(buffer: String, opt: &config::basic::Config) -> Option &opt.named_patterns, &opt.custom_patterns, opt.reverse, + opt.unique_hint, ); let hint_style = match &opt.hint_style { @@ -37,7 +38,6 @@ pub fn run(buffer: String, opt: &config::basic::Config) -> Option let selection: Option = { let mut ui = ui::ViewController::new( &mut model, - opt.unique_hint, opt.focus_wrap_around, default_output_destination, &opt.colors, diff --git a/src/textbuf/mod.rs b/src/textbuf/mod.rs index 92c9382..77bfe0b 100644 --- a/src/textbuf/mod.rs +++ b/src/textbuf/mod.rs @@ -20,6 +20,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -27,8 +28,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 3); assert_eq!(results.first().unwrap().hint, "a"); @@ -43,6 +45,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = true; let results = Model::new( buffer, &alphabet, @@ -50,8 +53,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(true); + .matches(); assert_eq!(results.len(), 3); assert_eq!(results.first().unwrap().hint, "a"); @@ -66,6 +70,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -73,8 +78,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 1); assert_eq!( @@ -92,6 +98,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = true; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -99,8 +106,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().text, "/var/log/nginx.log"); @@ -117,6 +125,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -124,8 +133,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().text, "/tmp/foo/bar_lol"); @@ -141,6 +151,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -148,8 +159,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 1); assert_eq!(results.get(0).unwrap().text, "~/.gnu/.config.txt"); @@ -163,6 +175,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -170,8 +183,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 1); } @@ -184,6 +198,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -191,8 +206,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text, "fd70b5695"); @@ -212,6 +228,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -219,8 +236,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().pattern, "ipv4"); @@ -239,6 +257,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -246,8 +265,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text, "fe80::2:202:fe4"); @@ -268,6 +288,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -275,8 +296,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 2); assert_eq!(results.get(0).unwrap().pattern, "markdown-url"); @@ -293,6 +315,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -300,8 +323,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 4); assert_eq!( @@ -326,6 +350,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -333,8 +358,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 2); assert_eq!(results.get(0).unwrap().pattern, "email"); @@ -357,6 +383,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -364,8 +391,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().pattern, "mem-address"); @@ -384,6 +412,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -391,8 +420,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text, "#fd7b56"); @@ -409,6 +439,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -416,8 +447,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 1); assert_eq!( @@ -434,6 +466,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -441,8 +474,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 8); } @@ -455,6 +489,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -462,8 +497,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 1); assert_eq!(results.get(0).unwrap().pattern, "diff-a"); @@ -478,6 +514,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -485,8 +522,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 1); assert_eq!(results.get(0).unwrap().pattern, "diff-b"); @@ -504,6 +542,7 @@ mod tests { .collect(); let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -511,8 +550,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 9); assert_eq!(results.get(0).unwrap().text, "http://foo.bar"); @@ -543,6 +583,7 @@ mod tests { let custom = vec![]; let alphabet = Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let results = Model::new( buffer, &alphabet, @@ -550,8 +591,9 @@ mod tests { &named_pat, &custom, reverse, + unique_hint, ) - .matches(false); + .matches(); assert_eq!(results.len(), 2); assert_eq!(results.get(0).unwrap().text, "http://foo.bar"); diff --git a/src/textbuf/model.rs b/src/textbuf/model.rs index 45b7a46..b0d5b06 100644 --- a/src/textbuf/model.rs +++ b/src/textbuf/model.rs @@ -17,6 +17,7 @@ pub struct Model<'a> { named_patterns: &'a [NamedPattern], custom_patterns: &'a [String], pub reverse: bool, + unique_hint: bool, } impl<'a> Model<'a> { @@ -27,6 +28,7 @@ impl<'a> Model<'a> { named_patterns: &'a [NamedPattern], custom_patterns: &'a [String], reverse: bool, + unique_hint: bool, ) -> Model<'a> { let lines = buffer.split('\n').collect(); @@ -38,19 +40,20 @@ impl<'a> Model<'a> { named_patterns, custom_patterns, reverse, + unique_hint, } } /// Returns a vector of `Match`es, each corresponding to a pattern match /// in the lines, its location (x, y), and associated hint. - pub fn matches(&self, unique: bool) -> Vec> { + pub fn matches(&self) -> Vec> { let mut raw_matches = self.raw_matches(); if self.reverse { raw_matches.reverse(); } - let mut matches = self.associate_hints(&raw_matches, unique); + let mut matches = self.associate_hints(&raw_matches, self.unique_hint); if self.reverse { matches.reverse(); diff --git a/src/ui/vc.rs b/src/ui/vc.rs index 586767a..255d594 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -29,14 +29,13 @@ impl<'a> ViewController<'a> { pub fn new( model: &'a mut textbuf::Model<'a>, - unique_hint: bool, focus_wrap_around: bool, default_output_destination: OutputDestination, rendering_colors: &'a UiColors, hint_alignment: &'a HintAlignment, hint_style: Option, ) -> ViewController<'a> { - let matches = model.matches(unique_hint); + let matches = model.matches(); let lookup_trie = textbuf::Model::build_lookup_trie(&matches); let focus_index = if model.reverse { matches.len() - 1 } else { 0 }; @@ -890,6 +889,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let custom_patterns = vec![]; let alphabet = alphabet::Alphabet("abcd".to_string()); let reverse = false; + let unique_hint = false; let mut model = textbuf::Model::new( content, &alphabet, @@ -897,6 +897,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; &named_pat, &custom_patterns, reverse, + unique_hint, ); let term_width: u16 = 80; let line_offsets = get_line_offsets(&model.lines, term_width); @@ -965,6 +966,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let custom_patterns = vec![]; let alphabet = alphabet::Alphabet("abcd".to_string()); let reverse = true; + let unique_hint = false; let mut model = textbuf::Model::new( content, &alphabet, @@ -972,8 +974,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; &named_pat, &custom_patterns, reverse, + unique_hint, ); - let unique_hint = false; let wrap_around = false; let default_output_destination = OutputDestination::Tmux; @@ -992,7 +994,6 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let ui = ViewController::new( &mut model, - unique_hint, wrap_around, default_output_destination, &rendering_colors, From 4ec240c2b1a125c212e362bb4ff20d544800cebb Mon Sep 17 00:00:00 2001 From: graelo Date: Mon, 22 Mar 2021 08:48:51 +0100 Subject: [PATCH 49/50] refactor: model receives buffer & computes matches --- src/lib.rs | 12 +- src/textbuf/mod.rs | 105 ++++++----- src/textbuf/model.rs | 412 +++++++++++++++++++++---------------------- src/ui/vc.rs | 54 +++--- 4 files changed, 304 insertions(+), 279 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d134a09..2a23b59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,8 +10,10 @@ pub mod ui; /// /// Maybe the decision to take ownership of the buffer is a bit bold. pub fn run(buffer: String, opt: &config::basic::Config) -> Option { - let mut model = textbuf::Model::new( - &buffer, + let lines = buffer.split('\n').collect::>(); + + let model = textbuf::Model::new( + &lines, &opt.alphabet, opt.use_all_patterns, &opt.named_patterns, @@ -20,6 +22,10 @@ pub fn run(buffer: String, opt: &config::basic::Config) -> Option opt.unique_hint, ); + if model.matches.is_empty() { + return None; + } + let hint_style = match &opt.hint_style { None => None, Some(style) => match style { @@ -37,7 +43,7 @@ pub fn run(buffer: String, opt: &config::basic::Config) -> Option let selection: Option = { let mut ui = ui::ViewController::new( - &mut model, + &model, opt.focus_wrap_around, default_output_destination, &opt.colors, diff --git a/src/textbuf/mod.rs b/src/textbuf/mod.rs index 77bfe0b..af6abe6 100644 --- a/src/textbuf/mod.rs +++ b/src/textbuf/mod.rs @@ -15,6 +15,7 @@ mod tests { #[test] fn match_reverse() { let buffer = "lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -22,7 +23,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -30,7 +31,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 3); assert_eq!(results.first().unwrap().hint, "a"); @@ -40,6 +41,7 @@ mod tests { #[test] fn match_unique() { let buffer = "lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -47,7 +49,7 @@ mod tests { let reverse = false; let unique_hint = true; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -55,7 +57,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 3); assert_eq!(results.first().unwrap().hint, "a"); @@ -65,6 +67,7 @@ mod tests { #[test] fn match_docker() { let buffer = "latest sha256:30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -72,7 +75,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -80,7 +83,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 1); assert_eq!( @@ -93,6 +96,7 @@ mod tests { fn match_ansi_colors() { let buffer = "path: /var/log/nginx.log\npath: test/log/nginx-2.log:32folder/.nginx@4df2.log"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -100,7 +104,7 @@ mod tests { let reverse = true; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -108,7 +112,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().text, "/var/log/nginx.log"); @@ -120,6 +124,7 @@ mod tests { fn match_paths() { let buffer = "Lorem /tmp/foo/bar_lol, lorem\n Lorem /var/log/boot-strap.log lorem ../log/kern.log lorem"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -127,7 +132,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -135,7 +140,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().text, "/tmp/foo/bar_lol"); @@ -146,6 +151,7 @@ mod tests { #[test] fn match_home() { let buffer = "Lorem ~/.gnu/.config.txt, lorem"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -153,7 +159,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -161,7 +167,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 1); assert_eq!(results.get(0).unwrap().text, "~/.gnu/.config.txt"); @@ -170,6 +176,7 @@ mod tests { #[test] fn match_uuids() { let buffer = "Lorem ipsum 123e4567-e89b-12d3-a456-426655440000 lorem\n Lorem lorem lorem"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -177,7 +184,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -185,7 +192,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 1); } @@ -193,6 +200,7 @@ mod tests { #[test] fn match_shas() { let buffer = "Lorem fd70b5695 5246ddf f924213 lorem\n Lorem 973113963b491874ab2e372ee60d4b4cb75f717c lorem"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -200,7 +208,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -208,7 +216,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text, "fd70b5695"); @@ -223,6 +231,7 @@ mod tests { #[test] fn match_ipv4s() { let buffer = "Lorem ipsum 127.0.0.1 lorem\n Lorem 255.255.10.255 lorem 127.0.0.1 lorem"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -230,7 +239,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -238,7 +247,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().pattern, "ipv4"); @@ -252,6 +261,7 @@ mod tests { #[test] fn match_ipv6s() { let buffer = "Lorem ipsum fe80::2:202:fe4 lorem\n Lorem 2001:67c:670:202:7ba8:5e41:1591:d723 lorem fe80::2:1 lorem ipsum fe80:22:312:fe::1%eth0"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -259,7 +269,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -267,7 +277,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text, "fe80::2:202:fe4"); @@ -283,6 +293,7 @@ mod tests { fn match_markdown_urls() { let buffer = "Lorem ipsum [link](https://github.io?foo=bar) ![](http://cdn.com/img.jpg) lorem"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -290,7 +301,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -298,7 +309,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 2); assert_eq!(results.get(0).unwrap().pattern, "markdown-url"); @@ -310,6 +321,7 @@ mod tests { #[test] fn match_urls() { let buffer = "Lorem ipsum https://www.rust-lang.org/tools lorem\n Lorem ipsumhttps://crates.io lorem https://github.io?foo=bar lorem ssh://github.io"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -317,7 +329,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -325,7 +337,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 4); assert_eq!( @@ -345,6 +357,7 @@ mod tests { fn match_emails() { let buffer = "Lorem ipsum john@server.department.company.com lorem"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -352,7 +365,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -360,7 +373,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 2); assert_eq!(results.get(0).unwrap().pattern, "email"); @@ -378,6 +391,7 @@ mod tests { #[test] fn match_addresses() { let buffer = "Lorem 0xfd70b5695 0x5246ddf lorem\n Lorem 0x973113tlorem"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -385,7 +399,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -393,7 +407,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().pattern, "mem-address"); @@ -407,6 +421,7 @@ mod tests { #[test] fn match_hex_colors() { let buffer = "Lorem #fd7b56 lorem #FF00FF\n Lorem #00fF05 lorem #abcd00 lorem #afRR00"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -414,7 +429,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -422,7 +437,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text, "#fd7b56"); @@ -434,6 +449,7 @@ mod tests { #[test] fn match_ipfs() { let buffer = "Lorem QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ lorem Qmfoobar"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -441,7 +457,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -449,7 +465,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 1); assert_eq!( @@ -461,6 +477,7 @@ mod tests { #[test] fn match_process_port() { let buffer = "Lorem 5695 52463 lorem\n Lorem 973113 lorem 99999 lorem 8888 lorem\n 23456 lorem 5432 lorem 23444"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -468,7 +485,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -476,7 +493,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 8); } @@ -484,6 +501,7 @@ mod tests { #[test] fn match_diff_a() { let buffer = "Lorem lorem\n--- a/src/main.rs"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -491,7 +509,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -499,7 +517,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 1); assert_eq!(results.get(0).unwrap().pattern, "diff-a"); @@ -509,6 +527,7 @@ mod tests { #[test] fn match_diff_b() { let buffer = "Lorem lorem\n+++ b/src/main.rs"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom = vec![]; @@ -516,7 +535,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -524,7 +543,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 1); assert_eq!(results.get(0).unwrap().pattern, "diff-b"); @@ -534,6 +553,7 @@ mod tests { #[test] fn priority_between_regexes() { let buffer = "Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; let custom: Vec = ["CUSTOM-[0-9]{4,}", "ISSUE-[0-9]{3}"] @@ -544,7 +564,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -552,7 +572,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 9); assert_eq!(results.get(0).unwrap().text, "http://foo.bar"); @@ -575,6 +595,7 @@ mod tests { #[test] fn named_patterns() { let buffer = "Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem"; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = false; use crate::textbuf::regexes::parse_pattern_name; @@ -585,7 +606,7 @@ mod tests { let reverse = false; let unique_hint = false; let results = Model::new( - buffer, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -593,7 +614,7 @@ mod tests { reverse, unique_hint, ) - .matches(); + .matches; assert_eq!(results.len(), 2); assert_eq!(results.get(0).unwrap().text, "http://foo.bar"); diff --git a/src/textbuf/model.rs b/src/textbuf/model.rs index b0d5b06..9208e35 100644 --- a/src/textbuf/model.rs +++ b/src/textbuf/model.rs @@ -11,18 +11,16 @@ use super::regexes::{NamedPattern, EXCLUDE_PATTERNS, PATTERNS}; /// Holds data for the `Ui`. pub struct Model<'a> { // buffer: &'a str, - pub lines: Vec<&'a str>, - alphabet: &'a Alphabet, - use_all_patterns: bool, - named_patterns: &'a [NamedPattern], - custom_patterns: &'a [String], + pub lines: &'a [&'a str], pub reverse: bool, - unique_hint: bool, + pub matches: Vec>, + pub lookup_trie: SequenceTrie, } impl<'a> Model<'a> { pub fn new( - buffer: &'a str, + // buffer: &'a str, + lines: &'a [&'a str], alphabet: &'a Alphabet, use_all_patterns: bool, named_patterns: &'a [NamedPattern], @@ -30,210 +28,212 @@ impl<'a> Model<'a> { reverse: bool, unique_hint: bool, ) -> Model<'a> { - let lines = buffer.split('\n').collect(); + // let lines = buffer.split('\n').collect::>(); + + let mut raw_matches = + raw_matches(&lines, named_patterns, custom_patterns, use_all_patterns); + + if reverse { + raw_matches.reverse(); + } + + let mut matches = associate_hints(&raw_matches, alphabet, unique_hint); + + if reverse { + matches.reverse(); + } + + let lookup_trie = build_lookup_trie(&matches); Model { // buffer, lines, - alphabet, - use_all_patterns, - named_patterns, - custom_patterns, reverse, - unique_hint, + matches, + lookup_trie, } } - - /// Returns a vector of `Match`es, each corresponding to a pattern match - /// in the lines, its location (x, y), and associated hint. - pub fn matches(&self) -> Vec> { - let mut raw_matches = self.raw_matches(); - - if self.reverse { - raw_matches.reverse(); - } - - let mut matches = self.associate_hints(&raw_matches, self.unique_hint); - - if self.reverse { - matches.reverse(); - } - - matches - } - - /// Internal function that searches the model's lines for pattern matches. - /// Returns a vector of `RawMatch`es (text, location, pattern id) without - /// an associated hint. The hint is attached to `Match`, not to `RawMatch`. - /// - /// # Notes - /// - /// Custom regexes have priority over other regexes. - /// - /// If no named patterns were specified, it will search for all available - /// patterns from the `PATTERNS` catalog. - fn raw_matches(&self) -> Vec> { - let exclude_regexes = EXCLUDE_PATTERNS - .iter() - .map(|&(name, pattern)| (name, Regex::new(pattern).unwrap())) - .collect::>(); - - let custom_regexes = self - .custom_patterns - .iter() - .map(|pattern| { - ( - "custom", - Regex::new(pattern).expect("Invalid custom regexp"), - ) - }) - .collect::>(); - - let regexes = if self.use_all_patterns { - PATTERNS - .iter() - .map(|&(name, pattern)| (name, Regex::new(pattern).unwrap())) - .collect::>() - } else { - self.named_patterns - .iter() - .map(|NamedPattern(name, pattern)| (name.as_str(), Regex::new(pattern).unwrap())) - .collect::>() - }; - - let all_regexes = [exclude_regexes, custom_regexes, regexes].concat(); - - let mut raw_matches = Vec::new(); - - for (index, line) in self.lines.iter().enumerate() { - // Chunk is the remainder of the line to be searched for matches. - // This advances iteratively, until no matches can be found. - let mut chunk: &str = line; - let mut offset: i32 = 0; - - // Use all avail regexes to match the chunk and select the match - // occuring the earliest on the chunk. Save its matched text and - // position in a `RawMatch` struct. - loop { - // For each avalable regex, use the `find_iter` iterator to - // get the first non-overlapping match in the chunk, returning - // the start and end byte indices with respect to the chunk. - let chunk_matches = all_regexes - .iter() - .filter_map(|(&ref pat_name, reg)| match reg.find_iter(chunk).next() { - Some(reg_match) => Some((pat_name, reg, reg_match)), - None => None, - }) - .collect::>(); - - if chunk_matches.is_empty() { - break; - } - - // First match on the chunk. - let (pat_name, reg, reg_match) = chunk_matches - .iter() - .min_by_key(|element| element.2.start()) - .unwrap(); - - // Never hint or break ansi color sequences. - if *pat_name != "ansi_colors" { - let text = reg_match.as_str(); - - // In case the pattern has a capturing group, try obtaining - // that text and start offset, else use the entire match. - let (subtext, substart) = match reg - .captures_iter(text) - .next() - .expect("This regex is guaranteed to match.") - .get(1) - { - Some(capture) => (capture.as_str(), capture.start()), - None => (text, 0), - }; - - raw_matches.push(RawMatch { - x: offset + reg_match.start() as i32 + substart as i32, - y: index as i32, - pattern: pat_name, - text: subtext, - }); - } - - chunk = chunk - .get(reg_match.end()..) - .expect("The chunk must be larger than the regex match."); - offset += reg_match.end() as i32; - } - } - - raw_matches - } - - /// Associate a hint to each `RawMatch`, returning a vector of `Match`es. - /// - /// If `unique` is `true`, all duplicate matches will have the same hint. - /// For copying matched text, this seems easier and more natural. - /// If `unique` is `false`, duplicate matches will have their own hint. - fn associate_hints(&self, raw_matches: &[RawMatch<'a>], unique: bool) -> Vec> { - let hints = self.alphabet.make_hints(raw_matches.len()); - let mut hints_iter = hints.iter(); - - let mut result: Vec> = vec![]; - - if unique { - // Map (text, hint) - let mut known: collections::HashMap<&str, &str> = collections::HashMap::new(); - - for raw_mat in raw_matches { - let hint: &str = known.entry(raw_mat.text).or_insert_with(|| { - hints_iter - .next() - .expect("We should have as many hints as necessary, even invisible ones.") - }); - - result.push(Match { - x: raw_mat.x, - y: raw_mat.y, - pattern: raw_mat.pattern, - text: raw_mat.text, - hint: hint.to_string(), - }); - } - } else { - for raw_mat in raw_matches { - let hint = hints_iter - .next() - .expect("We should have as many hints as necessary, even invisible ones."); - - result.push(Match { - x: raw_mat.x, - y: raw_mat.y, - pattern: raw_mat.pattern, - text: raw_mat.text, - hint: hint.to_string(), - }); - } - } - - result - } - - /// Builds a `SequenceTrie` that helps determine if a sequence of keys - /// entered by the user corresponds to a match. This kind of lookup - /// directly returns a reference to the corresponding `Match` if any. - pub fn build_lookup_trie(matches: &'a [Match<'a>]) -> SequenceTrie { - let mut trie = SequenceTrie::new(); - - for (index, mat) in matches.iter().enumerate() { - let hint_chars = mat.hint.chars().collect::>(); - - // no need to insert twice the same hint - if trie.get(&hint_chars).is_none() { - trie.insert_owned(hint_chars, index); - } - } - - trie - } +} + +/// Internal function that searches the model's lines for pattern matches. +/// Returns a vector of `RawMatch`es (text, location, pattern id) without +/// an associated hint. The hint is attached to `Match`, not to `RawMatch`. +/// +/// # Notes +/// +/// Custom regexes have priority over other regexes. +/// +/// If no named patterns were specified, it will search for all available +/// patterns from the `PATTERNS` catalog. +fn raw_matches<'a>( + lines: &'a [&'a str], + named_patterns: &'a [NamedPattern], + custom_patterns: &'a [String], + use_all_patterns: bool, +) -> Vec> { + let exclude_regexes = EXCLUDE_PATTERNS + .iter() + .map(|&(name, pattern)| (name, Regex::new(pattern).unwrap())) + .collect::>(); + + let custom_regexes = custom_patterns + .iter() + .map(|pattern| { + ( + "custom", + Regex::new(pattern).expect("Invalid custom regexp"), + ) + }) + .collect::>(); + + let regexes = if use_all_patterns { + PATTERNS + .iter() + .map(|&(name, pattern)| (name, Regex::new(pattern).unwrap())) + .collect::>() + } else { + named_patterns + .iter() + .map(|NamedPattern(name, pattern)| (name.as_str(), Regex::new(pattern).unwrap())) + .collect::>() + }; + + let all_regexes = [exclude_regexes, custom_regexes, regexes].concat(); + + let mut raw_matches = Vec::new(); + + for (index, line) in lines.iter().enumerate() { + // Chunk is the remainder of the line to be searched for matches. + // This advances iteratively, until no matches can be found. + let mut chunk: &str = line; + let mut offset: i32 = 0; + + // Use all avail regexes to match the chunk and select the match + // occuring the earliest on the chunk. Save its matched text and + // position in a `RawMatch` struct. + loop { + // For each avalable regex, use the `find_iter` iterator to + // get the first non-overlapping match in the chunk, returning + // the start and end byte indices with respect to the chunk. + let chunk_matches = all_regexes + .iter() + .filter_map(|(&ref pat_name, reg)| match reg.find_iter(chunk).next() { + Some(reg_match) => Some((pat_name, reg, reg_match)), + None => None, + }) + .collect::>(); + + if chunk_matches.is_empty() { + break; + } + + // First match on the chunk. + let (pat_name, reg, reg_match) = chunk_matches + .iter() + .min_by_key(|element| element.2.start()) + .unwrap(); + + // Never hint or break ansi color sequences. + if *pat_name != "ansi_colors" { + let text = reg_match.as_str(); + + // In case the pattern has a capturing group, try obtaining + // that text and start offset, else use the entire match. + let (subtext, substart) = match reg + .captures_iter(text) + .next() + .expect("This regex is guaranteed to match.") + .get(1) + { + Some(capture) => (capture.as_str(), capture.start()), + None => (text, 0), + }; + + raw_matches.push(RawMatch { + x: offset + reg_match.start() as i32 + substart as i32, + y: index as i32, + pattern: pat_name, + text: subtext, + }); + } + + chunk = chunk + .get(reg_match.end()..) + .expect("The chunk must be larger than the regex match."); + offset += reg_match.end() as i32; + } + } + + raw_matches +} + +/// Associate a hint to each `RawMatch`, returning a vector of `Match`es. +/// +/// If `unique` is `true`, all duplicate matches will have the same hint. +/// For copying matched text, this seems easier and more natural. +/// If `unique` is `false`, duplicate matches will have their own hint. +fn associate_hints<'a>( + raw_matches: &[RawMatch<'a>], + alphabet: &'a Alphabet, + unique: bool, +) -> Vec> { + let hints = alphabet.make_hints(raw_matches.len()); + let mut hints_iter = hints.iter(); + + let mut result: Vec> = vec![]; + + if unique { + // Map (text, hint) + let mut known: collections::HashMap<&str, &str> = collections::HashMap::new(); + + for raw_mat in raw_matches { + let hint: &str = known.entry(raw_mat.text).or_insert_with(|| { + hints_iter + .next() + .expect("We should have as many hints as necessary, even invisible ones.") + }); + + result.push(Match { + x: raw_mat.x, + y: raw_mat.y, + pattern: raw_mat.pattern, + text: raw_mat.text, + hint: hint.to_string(), + }); + } + } else { + for raw_mat in raw_matches { + let hint = hints_iter + .next() + .expect("We should have as many hints as necessary, even invisible ones."); + + result.push(Match { + x: raw_mat.x, + y: raw_mat.y, + pattern: raw_mat.pattern, + text: raw_mat.text, + hint: hint.to_string(), + }); + } + } + + result +} + +/// Builds a `SequenceTrie` that helps determine if a sequence of keys +/// entered by the user corresponds to a match. This kind of lookup +/// directly returns a reference to the corresponding `Match` if any. +fn build_lookup_trie<'a>(matches: &'a [Match<'a>]) -> SequenceTrie { + let mut trie = SequenceTrie::new(); + + for (index, mat) in matches.iter().enumerate() { + let hint_chars = mat.hint.chars().collect::>(); + + // no need to insert twice the same hint + if trie.get(&hint_chars).is_none() { + trie.insert_owned(hint_chars, index); + } + } + + trie } diff --git a/src/ui/vc.rs b/src/ui/vc.rs index 255d594..6dcfc24 100644 --- a/src/ui/vc.rs +++ b/src/ui/vc.rs @@ -2,7 +2,6 @@ use std::char; use std::cmp; use std::io; -use sequence_trie::SequenceTrie; use termion::{self, color, cursor, event, style}; use super::colors::UiColors; @@ -11,11 +10,9 @@ use super::{HintAlignment, HintStyle}; use crate::{config::extended::OutputDestination, textbuf}; pub struct ViewController<'a> { - model: &'a mut textbuf::Model<'a>, + model: &'a textbuf::Model<'a>, term_width: u16, line_offsets: Vec, - matches: Vec>, - lookup_trie: SequenceTrie, focus_index: usize, focus_wrap_around: bool, default_output_destination: OutputDestination, @@ -28,16 +25,18 @@ impl<'a> ViewController<'a> { // Initialize {{{1 pub fn new( - model: &'a mut textbuf::Model<'a>, + model: &'a textbuf::Model<'a>, focus_wrap_around: bool, default_output_destination: OutputDestination, rendering_colors: &'a UiColors, hint_alignment: &'a HintAlignment, hint_style: Option, ) -> ViewController<'a> { - let matches = model.matches(); - let lookup_trie = textbuf::Model::build_lookup_trie(&matches); - let focus_index = if model.reverse { matches.len() - 1 } else { 0 }; + let focus_index = if model.reverse { + model.matches.len() - 1 + } else { + 0 + }; let (term_width, _) = termion::terminal_size().unwrap_or((80u16, 30u16)); // .expect("Cannot read the terminal size."); let line_offsets = get_line_offsets(&model.lines, term_width); @@ -46,8 +45,6 @@ impl<'a> ViewController<'a> { model, term_width, line_offsets, - matches, - lookup_trie, focus_index, focus_wrap_around, default_output_destination, @@ -106,7 +103,7 @@ impl<'a> ViewController<'a> { let old_index = self.focus_index; if self.focus_wrap_around { if self.focus_index == 0 { - self.focus_index = self.matches.len() - 1; + self.focus_index = self.model.matches.len() - 1; } else { self.focus_index -= 1; } @@ -122,12 +119,12 @@ impl<'a> ViewController<'a> { fn next_focus_index(&mut self) -> (usize, usize) { let old_index = self.focus_index; if self.focus_wrap_around { - if self.focus_index == self.matches.len() - 1 { + if self.focus_index == self.model.matches.len() - 1 { self.focus_index = 0; } else { self.focus_index += 1; } - } else if self.focus_index < self.matches.len() - 1 { + } else if self.focus_index < self.model.matches.len() - 1 { self.focus_index += 1; } let new_index = self.focus_index; @@ -379,7 +376,7 @@ impl<'a> ViewController<'a> { &self.rendering_colors, ); - for (index, mat) in self.matches.iter().enumerate() { + for (index, mat) in self.model.matches.iter().enumerate() { let focused = index == self.focus_index; self.render_match(stdout, mat, focused); } @@ -396,12 +393,12 @@ impl<'a> ViewController<'a> { new_focus_index: usize, ) { // Render the previously focused match as non-focused - let mat = self.matches.get(old_focus_index).unwrap(); + let mat = self.model.matches.get(old_focus_index).unwrap(); let focused = false; self.render_match(stdout, mat, focused); // Render the previously focused match as non-focused - let mat = self.matches.get(new_focus_index).unwrap(); + let mat = self.model.matches.get(new_focus_index).unwrap(); let focused = true; self.render_match(stdout, mat, focused); @@ -420,7 +417,7 @@ impl<'a> ViewController<'a> { fn listen(&mut self, reader: &mut dyn io::Read, writer: &mut dyn io::Write) -> Event { use termion::input::TermRead; // Trait for `reader.keys().next()`. - if self.matches.is_empty() { + if self.model.matches.is_empty() { return Event::Exit; } @@ -487,7 +484,7 @@ impl<'a> ViewController<'a> { // Yank/copy event::Key::Char(_ch @ 'y') | event::Key::Char(_ch @ '\n') => { - let text = self.matches.get(self.focus_index).unwrap().text; + let text = self.model.matches.get(self.focus_index).unwrap().text; return Event::Match(Selection { text: text.to_string(), uppercased: false, @@ -495,7 +492,7 @@ impl<'a> ViewController<'a> { }); } event::Key::Char(_ch @ 'Y') => { - let text = self.matches.get(self.focus_index).unwrap().text; + let text = self.model.matches.get(self.focus_index).unwrap().text; return Event::Match(Selection { text: text.to_string(), uppercased: true, @@ -526,6 +523,7 @@ impl<'a> ViewController<'a> { typed_hint.push_str(&lower_key); let node = self + .model .lookup_trie .get_node(&typed_hint.chars().collect::>()); @@ -540,7 +538,7 @@ impl<'a> ViewController<'a> { let match_index = node.value().expect( "By construction, the Lookup Trie should have a value for each leaf.", ); - let mat = self.matches.get(*match_index).expect("By construction, the value in a leaf should correspond to an existing hint."); + let mat = self.model.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(Selection { text, @@ -880,18 +878,19 @@ path: /usr/local/bin/cargo"; #[test] /// Simulates rendering without any match. fn test_render_full_without_matches() { - let content = "lorem 127.0.0.1 lorem + let buffer = "lorem 127.0.0.1 lorem Barcelona https://en.wikipedia.org/wiki/Barcelona - "; + let lines = buffer.split('\n').collect::>(); - let use_all_patterns = true; + let use_all_patterns = false; let named_pat = vec![]; let custom_patterns = vec![]; let alphabet = alphabet::Alphabet("abcd".to_string()); let reverse = false; let unique_hint = false; let mut model = textbuf::Model::new( - content, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -918,8 +917,6 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; model: &mut model, term_width, line_offsets, - matches: vec![], // no matches - lookup_trie: SequenceTrie::new(), focus_index: 0, focus_wrap_around: false, default_output_destination: OutputDestination::Tmux, @@ -957,9 +954,10 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; #[test] /// Simulates rendering with matches. fn test_render_full_with_matches() { - let content = "lorem 127.0.0.1 lorem + let buffer = "lorem 127.0.0.1 lorem Barcelona https://en.wikipedia.org/wiki/Barcelona - "; + let lines = buffer.split('\n').collect::>(); let use_all_patterns = true; let named_pat = vec![]; @@ -968,7 +966,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let reverse = true; let unique_hint = false; let mut model = textbuf::Model::new( - content, + &lines, &alphabet, use_all_patterns, &named_pat, @@ -1092,7 +1090,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; // .find(|(_idx, (&l, &r))| l != r); // println!("{:?}", diff_point); - assert_eq!(2, ui.matches.len()); + assert_eq!(2, ui.model.matches.len()); assert_eq!(writer, expected.as_bytes()); } From 92509fe5349ebea8d51ddcd776a37cee6eccf8c5 Mon Sep 17 00:00:00 2001 From: graelo Date: Mon, 22 Mar 2021 08:55:03 +0100 Subject: [PATCH 50/50] refactor: run takes &[&str] --- src/bin/copyrat.rs | 3 ++- src/bin/tmux_copyrat.rs | 3 ++- src/lib.rs | 4 +--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bin/copyrat.rs b/src/bin/copyrat.rs index 5a3f565..b665441 100644 --- a/src/bin/copyrat.rs +++ b/src/bin/copyrat.rs @@ -12,10 +12,11 @@ fn main() { let mut buffer = String::new(); handle.read_to_string(&mut buffer).unwrap(); + let lines = buffer.split('\n').collect::>(); // Execute copyrat over the buffer (will take control over stdout). // This returns the selected matche. - let selection: Option = run(buffer, &opt); + let selection: Option = run(&lines, &opt); // Early exit, signaling no selections were found. if selection.is_none() { diff --git a/src/bin/tmux_copyrat.rs b/src/bin/tmux_copyrat.rs index 2c3375d..bba6f79 100644 --- a/src/bin/tmux_copyrat.rs +++ b/src/bin/tmux_copyrat.rs @@ -17,6 +17,7 @@ fn main() -> Result<(), error::ParseError> { .expect("Exactly one tmux pane should be active in the current window."); let buffer = tmux::capture_pane(&active_pane, &config.capture_region)?; + let lines = buffer.split('\n').collect::>(); // 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 @@ -24,7 +25,7 @@ fn main() -> Result<(), error::ParseError> { let temp_pane_spec = format!("{}.0", config.window_name); tmux::swap_pane_with(&temp_pane_spec)?; - let selection = copyrat::run(buffer, &config.basic_config); + let selection = copyrat::run(&lines, &config.basic_config); tmux::swap_pane_with(&temp_pane_spec)?; diff --git a/src/lib.rs b/src/lib.rs index 2a23b59..f61e584 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,9 +9,7 @@ pub mod ui; /// # Note /// /// Maybe the decision to take ownership of the buffer is a bit bold. -pub fn run(buffer: String, opt: &config::basic::Config) -> Option { - let lines = buffer.split('\n').collect::>(); - +pub fn run(lines: &[&str], opt: &config::basic::Config) -> Option { let model = textbuf::Model::new( &lines, &opt.alphabet,