refactor: rename Match -> Span

This commit is contained in:
graelo 2021-03-22 23:56:35 +01:00
parent 4fc7a75046
commit 9fbfff70f3
16 changed files with 316 additions and 330 deletions

View file

@ -134,7 +134,7 @@ set -g @thumbs-reverse
`default: disabled` `default: disabled`
Choose if you want to assign the same hint for the same matched strings. Choose if you want to assign the same hint for the same text spans.
For example: For example:
@ -146,7 +146,7 @@ set -g @thumbs-unique
`default: left` `default: left`
Choose where do you want to show the hint in the matched string. Options (left, right). Choose where do you want to show the hint in the text spans. Options (left, right).
For example: For example:
@ -193,7 +193,7 @@ set -g @thumbs-upcase-command 'echo -n {} | pbcopy'
`default: black` `default: black`
Sets the background color for matches Sets the background color for spans
For example: For example:
@ -205,7 +205,7 @@ set -g @thumbs-bg-color blue
`default: green` `default: green`
Sets the foreground color for matches Sets the foreground color for spans
For example: For example:
@ -314,7 +314,7 @@ This is the list of available alphabets:
## Extra features ## Extra features
- **Arrow navigation:** You can use the arrows to move around between all matched items. - **Arrow navigation:** You can use the arrows to move around between all spans.
- **Auto paste:** If your last typed hint character is uppercase, you are going to pick and paste the desired hint. - **Auto paste:** If your last typed hint character is uppercase, you are going to pick and paste the desired hint.
- **Multi selection:** If you run thumb with multi selection mode you will be able to choose multiple hints pressing the desired letter and `Space` to finalize the selection. - **Multi selection:** If you run thumb with multi selection mode you will be able to choose multiple hints pressing the desired letter and `Space` to finalize the selection.
@ -361,13 +361,13 @@ FLAGS:
-h, --help Prints help information -h, --help Prints help information
-m, --multi Enable multi-selection -m, --multi Enable multi-selection
-r, --reverse Reverse the order for assigned hints -r, --reverse Reverse the order for assigned hints
-u, --unique Don't show duplicated hints for the same match -u, --unique Don't show duplicated hints for the same span
-V, --version Prints version information -V, --version Prints version information
OPTIONS: OPTIONS:
-a, --alphabet <alphabet> Sets the alphabet [default: qwerty] -a, --alphabet <alphabet> Sets the alphabet [default: qwerty]
--bg-color <background_color> Sets the background color for matches [default: black] --bg-color <background_color> Sets the background color for spans [default: black]
--fg-color <foreground_color> Sets the foregroud color for matches [default: green] --fg-color <foreground_color> Sets the foregroud color for spans [default: green]
-f, --format <format> -f, --format <format>
Specifies the out format for the picked hint. (%U: Upcase, %H: Hint) [default: %H] Specifies the out format for the picked hint. (%U: Upcase, %H: Hint) [default: %H]

View file

@ -7,7 +7,7 @@
# #
# set -g @copyrat-keytable "foobar" # set -g @copyrat-keytable "foobar"
# set -g @copyrat-keyswitch "z" # set -g @copyrat-keyswitch "z"
# set -g @copyrat-match-bg "magenta" # set -g @copyrat-span-bg "magenta"
# #
# and bindings like # and bindings like
# #

View file

@ -15,7 +15,7 @@ fn main() {
let lines = buffer.split('\n').collect::<Vec<_>>(); let lines = buffer.split('\n').collect::<Vec<_>>();
// Execute copyrat over the buffer (will take control over stdout). // Execute copyrat over the buffer (will take control over stdout).
// This returns the selected matche. // This returns the selected span of text.
let selection: Option<Selection> = run(&lines, &opt); let selection: Option<Selection> = run(&lines, &opt);
// Early exit, signaling no selections were found. // Early exit, signaling no selections were found.

View file

@ -40,18 +40,18 @@ pub struct Config {
#[clap(short, long)] #[clap(short, long)]
pub reverse: bool, pub reverse: bool,
/// Keep the same hint for identical matches. /// Keep the same hint for identical spans.
#[clap(short, long)] #[clap(short, long)]
pub unique_hint: bool, pub unique_hint: bool,
/// Move focus back to first/last match. /// Move focus back to first/last span.
#[clap(short = 'w', long)] #[clap(short = 'w', long)]
pub focus_wrap_around: bool, pub focus_wrap_around: bool,
#[clap(flatten)] #[clap(flatten)]
pub colors: ui::colors::UiColors, pub colors: ui::colors::UiColors,
/// Align hint with its match. /// Align hint with its span.
#[clap(long, arg_enum, default_value = "leading")] #[clap(long, arg_enum, default_value = "leading")]
pub hint_alignment: ui::HintAlignment, pub hint_alignment: ui::HintAlignment,

View file

@ -80,12 +80,8 @@ impl ConfigExt {
wrapped.unique_hint = value.parse::<bool>()?; wrapped.unique_hint = value.parse::<bool>()?;
} }
"@copyrat-match-fg" => { "@copyrat-span-fg" => wrapped.colors.span_fg = ui::colors::parse_color(value)?,
wrapped.colors.match_fg = ui::colors::parse_color(value)? "@copyrat-span-bg" => wrapped.colors.span_bg = ui::colors::parse_color(value)?,
}
"@copyrat-match-bg" => {
wrapped.colors.match_bg = ui::colors::parse_color(value)?
}
"@copyrat-focused-fg" => { "@copyrat-focused-fg" => {
wrapped.colors.focused_fg = ui::colors::parse_color(value)? wrapped.colors.focused_fg = ui::colors::parse_color(value)?
} }

View file

@ -20,7 +20,7 @@ pub fn run(lines: &[&str], opt: &config::basic::Config) -> Option<ui::Selection>
opt.unique_hint, opt.unique_hint,
); );
if model.matches.is_empty() { if model.spans.is_empty() {
return None; return None;
} }

View file

@ -139,35 +139,35 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn simple_matches() { fn simple_hints() {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let hints = alphabet.make_hints(3); let hints = alphabet.make_hints(3);
assert_eq!(hints, ["a", "b", "c"]); assert_eq!(hints, ["a", "b", "c"]);
} }
#[test] #[test]
fn composed_matches() { fn composed_hints() {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let hints = alphabet.make_hints(6); let hints = alphabet.make_hints(6);
assert_eq!(hints, ["a", "b", "c", "da", "db", "dc"]); assert_eq!(hints, ["a", "b", "c", "da", "db", "dc"]);
} }
#[test] #[test]
fn composed_matches_multiple() { fn composed_hints_multiple() {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let hints = alphabet.make_hints(8); let hints = alphabet.make_hints(8);
assert_eq!(hints, ["a", "b", "ca", "cb", "da", "db", "dc", "dd"]); assert_eq!(hints, ["a", "b", "ca", "cb", "da", "db", "dc", "dd"]);
} }
#[test] #[test]
fn composed_matches_max_2() { fn composed_hints_max_2() {
let alphabet = Alphabet("ab".to_string()); let alphabet = Alphabet("ab".to_string());
let hints = alphabet.make_hints(4); let hints = alphabet.make_hints(4);
assert_eq!(hints, ["aa", "ab", "ba", "bb"]); assert_eq!(hints, ["aa", "ab", "ba", "bb"]);
} }
#[test] #[test]
fn composed_matches_max_4() { fn composed_hints_max_4() {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let hints = alphabet.make_hints(13); let hints = alphabet.make_hints(13);
assert_eq!( assert_eq!(
@ -177,7 +177,7 @@ mod tests {
} }
#[test] #[test]
fn matches_with_longest_alphabet() { fn hints_with_longest_alphabet() {
let alphabet = Alphabet("ab".to_string()); let alphabet = Alphabet("ab".to_string());
let hints = alphabet.make_hints(2500); let hints = alphabet.make_hints(2500);
assert_eq!(hints.len(), 2500); assert_eq!(hints.len(), 2500);
@ -186,7 +186,7 @@ mod tests {
} }
#[test] #[test]
fn matches_exceed_longest_alphabet() { fn hints_exceed_longest_alphabet() {
let alphabet = Alphabet("ab".to_string()); let alphabet = Alphabet("ab".to_string());
let hints = alphabet.make_hints(10000); let hints = alphabet.make_hints(10000);
// 2500 unique hints are produced from the longest alphabet // 2500 unique hints are produced from the longest alphabet

View file

@ -1,10 +0,0 @@
/// 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,
}

View file

@ -1,11 +1,11 @@
pub(crate) mod alphabet; pub(crate) mod alphabet;
mod matches;
mod model; mod model;
mod raw_match; mod raw_span;
pub(crate) mod regexes; pub(crate) mod regexes;
mod span;
pub use matches::Match;
pub use model::Model; pub use model::Model;
pub use span::Span;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -22,7 +22,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -31,11 +31,11 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 3); assert_eq!(spans.len(), 3);
assert_eq!(results.first().unwrap().hint, "a"); assert_eq!(spans.first().unwrap().hint, "a");
assert_eq!(results.last().unwrap().hint, "c"); assert_eq!(spans.last().unwrap().hint, "c");
} }
#[test] #[test]
@ -48,7 +48,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = true; let unique_hint = true;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -57,11 +57,11 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 3); assert_eq!(spans.len(), 3);
assert_eq!(results.first().unwrap().hint, "a"); assert_eq!(spans.first().unwrap().hint, "a");
assert_eq!(results.last().unwrap().hint, "a"); assert_eq!(spans.last().unwrap().hint, "a");
} }
#[test] #[test]
@ -74,7 +74,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -83,11 +83,11 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 1); assert_eq!(spans.len(), 1);
assert_eq!( assert_eq!(
results.get(0).unwrap().text, spans.get(0).unwrap().text,
"30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4" "30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4"
); );
} }
@ -103,7 +103,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = true; let reverse = true;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -112,12 +112,12 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 3); assert_eq!(spans.len(), 3);
assert_eq!(results.get(0).unwrap().text, "/var/log/nginx.log"); assert_eq!(spans.get(0).unwrap().text, "/var/log/nginx.log");
assert_eq!(results.get(1).unwrap().text, "test/log/nginx-2.log"); assert_eq!(spans.get(1).unwrap().text, "test/log/nginx-2.log");
assert_eq!(results.get(2).unwrap().text, "folder/.nginx@4df2.log"); assert_eq!(spans.get(2).unwrap().text, "folder/.nginx@4df2.log");
} }
#[test] #[test]
@ -131,7 +131,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -140,12 +140,12 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 3); assert_eq!(spans.len(), 3);
assert_eq!(results.get(0).unwrap().text, "/tmp/foo/bar_lol"); assert_eq!(spans.get(0).unwrap().text, "/tmp/foo/bar_lol");
assert_eq!(results.get(1).unwrap().text, "/var/log/boot-strap.log"); assert_eq!(spans.get(1).unwrap().text, "/var/log/boot-strap.log");
assert_eq!(results.get(2).unwrap().text, "../log/kern.log"); assert_eq!(spans.get(2).unwrap().text, "../log/kern.log");
} }
#[test] #[test]
@ -158,7 +158,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -167,10 +167,10 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 1); assert_eq!(spans.len(), 1);
assert_eq!(results.get(0).unwrap().text, "~/.gnu/.config.txt"); assert_eq!(spans.get(0).unwrap().text, "~/.gnu/.config.txt");
} }
#[test] #[test]
@ -183,7 +183,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -192,9 +192,9 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 1); assert_eq!(spans.len(), 1);
} }
#[test] #[test]
@ -207,7 +207,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -216,14 +216,14 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 4); assert_eq!(spans.len(), 4);
assert_eq!(results.get(0).unwrap().text, "fd70b5695"); assert_eq!(spans.get(0).unwrap().text, "fd70b5695");
assert_eq!(results.get(1).unwrap().text, "5246ddf"); assert_eq!(spans.get(1).unwrap().text, "5246ddf");
assert_eq!(results.get(2).unwrap().text, "f924213"); assert_eq!(spans.get(2).unwrap().text, "f924213");
assert_eq!( assert_eq!(
results.get(3).unwrap().text, spans.get(3).unwrap().text,
"973113963b491874ab2e372ee60d4b4cb75f717c" "973113963b491874ab2e372ee60d4b4cb75f717c"
); );
} }
@ -238,7 +238,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -247,15 +247,15 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 3); assert_eq!(spans.len(), 3);
assert_eq!(results.get(0).unwrap().pattern, "ipv4"); assert_eq!(spans.get(0).unwrap().pattern, "ipv4");
assert_eq!(results.get(0).unwrap().text, "127.0.0.1"); assert_eq!(spans.get(0).unwrap().text, "127.0.0.1");
assert_eq!(results.get(1).unwrap().pattern, "ipv4"); assert_eq!(spans.get(1).unwrap().pattern, "ipv4");
assert_eq!(results.get(1).unwrap().text, "255.255.10.255"); assert_eq!(spans.get(1).unwrap().text, "255.255.10.255");
assert_eq!(results.get(2).unwrap().pattern, "ipv4"); assert_eq!(spans.get(2).unwrap().pattern, "ipv4");
assert_eq!(results.get(2).unwrap().text, "127.0.0.1"); assert_eq!(spans.get(2).unwrap().text, "127.0.0.1");
} }
#[test] #[test]
@ -268,7 +268,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -277,16 +277,16 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 4); assert_eq!(spans.len(), 4);
assert_eq!(results.get(0).unwrap().text, "fe80::2:202:fe4"); assert_eq!(spans.get(0).unwrap().text, "fe80::2:202:fe4");
assert_eq!( assert_eq!(
results.get(1).unwrap().text, spans.get(1).unwrap().text,
"2001:67c:670:202:7ba8:5e41:1591:d723" "2001:67c:670:202:7ba8:5e41:1591:d723"
); );
assert_eq!(results.get(2).unwrap().text, "fe80::2:1"); assert_eq!(spans.get(2).unwrap().text, "fe80::2:1");
assert_eq!(results.get(3).unwrap().text, "fe80:22:312:fe::1%eth0"); assert_eq!(spans.get(3).unwrap().text, "fe80:22:312:fe::1%eth0");
} }
#[test] #[test]
@ -300,7 +300,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -309,13 +309,13 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 2); assert_eq!(spans.len(), 2);
assert_eq!(results.get(0).unwrap().pattern, "markdown-url"); assert_eq!(spans.get(0).unwrap().pattern, "markdown-url");
assert_eq!(results.get(0).unwrap().text, "https://github.io?foo=bar"); assert_eq!(spans.get(0).unwrap().text, "https://github.io?foo=bar");
assert_eq!(results.get(1).unwrap().pattern, "markdown-url"); assert_eq!(spans.get(1).unwrap().pattern, "markdown-url");
assert_eq!(results.get(1).unwrap().text, "http://cdn.com/img.jpg"); assert_eq!(spans.get(1).unwrap().text, "http://cdn.com/img.jpg");
} }
#[test] #[test]
@ -328,7 +328,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -337,20 +337,20 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 4); assert_eq!(spans.len(), 4);
assert_eq!( assert_eq!(
results.get(0).unwrap().text, spans.get(0).unwrap().text,
"https://www.rust-lang.org/tools" "https://www.rust-lang.org/tools"
); );
assert_eq!(results.get(0).unwrap().pattern, "url"); assert_eq!(spans.get(0).unwrap().pattern, "url");
assert_eq!(results.get(1).unwrap().text, "https://crates.io"); assert_eq!(spans.get(1).unwrap().text, "https://crates.io");
assert_eq!(results.get(1).unwrap().pattern, "url"); assert_eq!(spans.get(1).unwrap().pattern, "url");
assert_eq!(results.get(2).unwrap().text, "https://github.io?foo=bar"); assert_eq!(spans.get(2).unwrap().text, "https://github.io?foo=bar");
assert_eq!(results.get(2).unwrap().pattern, "url"); assert_eq!(spans.get(2).unwrap().pattern, "url");
assert_eq!(results.get(3).unwrap().text, "ssh://github.io"); assert_eq!(spans.get(3).unwrap().text, "ssh://github.io");
assert_eq!(results.get(3).unwrap().pattern, "url"); assert_eq!(spans.get(3).unwrap().pattern, "url");
} }
#[test] #[test]
@ -364,7 +364,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -373,17 +373,14 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 2); assert_eq!(spans.len(), 2);
assert_eq!(results.get(0).unwrap().pattern, "email"); assert_eq!(spans.get(0).unwrap().pattern, "email");
assert_eq!(spans.get(0).unwrap().text, "first.last+social@example.com");
assert_eq!(spans.get(1).unwrap().pattern, "email");
assert_eq!( assert_eq!(
results.get(0).unwrap().text, spans.get(1).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" "john@server.department.company.com"
); );
} }
@ -398,7 +395,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -407,15 +404,15 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 3); assert_eq!(spans.len(), 3);
assert_eq!(results.get(0).unwrap().pattern, "mem-address"); assert_eq!(spans.get(0).unwrap().pattern, "mem-address");
assert_eq!(results.get(0).unwrap().text, "0xfd70b5695"); assert_eq!(spans.get(0).unwrap().text, "0xfd70b5695");
assert_eq!(results.get(1).unwrap().pattern, "mem-address"); assert_eq!(spans.get(1).unwrap().pattern, "mem-address");
assert_eq!(results.get(1).unwrap().text, "0x5246ddf"); assert_eq!(spans.get(1).unwrap().text, "0x5246ddf");
assert_eq!(results.get(2).unwrap().pattern, "mem-address"); assert_eq!(spans.get(2).unwrap().pattern, "mem-address");
assert_eq!(results.get(2).unwrap().text, "0x973113"); assert_eq!(spans.get(2).unwrap().text, "0x973113");
} }
#[test] #[test]
@ -428,7 +425,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -437,13 +434,13 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 4); assert_eq!(spans.len(), 4);
assert_eq!(results.get(0).unwrap().text, "#fd7b56"); assert_eq!(spans.get(0).unwrap().text, "#fd7b56");
assert_eq!(results.get(1).unwrap().text, "#FF00FF"); assert_eq!(spans.get(1).unwrap().text, "#FF00FF");
assert_eq!(results.get(2).unwrap().text, "#00fF05"); assert_eq!(spans.get(2).unwrap().text, "#00fF05");
assert_eq!(results.get(3).unwrap().text, "#abcd00"); assert_eq!(spans.get(3).unwrap().text, "#abcd00");
} }
#[test] #[test]
@ -456,7 +453,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -465,11 +462,11 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 1); assert_eq!(spans.len(), 1);
assert_eq!( assert_eq!(
results.get(0).unwrap().text, spans.get(0).unwrap().text,
"QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ" "QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ"
); );
} }
@ -484,7 +481,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -493,9 +490,9 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 8); assert_eq!(spans.len(), 8);
} }
#[test] #[test]
@ -508,7 +505,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -517,11 +514,11 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 1); assert_eq!(spans.len(), 1);
assert_eq!(results.get(0).unwrap().pattern, "diff-a"); assert_eq!(spans.get(0).unwrap().pattern, "diff-a");
assert_eq!(results.get(0).unwrap().text, "src/main.rs"); assert_eq!(spans.get(0).unwrap().text, "src/main.rs");
} }
#[test] #[test]
@ -534,7 +531,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -543,11 +540,11 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 1); assert_eq!(spans.len(), 1);
assert_eq!(results.get(0).unwrap().pattern, "diff-b"); assert_eq!(spans.get(0).unwrap().pattern, "diff-b");
assert_eq!(results.get(0).unwrap().text, "src/main.rs"); assert_eq!(spans.get(0).unwrap().text, "src/main.rs");
} }
#[test] #[test]
@ -563,7 +560,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -572,22 +569,22 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 9); assert_eq!(spans.len(), 9);
assert_eq!(results.get(0).unwrap().text, "http://foo.bar"); assert_eq!(spans.get(0).unwrap().text, "http://foo.bar");
assert_eq!(results.get(1).unwrap().text, "CUSTOM-52463"); assert_eq!(spans.get(1).unwrap().text, "CUSTOM-52463");
assert_eq!(results.get(2).unwrap().text, "ISSUE-123"); assert_eq!(spans.get(2).unwrap().text, "ISSUE-123");
assert_eq!(results.get(3).unwrap().text, "/var/fd70b569/9999.log"); assert_eq!(spans.get(3).unwrap().text, "/var/fd70b569/9999.log");
assert_eq!(results.get(4).unwrap().text, "52463"); assert_eq!(spans.get(4).unwrap().text, "52463");
assert_eq!(results.get(5).unwrap().text, "973113"); assert_eq!(spans.get(5).unwrap().text, "973113");
assert_eq!( assert_eq!(
results.get(6).unwrap().text, spans.get(6).unwrap().text,
"123e4567-e89b-12d3-a456-426655440000" "123e4567-e89b-12d3-a456-426655440000"
); );
assert_eq!(results.get(7).unwrap().text, "8888"); assert_eq!(spans.get(7).unwrap().text, "8888");
assert_eq!( assert_eq!(
results.get(8).unwrap().text, spans.get(8).unwrap().text,
"https://crates.io/23456/fd70b569" "https://crates.io/23456/fd70b569"
); );
} }
@ -605,7 +602,7 @@ mod tests {
let alphabet = Alphabet("abcd".to_string()); let alphabet = Alphabet("abcd".to_string());
let reverse = false; let reverse = false;
let unique_hint = false; let unique_hint = false;
let results = Model::new( let spans = Model::new(
&lines, &lines,
&alphabet, &alphabet,
use_all_patterns, use_all_patterns,
@ -614,12 +611,12 @@ mod tests {
reverse, reverse,
unique_hint, unique_hint,
) )
.matches; .spans;
assert_eq!(results.len(), 2); assert_eq!(spans.len(), 2);
assert_eq!(results.get(0).unwrap().text, "http://foo.bar"); assert_eq!(spans.get(0).unwrap().text, "http://foo.bar");
assert_eq!( assert_eq!(
results.get(1).unwrap().text, spans.get(1).unwrap().text,
"https://crates.io/23456/fd70b569" "https://crates.io/23456/fd70b569"
); );
} }

View file

@ -4,22 +4,20 @@ use regex::Regex;
use sequence_trie::SequenceTrie; use sequence_trie::SequenceTrie;
use super::alphabet::Alphabet; use super::alphabet::Alphabet;
use super::matches::Match; use super::raw_span::RawSpan;
use super::raw_match::RawMatch;
use super::regexes::{NamedPattern, EXCLUDE_PATTERNS, PATTERNS}; use super::regexes::{NamedPattern, EXCLUDE_PATTERNS, PATTERNS};
use super::span::Span;
/// Holds data for the `Ui`. /// Holds data for the `Ui`.
pub struct Model<'a> { pub struct Model<'a> {
// buffer: &'a str,
pub lines: &'a [&'a str], pub lines: &'a [&'a str],
pub reverse: bool, pub reverse: bool,
pub matches: Vec<Match<'a>>, pub spans: Vec<Span<'a>>,
pub lookup_trie: SequenceTrie<char, usize>, pub lookup_trie: SequenceTrie<char, usize>,
} }
impl<'a> Model<'a> { impl<'a> Model<'a> {
pub fn new( pub fn new(
// buffer: &'a str,
lines: &'a [&'a str], lines: &'a [&'a str],
alphabet: &'a Alphabet, alphabet: &'a Alphabet,
use_all_patterns: bool, use_all_patterns: bool,
@ -28,36 +26,34 @@ impl<'a> Model<'a> {
reverse: bool, reverse: bool,
unique_hint: bool, unique_hint: bool,
) -> Model<'a> { ) -> Model<'a> {
// let lines = buffer.split('\n').collect::<Vec<_>>(); let mut raw_spans =
find_raw_spans(&lines, named_patterns, custom_patterns, use_all_patterns);
let mut raw_matches =
raw_matches(&lines, named_patterns, custom_patterns, use_all_patterns);
if reverse { if reverse {
raw_matches.reverse(); raw_spans.reverse();
} }
let mut matches = associate_hints(&raw_matches, alphabet, unique_hint); let mut spans = associate_hints(&raw_spans, alphabet, unique_hint);
if reverse { if reverse {
matches.reverse(); spans.reverse();
} }
let lookup_trie = build_lookup_trie(&matches); let lookup_trie = build_lookup_trie(&spans);
Model { Model {
// buffer, // buffer,
lines, lines,
reverse, reverse,
matches, spans,
lookup_trie, lookup_trie,
} }
} }
} }
/// Internal function that searches the model's lines for pattern matches. /// Internal function that searches the model's lines for pattern matches.
/// Returns a vector of `RawMatch`es (text, location, pattern id) without /// Returns a vector of `RawSpan` (text, location, pattern id) without
/// an associated hint. The hint is attached to `Match`, not to `RawMatch`. /// an associated hint. The hint is attached to `Span`, not to `RawSpan`.
/// ///
/// # Notes /// # Notes
/// ///
@ -65,12 +61,12 @@ impl<'a> Model<'a> {
/// ///
/// If no named patterns were specified, it will search for all available /// If no named patterns were specified, it will search for all available
/// patterns from the `PATTERNS` catalog. /// patterns from the `PATTERNS` catalog.
fn raw_matches<'a>( fn find_raw_spans<'a>(
lines: &'a [&'a str], lines: &'a [&'a str],
named_patterns: &'a [NamedPattern], named_patterns: &'a [NamedPattern],
custom_patterns: &'a [String], custom_patterns: &'a [String],
use_all_patterns: bool, use_all_patterns: bool,
) -> Vec<RawMatch<'a>> { ) -> Vec<RawSpan<'a>> {
let exclude_regexes = EXCLUDE_PATTERNS let exclude_regexes = EXCLUDE_PATTERNS
.iter() .iter()
.map(|&(name, pattern)| (name, Regex::new(pattern).unwrap())) .map(|&(name, pattern)| (name, Regex::new(pattern).unwrap()))
@ -100,7 +96,7 @@ fn raw_matches<'a>(
let all_regexes = [exclude_regexes, custom_regexes, regexes].concat(); let all_regexes = [exclude_regexes, custom_regexes, regexes].concat();
let mut raw_matches = Vec::new(); let mut raw_spans = Vec::new();
for (index, line) in lines.iter().enumerate() { for (index, line) in lines.iter().enumerate() {
// Chunk is the remainder of the line to be searched for matches. // Chunk is the remainder of the line to be searched for matches.
@ -110,7 +106,7 @@ fn raw_matches<'a>(
// Use all avail regexes to match the chunk and select the match // Use all avail regexes to match the chunk and select the match
// occuring the earliest on the chunk. Save its matched text and // occuring the earliest on the chunk. Save its matched text and
// position in a `RawMatch` struct. // position in a `RawSpan` struct.
loop { loop {
// For each avalable regex, use the `find_iter` iterator to // For each avalable regex, use the `find_iter` iterator to
// get the first non-overlapping match in the chunk, returning // get the first non-overlapping match in the chunk, returning
@ -149,7 +145,7 @@ fn raw_matches<'a>(
None => (text, 0), None => (text, 0),
}; };
raw_matches.push(RawMatch { raw_spans.push(RawSpan {
x: offset + reg_match.start() as i32 + substart as i32, x: offset + reg_match.start() as i32 + substart as i32,
y: index as i32, y: index as i32,
pattern: pat_name, pattern: pat_name,
@ -164,54 +160,54 @@ fn raw_matches<'a>(
} }
} }
raw_matches raw_spans
} }
/// Associate a hint to each `RawMatch`, returning a vector of `Match`es. /// Associate a hint to each `RawSpan`, returning a vector of `Span`.
/// ///
/// If `unique` is `true`, all duplicate matches will have the same hint. /// If `unique` is `true`, all duplicate spans will have the same hint.
/// For copying matched text, this seems easier and more natural. /// For copying text spans, this seems easier and more natural.
/// If `unique` is `false`, duplicate matches will have their own hint. /// If `unique` is `false`, duplicate spans will have their own hint.
fn associate_hints<'a>( fn associate_hints<'a>(
raw_matches: &[RawMatch<'a>], raw_spans: &[RawSpan<'a>],
alphabet: &'a Alphabet, alphabet: &'a Alphabet,
unique: bool, unique: bool,
) -> Vec<Match<'a>> { ) -> Vec<Span<'a>> {
let hints = alphabet.make_hints(raw_matches.len()); let hints = alphabet.make_hints(raw_spans.len());
let mut hints_iter = hints.iter(); let mut hints_iter = hints.iter();
let mut result: Vec<Match<'a>> = vec![]; let mut result: Vec<Span<'a>> = vec![];
if unique { if unique {
// Map (text, hint) // Map (text, hint)
let mut known: collections::HashMap<&str, &str> = collections::HashMap::new(); let mut known: collections::HashMap<&str, &str> = collections::HashMap::new();
for raw_mat in raw_matches { for raw_span in raw_spans {
let hint: &str = known.entry(raw_mat.text).or_insert_with(|| { let hint: &str = known.entry(raw_span.text).or_insert_with(|| {
hints_iter hints_iter
.next() .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 { result.push(Span {
x: raw_mat.x, x: raw_span.x,
y: raw_mat.y, y: raw_span.y,
pattern: raw_mat.pattern, pattern: raw_span.pattern,
text: raw_mat.text, text: raw_span.text,
hint: hint.to_string(), hint: hint.to_string(),
}); });
} }
} else { } else {
for raw_mat in raw_matches { for raw_span in raw_spans {
let hint = hints_iter let hint = hints_iter
.next() .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 { result.push(Span {
x: raw_mat.x, x: raw_span.x,
y: raw_mat.y, y: raw_span.y,
pattern: raw_mat.pattern, pattern: raw_span.pattern,
text: raw_mat.text, text: raw_span.text,
hint: hint.to_string(), hint: hint.to_string(),
}); });
} }
@ -222,12 +218,12 @@ fn associate_hints<'a>(
/// Builds a `SequenceTrie` that helps determine if a sequence of keys /// Builds a `SequenceTrie` that helps determine if a sequence of keys
/// entered by the user corresponds to a match. This kind of lookup /// entered by the user corresponds to a match. This kind of lookup
/// directly returns a reference to the corresponding `Match` if any. /// directly returns a reference to the corresponding `Span` if any.
fn build_lookup_trie<'a>(matches: &'a [Match<'a>]) -> SequenceTrie<char, usize> { fn build_lookup_trie<'a>(spans: &'a [Span<'a>]) -> SequenceTrie<char, usize> {
let mut trie = SequenceTrie::new(); let mut trie = SequenceTrie::new();
for (index, mat) in matches.iter().enumerate() { for (index, span) in spans.iter().enumerate() {
let hint_chars = mat.hint.chars().collect::<Vec<char>>(); let hint_chars = span.hint.chars().collect::<Vec<char>>();
// no need to insert twice the same hint // no need to insert twice the same hint
if trie.get(&hint_chars).is_none() { if trie.get(&hint_chars).is_none() {

View file

@ -1,8 +0,0 @@
/// 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,
}

8
src/textbuf/raw_span.rs Normal file
View file

@ -0,0 +1,8 @@
/// Internal surrogate for `Span`, before a Hint has been associated.
#[derive(Debug)]
pub(super) struct RawSpan<'a> {
pub x: i32,
pub y: i32,
pub pattern: &'a str,
pub text: &'a str,
}

10
src/textbuf/span.rs Normal file
View file

@ -0,0 +1,10 @@
/// Represents some span of text, its location on screen, the pattern that
/// created it, and the associated hint.
#[derive(Debug)]
pub struct Span<'a> {
pub x: i32,
pub y: i32,
pub pattern: &'a str,
pub text: &'a str,
pub hint: String,
}

View file

@ -30,7 +30,7 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn match_color() { fn span_color() {
let text1 = format!( let text1 = format!(
"{}{}", "{}{}",
color::Fg(parse_color("green").unwrap().as_ref()), color::Fg(parse_color("green").unwrap().as_ref()),
@ -42,15 +42,15 @@ mod tests {
} }
#[test] #[test]
fn no_match_color() { fn no_span_color() {
assert!(parse_color("wat").is_err(), "this color should not exist"); assert!(parse_color("wat").is_err(), "this color should not exist");
} }
} }
/// Holds color-related data. /// Holds color-related data.
/// ///
/// - `focus_*` colors are used to render the currently focused matched text. /// - `focus_*` colors are used to render the currently focused text span.
/// - `normal_*` colors are used to render other matched text. /// - `normal_*` colors are used to render other text spans.
/// - `hint_*` colors are used to render the hints. /// - `hint_*` colors are used to render the hints.
#[derive(Clap, Debug)] #[derive(Clap, Debug)]
#[clap(about)] // Needed to avoid this doc comment to be used as overall `about`. #[clap(about)] // Needed to avoid this doc comment to be used as overall `about`.
@ -63,22 +63,22 @@ pub struct UiColors {
#[clap(long, default_value = "bright-white", parse(try_from_str = parse_color))] #[clap(long, default_value = "bright-white", parse(try_from_str = parse_color))]
pub text_bg: Box<dyn color::Color>, pub text_bg: Box<dyn color::Color>,
/// Foreground color for matches. /// Foreground color for spans.
#[clap(long, default_value = "yellow", #[clap(long, default_value = "yellow",
parse(try_from_str = parse_color))] parse(try_from_str = parse_color))]
pub match_fg: Box<dyn color::Color>, pub span_fg: Box<dyn color::Color>,
/// Background color for matches. /// Background color for spans.
#[clap(long, default_value = "bright-white", #[clap(long, default_value = "bright-white",
parse(try_from_str = parse_color))] parse(try_from_str = parse_color))]
pub match_bg: Box<dyn color::Color>, pub span_bg: Box<dyn color::Color>,
/// Foreground color for the focused match. /// Foreground color for the focused span.
#[clap(long, default_value = "magenta", #[clap(long, default_value = "magenta",
parse(try_from_str = parse_color))] parse(try_from_str = parse_color))]
pub focused_fg: Box<dyn color::Color>, pub focused_fg: Box<dyn color::Color>,
/// Background color for the focused match. /// Background color for the focused span.
#[clap(long, default_value = "bright-white", #[clap(long, default_value = "bright-white",
parse(try_from_str = parse_color))] parse(try_from_str = parse_color))]
pub focused_bg: Box<dyn color::Color>, pub focused_bg: Box<dyn color::Color>,

View file

@ -3,7 +3,7 @@
//! //!
//! In particular, the `Ui` struct //! In particular, the `Ui` struct
//! //!
//! - renders text, matched text and hints from the structured buffer content //! - renders text, spans and hints from the structured buffer content
//! to the screen, //! to the screen,
//! - listens for keypress events, //! - listens for keypress events,
//! - and returns the user selection in the form of a `Selection` struct. //! - and returns the user selection in the form of a `Selection` struct.
@ -12,8 +12,8 @@
//! //!
//! - navigate the buffer (in case it is larger than the number of lines in //! - navigate the buffer (in case it is larger than the number of lines in
//! the terminal) //! the terminal)
//! - move the focus from one match to another //! - move the focus from one span to another
//! - select one of the matches //! - select one of the available spans
//! - toggle the output destination (tmux buffer or clipboard) //! - toggle the output destination (tmux buffer or clipboard)
//! //!

View file

@ -33,7 +33,7 @@ impl<'a> ViewController<'a> {
hint_style: Option<HintStyle>, hint_style: Option<HintStyle>,
) -> ViewController<'a> { ) -> ViewController<'a> {
let focus_index = if model.reverse { let focus_index = if model.reverse {
model.matches.len() - 1 model.spans.len() - 1
} else { } else {
0 0
}; };
@ -57,10 +57,10 @@ impl<'a> ViewController<'a> {
// }}} // }}}
// Coordinates {{{1 // Coordinates {{{1
/// Convert the `Match` text into the coordinates of the wrapped lines. /// Convert the `Span` text into the coordinates of the wrapped lines.
/// ///
/// Compute the new x offset of the text as the remainder of the line width /// Compute the new x offset of the text as the remainder of the line width
/// (e.g. the match could start at offset 120 in a 80-width terminal, the new /// (e.g. the Span could start at offset 120 in a 80-width terminal, the new
/// offset being 40). /// offset being 40).
/// ///
/// Compute the new y offset of the text as the initial y offset plus any /// Compute the new y offset of the text as the initial y offset plus any
@ -76,20 +76,20 @@ impl<'a> ViewController<'a> {
(new_offset_x, new_offset_y) (new_offset_x, new_offset_y)
} }
/// Returns screen offset of a given `Match`. /// Returns screen offset of a given `Span`.
/// ///
/// If multibyte characters occur before the hint (in the "prefix"), then /// If multibyte characters occur before the hint (in the "prefix"), then
/// their compouding takes less space on screen when printed: for /// their compouding takes less space on screen when printed: for
/// instance ´ + e = é. Consequently the hint offset has to be adjusted /// instance ´ + e = é. Consequently the hint offset has to be adjusted
/// to the left. /// to the left.
fn match_offsets(&self, mat: &textbuf::Match<'a>) -> (usize, usize) { fn span_offsets(&self, span: &textbuf::Span<'a>) -> (usize, usize) {
let offset_x = { let offset_x = {
let line = &self.model.lines[mat.y as usize]; let line = &self.model.lines[span.y as usize];
let prefix = &line[0..mat.x as usize]; let prefix = &line[0..span.x as usize];
let adjust = prefix.len() - prefix.chars().count(); let adjust = prefix.len() - prefix.chars().count();
(mat.x as usize) - (adjust) (span.x as usize) - (adjust)
}; };
let offset_y = mat.y as usize; let offset_y = span.y as usize;
(offset_x, offset_y) (offset_x, offset_y)
} }
@ -98,12 +98,12 @@ impl<'a> ViewController<'a> {
// Focus management {{{1 // Focus management {{{1
/// Move focus onto the previous hint, returning both the index of the /// Move focus onto the previous hint, returning both the index of the
/// previously focused match, and the index of the newly focused one. /// previously focused Span, and the index of the newly focused one.
fn prev_focus_index(&mut self) -> (usize, usize) { fn prev_focus_index(&mut self) -> (usize, usize) {
let old_index = self.focus_index; let old_index = self.focus_index;
if self.focus_wrap_around { if self.focus_wrap_around {
if self.focus_index == 0 { if self.focus_index == 0 {
self.focus_index = self.model.matches.len() - 1; self.focus_index = self.model.spans.len() - 1;
} else { } else {
self.focus_index -= 1; self.focus_index -= 1;
} }
@ -115,16 +115,16 @@ impl<'a> ViewController<'a> {
} }
/// Move focus onto the next hint, returning both the index of the /// Move focus onto the next hint, returning both the index of the
/// previously focused match, and the index of the newly focused one. /// previously focused Span, and the index of the newly focused one.
fn next_focus_index(&mut self) -> (usize, usize) { fn next_focus_index(&mut self) -> (usize, usize) {
let old_index = self.focus_index; let old_index = self.focus_index;
if self.focus_wrap_around { if self.focus_wrap_around {
if self.focus_index == self.model.matches.len() - 1 { if self.focus_index == self.model.spans.len() - 1 {
self.focus_index = 0; self.focus_index = 0;
} else { } else {
self.focus_index += 1; self.focus_index += 1;
} }
} else if self.focus_index < self.model.matches.len() - 1 { } else if self.focus_index < self.model.spans.len() - 1 {
self.focus_index += 1; self.focus_index += 1;
} }
let new_index = self.focus_index; let new_index = self.focus_index;
@ -136,7 +136,7 @@ impl<'a> ViewController<'a> {
/// Render entire model lines on provided writer. /// Render entire model lines on provided writer.
/// ///
/// This renders the basic content on which matches and hints can be rendered. /// This renders the basic content on which spans and hints can be rendered.
/// ///
/// # Notes /// # Notes
/// - All trailing whitespaces are trimmed, empty lines are skipped. /// - All trailing whitespaces are trimmed, empty lines are skipped.
@ -181,7 +181,7 @@ impl<'a> ViewController<'a> {
.unwrap(); .unwrap();
} }
/// Render the Match's `text` field on provided writer using the `match_*g` color. /// Render the Span's `text` field on provided writer using the `span_*g` color.
/// ///
/// If a Mach is "focused", it is then rendered with the `focused_*g` colors. /// If a Mach is "focused", it is then rendered with the `focused_*g` colors.
/// ///
@ -195,14 +195,14 @@ impl<'a> ViewController<'a> {
offset: (usize, usize), offset: (usize, usize),
colors: &UiColors, colors: &UiColors,
) { ) {
// To help identify it, the match thas has focus is rendered with a dedicated color. // To help identify it, the span thas has focus is rendered with a dedicated color.
let (fg_color, bg_color) = if focused { let (fg_color, bg_color) = if focused {
(&colors.focused_fg, &colors.focused_bg) (&colors.focused_fg, &colors.focused_bg)
} else { } else {
(&colors.match_fg, &colors.match_bg) (&colors.span_fg, &colors.span_bg)
}; };
// Render just the Match's text on top of existing content. // Render just the Span's text on top of existing content.
write!( write!(
stdout, stdout,
"{goto}{bg_color}{fg_color}{text}{fg_reset}{bg_reset}", "{goto}{bg_color}{fg_color}{text}{fg_reset}{bg_reset}",
@ -216,7 +216,7 @@ impl<'a> ViewController<'a> {
.unwrap(); .unwrap();
} }
/// Render a Match's `hint` field on the provided writer. /// Render a Span's `hint` field on the provided writer.
/// ///
/// This renders the hint according to some provided style: /// This renders the hint according to some provided style:
/// - just colors /// - just colors
@ -318,12 +318,12 @@ impl<'a> ViewController<'a> {
} }
} }
/// Convenience function that renders both the matched text and its hint, /// Convenience function that renders both the text span and its hint,
/// if focused. /// if focused.
fn render_match(&self, stdout: &mut dyn io::Write, mat: &textbuf::Match<'a>, focused: bool) { fn render_span(&self, stdout: &mut dyn io::Write, span: &textbuf::Span<'a>, focused: bool) {
let text = mat.text; let text = span.text;
let (offset_x, offset_y) = self.match_offsets(mat); let (offset_x, offset_y) = self.span_offsets(span);
let (offset_x, offset_y) = self.map_coords_to_wrapped_space(offset_x, offset_y); let (offset_x, offset_y) = self.map_coords_to_wrapped_space(offset_x, offset_y);
ViewController::render_matched_text( ViewController::render_matched_text(
@ -336,16 +336,16 @@ impl<'a> ViewController<'a> {
if !focused { if !focused {
// If not focused, render the hint (e.g. "eo") as an overlay on // If not focused, render the hint (e.g. "eo") as an overlay on
// top of the rendered matched text, aligned at its leading or the // top of the rendered text span, aligned at its leading or the
// trailing edge. // trailing edge.
let extra_offset = match self.hint_alignment { let extra_offset = match self.hint_alignment {
HintAlignment::Leading => 0, HintAlignment::Leading => 0,
HintAlignment::Trailing => text.len() - mat.hint.len(), HintAlignment::Trailing => text.len() - span.hint.len(),
}; };
ViewController::render_matched_hint( ViewController::render_matched_hint(
stdout, stdout,
&mat.hint, &span.hint,
(offset_x + extra_offset, offset_y), (offset_x + extra_offset, offset_y),
&self.rendering_colors, &self.rendering_colors,
&self.hint_style, &self.hint_style,
@ -357,15 +357,15 @@ impl<'a> ViewController<'a> {
/// ///
/// This renders in 3 phases: /// This renders in 3 phases:
/// - all lines are rendered verbatim /// - all lines are rendered verbatim
/// - each Match's `text` is rendered as an overlay on top of it /// - each Span's `text` is rendered as an overlay on top of it
/// - each Match's `hint` text is rendered as a final overlay /// - each Span's `hint` text is rendered as a final overlay
/// ///
/// Depending on the value of `self.hint_alignment`, the hint can be rendered /// Depending on the value of `self.hint_alignment`, the hint can be rendered
/// on the leading edge of the underlying Match's `text`, /// on the leading edge of the underlying Span's `text`,
/// or on the trailing edge. /// or on the trailing edge.
/// ///
/// # Note /// # Note
/// Multibyte characters are taken into account, so that the Match's `text` /// Multibyte characters are taken into account, so that the Span's `text`
/// and `hint` are rendered in their proper position. /// 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. // 1. Trim all lines and render non-empty ones.
@ -376,31 +376,31 @@ impl<'a> ViewController<'a> {
&self.rendering_colors, &self.rendering_colors,
); );
for (index, mat) in self.model.matches.iter().enumerate() { for (index, span) in self.model.spans.iter().enumerate() {
let focused = index == self.focus_index; let focused = index == self.focus_index;
self.render_match(stdout, mat, focused); self.render_span(stdout, span, focused);
} }
stdout.flush().unwrap(); stdout.flush().unwrap();
} }
/// Render the previous match with its hint, and render the newly focused /// Render the previous span with its hint, and render the newly focused
/// match without its hint. This is more efficient than a full render. /// span without its hint. This is more efficient than a full render.
fn diff_render( fn diff_render(
&self, &self,
stdout: &mut dyn io::Write, stdout: &mut dyn io::Write,
old_focus_index: usize, old_focus_index: usize,
new_focus_index: usize, new_focus_index: usize,
) { ) {
// Render the previously focused match as non-focused // Render the previously focused span as non-focused
let mat = self.model.matches.get(old_focus_index).unwrap(); let span = self.model.spans.get(old_focus_index).unwrap();
let focused = false; let focused = false;
self.render_match(stdout, mat, focused); self.render_span(stdout, span, focused);
// Render the previously focused match as non-focused // Render the previously focused span as non-focused
let mat = self.model.matches.get(new_focus_index).unwrap(); let span = self.model.spans.get(new_focus_index).unwrap();
let focused = true; let focused = true;
self.render_match(stdout, mat, focused); self.render_span(stdout, span, focused);
stdout.flush().unwrap(); stdout.flush().unwrap();
} }
@ -409,7 +409,7 @@ impl<'a> ViewController<'a> {
// Listening {{{1 // Listening {{{1
/// Listen to keys entered on stdin, moving focus accordingly, or /// Listen to keys entered on stdin, moving focus accordingly, or
/// selecting one match. /// selecting one span.
/// ///
/// # Panics /// # Panics
/// ///
@ -417,7 +417,7 @@ impl<'a> ViewController<'a> {
fn listen(&mut self, reader: &mut dyn io::Read, writer: &mut dyn io::Write) -> Event { fn listen(&mut self, reader: &mut dyn io::Read, writer: &mut dyn io::Write) -> Event {
use termion::input::TermRead; // Trait for `reader.keys().next()`. use termion::input::TermRead; // Trait for `reader.keys().next()`.
if self.model.matches.is_empty() { if self.model.spans.is_empty() {
return Event::Exit; return Event::Exit;
} }
@ -448,7 +448,7 @@ impl<'a> ViewController<'a> {
break; break;
} }
// Move focus to next/prev match. // Move focus to next/prev span.
event::Key::Up => { event::Key::Up => {
let (old_index, focused_index) = self.prev_focus_index(); let (old_index, focused_index) = self.prev_focus_index();
self.diff_render(writer, old_index, focused_index); self.diff_render(writer, old_index, focused_index);
@ -484,16 +484,16 @@ impl<'a> ViewController<'a> {
// Yank/copy // Yank/copy
event::Key::Char(_ch @ 'y') | event::Key::Char(_ch @ '\n') => { event::Key::Char(_ch @ 'y') | event::Key::Char(_ch @ '\n') => {
let text = self.model.matches.get(self.focus_index).unwrap().text; let text = self.model.spans.get(self.focus_index).unwrap().text;
return Event::Match(Selection { return Event::Select(Selection {
text: text.to_string(), text: text.to_string(),
uppercased: false, uppercased: false,
output_destination, output_destination,
}); });
} }
event::Key::Char(_ch @ 'Y') => { event::Key::Char(_ch @ 'Y') => {
let text = self.model.matches.get(self.focus_index).unwrap().text; let text = self.model.spans.get(self.focus_index).unwrap().text;
return Event::Match(Selection { return Event::Select(Selection {
text: text.to_string(), text: text.to_string(),
uppercased: true, uppercased: true,
output_destination, output_destination,
@ -511,7 +511,7 @@ impl<'a> ViewController<'a> {
// 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. // if the entered key belongs to a longer hint.
// Attempts at finding a match with a corresponding hint. // Attempts at finding a span with a corresponding hint.
// //
// If any of the typed character is caps, the typed hint is // If any of the typed character is caps, the typed hint is
// deemed as uppercased. // deemed as uppercased.
@ -535,12 +535,12 @@ impl<'a> ViewController<'a> {
let node = node.unwrap(); let node = node.unwrap();
if node.is_leaf() { if node.is_leaf() {
// The last key of a hint was entered. // The last key of a hint was entered.
let match_index = node.value().expect( let span_index = node.value().expect(
"By construction, the Lookup Trie should have a value for each leaf.", "By construction, the Lookup Trie should have a value for each leaf.",
); );
let mat = self.model.matches.get(*match_index).expect("By construction, the value in a leaf should correspond to an existing hint."); let span = self.model.spans.get(*span_index).expect("By construction, the value in a leaf should correspond to an existing hint.");
let text = mat.text.to_string(); let text = span.text.to_string();
return Event::Match(Selection { return Event::Select(Selection {
text, text,
uppercased, uppercased,
output_destination, output_destination,
@ -585,7 +585,7 @@ impl<'a> ViewController<'a> {
let selection = match self.listen(&mut stdin, &mut stdout) { let selection = match self.listen(&mut stdin, &mut stdout) {
Event::Exit => None, Event::Exit => None,
Event::Match(selection) => Some(selection), Event::Select(selection) => Some(selection),
}; };
write!(stdout, "{}", cursor::Show).unwrap(); write!(stdout, "{}", cursor::Show).unwrap();
@ -623,10 +623,10 @@ fn get_line_offsets(lines: &[&str], term_width: u16) -> Vec<usize> {
/// Returned value after the `Ui` has finished listening to events. /// Returned value after the `Ui` has finished listening to events.
enum Event { enum Event {
/// Exit with no selected matches, /// Exit with no selected spans,
Exit, Exit,
/// A vector of matched text and whether it was selected with uppercase. /// The selected span of text and whether it was selected with uppercase.
Match(Selection), Select(Selection),
} }
#[cfg(test)] #[cfg(test)]
@ -650,8 +650,8 @@ path: /usr/local/bin/cargo";
text_bg: Box::new(color::White), text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red), focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue), focused_bg: Box::new(color::Blue),
match_fg: Box::new(color::Green), span_fg: Box::new(color::Green),
match_bg: Box::new(color::Magenta), span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow), hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan), hint_bg: Box::new(color::Cyan),
}; };
@ -688,8 +688,8 @@ path: /usr/local/bin/cargo";
text_bg: Box::new(color::White), text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red), focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue), focused_bg: Box::new(color::Blue),
match_fg: Box::new(color::Green), span_fg: Box::new(color::Green),
match_bg: Box::new(color::Magenta), span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow), hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan), hint_bg: Box::new(color::Cyan),
}; };
@ -722,8 +722,8 @@ path: /usr/local/bin/cargo";
text_bg: Box::new(color::White), text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red), focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue), focused_bg: Box::new(color::Blue),
match_fg: Box::new(color::Green), span_fg: Box::new(color::Green),
match_bg: Box::new(color::Magenta), span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow), hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan), hint_bg: Box::new(color::Cyan),
}; };
@ -735,8 +735,8 @@ path: /usr/local/bin/cargo";
format!( format!(
"{goto}{bg}{fg}{text}{fg_reset}{bg_reset}", "{goto}{bg}{fg}{text}{fg_reset}{bg_reset}",
goto = cursor::Goto(4, 2), goto = cursor::Goto(4, 2),
fg = color::Fg(colors.match_fg.as_ref()), fg = color::Fg(colors.span_fg.as_ref()),
bg = color::Bg(colors.match_bg.as_ref()), bg = color::Bg(colors.span_bg.as_ref()),
fg_reset = color::Fg(color::Reset), fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset), bg_reset = color::Bg(color::Reset),
text = &text, text = &text,
@ -755,8 +755,8 @@ path: /usr/local/bin/cargo";
text_bg: Box::new(color::White), text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red), focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue), focused_bg: Box::new(color::Blue),
match_fg: Box::new(color::Green), span_fg: Box::new(color::Green),
match_bg: Box::new(color::Magenta), span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow), hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan), hint_bg: Box::new(color::Cyan),
}; };
@ -797,8 +797,8 @@ path: /usr/local/bin/cargo";
text_bg: Box::new(color::White), text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red), focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue), focused_bg: Box::new(color::Blue),
match_fg: Box::new(color::Green), span_fg: Box::new(color::Green),
match_bg: Box::new(color::Magenta), span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow), hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan), hint_bg: Box::new(color::Cyan),
}; };
@ -841,8 +841,8 @@ path: /usr/local/bin/cargo";
text_bg: Box::new(color::White), text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red), focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue), focused_bg: Box::new(color::Blue),
match_fg: Box::new(color::Green), span_fg: Box::new(color::Green),
match_bg: Box::new(color::Magenta), span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow), hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan), hint_bg: Box::new(color::Cyan),
}; };
@ -876,8 +876,8 @@ path: /usr/local/bin/cargo";
} }
#[test] #[test]
/// Simulates rendering without any match. /// Simulates rendering without any span.
fn test_render_full_without_matches() { fn test_render_full_without_available_spans() {
let buffer = "lorem 127.0.0.1 lorem let buffer = "lorem 127.0.0.1 lorem
Barcelona https://en.wikipedia.org/wiki/Barcelona - "; Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
@ -905,14 +905,14 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
text_bg: Box::new(color::White), text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red), focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue), focused_bg: Box::new(color::Blue),
match_fg: Box::new(color::Green), span_fg: Box::new(color::Green),
match_bg: Box::new(color::Magenta), span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow), hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan), hint_bg: Box::new(color::Cyan),
}; };
let hint_alignment = HintAlignment::Leading; let hint_alignment = HintAlignment::Leading;
// create a Ui without any match // create a Ui without any span
let ui = ViewController { let ui = ViewController {
model: &mut model, model: &mut model,
term_width, term_width,
@ -945,15 +945,12 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
// println!("{:?}", writer); // println!("{:?}", writer);
// println!("{:?}", expected.as_bytes()); // println!("{:?}", expected.as_bytes());
// println!("matches: {}", ui.matches.len());
// println!("lines: {}", lines.len());
assert_eq!(writer, expected.as_bytes()); assert_eq!(writer, expected.as_bytes());
} }
#[test] #[test]
/// Simulates rendering with matches. /// Simulates rendering with available spans.
fn test_render_full_with_matches() { fn test_render_full_with_spans() {
let buffer = "lorem 127.0.0.1 lorem let buffer = "lorem 127.0.0.1 lorem
Barcelona https://en.wikipedia.org/wiki/Barcelona - "; Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
@ -982,8 +979,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
text_bg: Box::new(color::White), text_bg: Box::new(color::White),
focused_fg: Box::new(color::Red), focused_fg: Box::new(color::Red),
focused_bg: Box::new(color::Blue), focused_bg: Box::new(color::Blue),
match_fg: Box::new(color::Green), span_fg: Box::new(color::Green),
match_bg: Box::new(color::Magenta), span_bg: Box::new(color::Magenta),
hint_fg: Box::new(color::Yellow), hint_fg: Box::new(color::Yellow),
hint_bg: Box::new(color::Cyan), hint_bg: Box::new(color::Cyan),
}; };
@ -1021,10 +1018,10 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
let expected_match1_text = { let expected_match1_text = {
let goto7_1 = cursor::Goto(7, 1); let goto7_1 = cursor::Goto(7, 1);
format!( format!(
"{goto7_1}{match_bg}{match_fg}127.0.0.1{fg_reset}{bg_reset}", "{goto7_1}{span_bg}{span_fg}127.0.0.1{fg_reset}{bg_reset}",
goto7_1 = goto7_1, goto7_1 = goto7_1,
match_fg = color::Fg(rendering_colors.match_fg.as_ref()), span_fg = color::Fg(rendering_colors.span_fg.as_ref()),
match_bg = color::Bg(rendering_colors.match_bg.as_ref()), span_bg = color::Bg(rendering_colors.span_bg.as_ref()),
fg_reset = color::Fg(color::Reset), fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset) bg_reset = color::Bg(color::Reset)
) )
@ -1055,7 +1052,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
) )
}; };
// Because reverse is true, this second match is focused, // Because reverse is true, this second span is focused,
// then the hint should not be rendered. // then the hint should not be rendered.
// let expected_match2_hint = { // let expected_match2_hint = {
@ -1090,7 +1087,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
// .find(|(_idx, (&l, &r))| l != r); // .find(|(_idx, (&l, &r))| l != r);
// println!("{:?}", diff_point); // println!("{:?}", diff_point);
assert_eq!(2, ui.model.matches.len()); assert_eq!(2, ui.model.spans.len());
assert_eq!(writer, expected.as_bytes()); assert_eq!(writer, expected.as_bytes());
} }