mirror of
https://github.com/TECHNOFAB11/tmux-copyrat.git
synced 2025-12-12 16:10:07 +01:00
Merge pull request 'Refactoring' (#2) from clean into master
Reviewed-on: https://gitea.grael.cc/grael/tmux-feat: copyrat/pulls/2
This commit is contained in:
commit
9a69cf5819
19 changed files with 542 additions and 484 deletions
16
README.md
16
README.md
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
||||||
12
copyrat.tmux
12
copyrat.tmux
|
|
@ -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
|
||||||
#
|
#
|
||||||
|
|
@ -81,8 +81,10 @@ setup_pattern_binding "p" "--pattern-name path"
|
||||||
setup_pattern_binding "u" "--pattern-name url"
|
setup_pattern_binding "u" "--pattern-name url"
|
||||||
# prefix + t + m searches for Markdown URLs [...](matched.url)
|
# prefix + t + m searches for Markdown URLs [...](matched.url)
|
||||||
setup_pattern_binding "m" "--pattern-name markdown-url"
|
setup_pattern_binding "m" "--pattern-name markdown-url"
|
||||||
# prefix + t + h searches for SHA1/2 (hashes)
|
# prefix + t + h searches for SHA1/2 short or long hashes
|
||||||
setup_pattern_binding "h" "--pattern-name sha"
|
setup_pattern_binding "h" "--pattern-name sha"
|
||||||
|
# prefix + t + d searches for dates or datetimes
|
||||||
|
setup_pattern_binding "d" "--pattern-name datetime"
|
||||||
# prefix + t + e searches for email addresses (see https://www.regular-expressions.info/email.html)
|
# prefix + t + e searches for email addresses (see https://www.regular-expressions.info/email.html)
|
||||||
setup_pattern_binding "e" "--pattern-name email"
|
setup_pattern_binding "e" "--pattern-name email"
|
||||||
# prefix + t + D searches for docker shas
|
# prefix + t + D searches for docker shas
|
||||||
|
|
@ -93,10 +95,10 @@ setup_pattern_binding "c" "--pattern-name hexcolor"
|
||||||
setup_pattern_binding "U" "--pattern-name uuid"
|
setup_pattern_binding "U" "--pattern-name uuid"
|
||||||
# prefix + t + v searches for version numbers
|
# prefix + t + v searches for version numbers
|
||||||
setup_pattern_binding "v" "--pattern-name version"
|
setup_pattern_binding "v" "--pattern-name version"
|
||||||
# prefix + t + d searches for any string of 4+ digits
|
# prefix + t + G searches for any string of 4+ digits
|
||||||
setup_pattern_binding "d" "--pattern-name digits"
|
setup_pattern_binding "G" "--pattern-name digits"
|
||||||
# prefix + t + m searches for hex numbers: 0xbedead
|
# prefix + t + m searches for hex numbers: 0xbedead
|
||||||
setup_pattern_binding "m" "--pattern-name mem-address"
|
setup_pattern_binding "P" "--pattern-name pointer-address"
|
||||||
# prefix + t + 4 searches for IPV4
|
# prefix + t + 4 searches for IPV4
|
||||||
setup_pattern_binding "4" "--pattern-name ipv4"
|
setup_pattern_binding "4" "--pattern-name ipv4"
|
||||||
# prefix + t + 6 searches for IPV6
|
# prefix + t + 6 searches for IPV6
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,10 @@ fn main() -> Result<(), error::ParseError> {
|
||||||
output_destination,
|
output_destination,
|
||||||
}) => {
|
}) => {
|
||||||
if uppercased {
|
if uppercased {
|
||||||
|
if active_pane.is_copy_mode {
|
||||||
|
// break out of copy mode
|
||||||
|
duct::cmd!("tmux", "copy-mode", "-t", active_pane.id.as_str(), "-q").run()?;
|
||||||
|
}
|
||||||
duct::cmd!("tmux", "send-keys", "-t", active_pane.id.as_str(), &text).run()?;
|
duct::cmd!("tmux", "send-keys", "-t", active_pane.id.as_str(), &text).run()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
||||||
|
|
@ -83,10 +83,12 @@ impl FromStr for HintStyleArg {
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, error::ParseError> {
|
fn from_str(s: &str) -> Result<Self, error::ParseError> {
|
||||||
match s {
|
match s {
|
||||||
"leading" => Ok(HintStyleArg::Underline),
|
"bold" => Ok(HintStyleArg::Bold),
|
||||||
"trailing" => Ok(HintStyleArg::Surround),
|
"italic" => Ok(HintStyleArg::Italic),
|
||||||
|
"underline" => Ok(HintStyleArg::Underline),
|
||||||
|
"surrond" => Ok(HintStyleArg::Surround),
|
||||||
_ => Err(error::ParseError::ExpectedString(String::from(
|
_ => Err(error::ParseError::ExpectedString(String::from(
|
||||||
"underline or surround",
|
"bold, italic, underline or surround",
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,7 @@ use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::basic;
|
use super::basic;
|
||||||
use crate::{
|
use crate::{error, textbuf::alphabet, tmux, ui};
|
||||||
error,
|
|
||||||
textbuf::{alphabet, regexes},
|
|
||||||
tmux, ui,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Extended configuration for handling Tmux-specific configuration (options
|
/// Extended configuration for handling Tmux-specific configuration (options
|
||||||
/// and outputs). This is only used by `tmux-copyrat` and parsed from command
|
/// and outputs). This is only used by `tmux-copyrat` and parsed from command
|
||||||
|
|
@ -61,18 +57,12 @@ impl ConfigExt {
|
||||||
|
|
||||||
for (name, value) in &tmux_options {
|
for (name, value) in &tmux_options {
|
||||||
match name.as_ref() {
|
match name.as_ref() {
|
||||||
"@copyrat-capture" => {
|
"@copyrat-capture-region" => {
|
||||||
config_ext.capture_region = CaptureRegion::from_str(&value)?
|
config_ext.capture_region = CaptureRegion::from_str(&value)?
|
||||||
}
|
}
|
||||||
"@copyrat-alphabet" => {
|
"@copyrat-alphabet" => {
|
||||||
wrapped.alphabet = alphabet::parse_alphabet(value)?;
|
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" => {
|
"@copyrat-reverse" => {
|
||||||
wrapped.reverse = value.parse::<bool>()?;
|
wrapped.reverse = value.parse::<bool>()?;
|
||||||
}
|
}
|
||||||
|
|
@ -80,12 +70,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)?
|
||||||
}
|
}
|
||||||
|
|
@ -131,8 +117,8 @@ impl FromStr for CaptureRegion {
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, error::ParseError> {
|
fn from_str(s: &str) -> Result<Self, error::ParseError> {
|
||||||
match s {
|
match s {
|
||||||
"leading" => Ok(CaptureRegion::EntireHistory),
|
"entire-history" => Ok(CaptureRegion::EntireHistory),
|
||||||
"trailing" => Ok(CaptureRegion::VisibleArea),
|
"visible-area" => Ok(CaptureRegion::VisibleArea),
|
||||||
_ => Err(error::ParseError::ExpectedString(String::from(
|
_ => Err(error::ParseError::ExpectedString(String::from(
|
||||||
"entire-history or visible-area",
|
"entire-history or visible-area",
|
||||||
))),
|
))),
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
|
|
@ -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,23 +373,20 @@ 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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn match_addresses() {
|
fn match_pointer_addresses() {
|
||||||
let buffer = "Lorem 0xfd70b5695 0x5246ddf lorem\n Lorem 0x973113tlorem";
|
let buffer = "Lorem 0xfd70b5695 0x5246ddf lorem\n Lorem 0x973113tlorem";
|
||||||
let lines = buffer.split('\n').collect::<Vec<_>>();
|
let lines = buffer.split('\n').collect::<Vec<_>>();
|
||||||
let use_all_patterns = true;
|
let use_all_patterns = true;
|
||||||
|
|
@ -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, "pointer-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, "pointer-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, "pointer-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,37 @@ 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]
|
||||||
|
fn match_datetime() {
|
||||||
|
let buffer = "12 days ago = 2021-03-04T12:23:34 text";
|
||||||
|
let lines = buffer.split('\n').collect::<Vec<_>>();
|
||||||
|
let use_all_patterns = true;
|
||||||
|
let named_pat = vec![];
|
||||||
|
let custom = vec![];
|
||||||
|
let alphabet = Alphabet("abcd".to_string());
|
||||||
|
let reverse = false;
|
||||||
|
let unique_hint = false;
|
||||||
|
let spans = Model::new(
|
||||||
|
&lines,
|
||||||
|
&alphabet,
|
||||||
|
use_all_patterns,
|
||||||
|
&named_pat,
|
||||||
|
&custom,
|
||||||
|
reverse,
|
||||||
|
unique_hint,
|
||||||
|
)
|
||||||
|
.spans;
|
||||||
|
|
||||||
|
assert_eq!(spans.len(), 1);
|
||||||
|
assert_eq!(spans.get(0).unwrap().pattern, "datetime");
|
||||||
|
assert_eq!(spans.get(0).unwrap().text, "2021-03-04T12:23:34");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -563,7 +586,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 +595,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 +628,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 +637,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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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
8
src/textbuf/raw_span.rs
Normal 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,
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ pub(super) const EXCLUDE_PATTERNS: [(&str, &str); 1] =
|
||||||
///
|
///
|
||||||
/// The email address was obtained at https://www.regular-expressions.info/email.html.
|
/// The email address was obtained at https://www.regular-expressions.info/email.html.
|
||||||
/// Others were obtained from Ferran Basora.
|
/// Others were obtained from Ferran Basora.
|
||||||
pub(super) const PATTERNS: [(&str, &str); 16] = [
|
pub(super) const PATTERNS: [(&str, &str); 17] = [
|
||||||
("markdown-url", r"\[[^]]*\]\(([^)]+)\)"),
|
("markdown-url", r"\[[^]]*\]\(([^)]+)\)"),
|
||||||
(
|
(
|
||||||
"url",
|
"url",
|
||||||
|
|
@ -31,7 +31,11 @@ pub(super) const PATTERNS: [(&str, &str); 16] = [
|
||||||
("sha", r"[0-9a-f]{7,40}"),
|
("sha", r"[0-9a-f]{7,40}"),
|
||||||
("ipv4", 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]+"),
|
("ipv6", r"[A-f0-9:]+:+[A-f0-9:]+[%\w\d]+"),
|
||||||
("mem-address", r"0x[0-9a-fA-F]+"),
|
("pointer-address", r"0x[0-9a-fA-F]+"),
|
||||||
|
(
|
||||||
|
"datetime",
|
||||||
|
r"(\d{4}-?\d{2}-?\d{2}([ T]\d{2}:\d{2}:\d{2}(\.\d{3,9})?)?)",
|
||||||
|
),
|
||||||
("digits", r"[0-9]{4,}"),
|
("digits", r"[0-9]{4,}"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
10
src/textbuf/span.rs
Normal file
10
src/textbuf/span.rs
Normal 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,
|
||||||
|
}
|
||||||
66
src/tmux.rs
66
src/tmux.rs
|
|
@ -15,16 +15,16 @@ use crate::error::ParseError;
|
||||||
pub struct Pane {
|
pub struct Pane {
|
||||||
/// Pane identifier, e.g. `%37`.
|
/// Pane identifier, e.g. `%37`.
|
||||||
pub id: PaneId,
|
pub id: PaneId,
|
||||||
/// Describes if the pane is in some mode.
|
/// Describes if the pane is in copy mode.
|
||||||
pub in_mode: bool,
|
pub is_copy_mode: bool,
|
||||||
/// Number of lines in the pane.
|
/// Number of lines in the pane.
|
||||||
pub height: u32,
|
pub height: i32,
|
||||||
/// Optional offset from the bottom if the pane is in some mode.
|
/// Optional offset from the bottom if the pane is in some mode.
|
||||||
///
|
///
|
||||||
/// When a pane is in copy mode, scrolling up changes the
|
/// When a pane is in copy mode, scrolling up changes the
|
||||||
/// `scroll_position`. If the pane is in normal mode, or unscrolled,
|
/// `scroll_position`. If the pane is in normal mode, or unscrolled,
|
||||||
/// then `0` is returned.
|
/// then `0` is returned.
|
||||||
pub scroll_position: u32,
|
pub scroll_position: i32,
|
||||||
/// Describes if the pane is currently active (focused).
|
/// Describes if the pane is currently active (focused).
|
||||||
pub is_active: bool,
|
pub is_active: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -59,9 +59,9 @@ impl FromStr for Pane {
|
||||||
// let id = id_str[1..].parse::<u32>()?;
|
// let id = id_str[1..].parse::<u32>()?;
|
||||||
// let id = format!("%{}", id);
|
// let id = format!("%{}", id);
|
||||||
|
|
||||||
let in_mode = iter.next().unwrap().parse::<bool>()?;
|
let is_copy_mode = iter.next().unwrap().parse::<bool>()?;
|
||||||
|
|
||||||
let height = iter.next().unwrap().parse::<u32>()?;
|
let height = iter.next().unwrap().parse::<i32>()?;
|
||||||
|
|
||||||
let scroll_position = iter.next().unwrap();
|
let scroll_position = iter.next().unwrap();
|
||||||
let scroll_position = if scroll_position.is_empty() {
|
let scroll_position = if scroll_position.is_empty() {
|
||||||
|
|
@ -69,13 +69,13 @@ impl FromStr for Pane {
|
||||||
} else {
|
} else {
|
||||||
scroll_position
|
scroll_position
|
||||||
};
|
};
|
||||||
let scroll_position = scroll_position.parse::<u32>()?;
|
let scroll_position = scroll_position.parse::<i32>()?;
|
||||||
|
|
||||||
let is_active = iter.next().unwrap().parse::<bool>()?;
|
let is_active = iter.next().unwrap().parse::<bool>()?;
|
||||||
|
|
||||||
Ok(Pane {
|
Ok(Pane {
|
||||||
id,
|
id,
|
||||||
in_mode,
|
is_copy_mode,
|
||||||
height,
|
height,
|
||||||
scroll_position,
|
scroll_position,
|
||||||
is_active,
|
is_active,
|
||||||
|
|
@ -141,10 +141,10 @@ pub fn list_panes() -> Result<Vec<Pane>, ParseError> {
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```get_options("@copyrat-")```
|
/// ```get_options("@copyrat-")```
|
||||||
pub fn get_options(prefix: &str) -> Result<HashMap<String, String>, ParseError> {
|
pub fn get_options(prefix: &str) -> Result<HashMap<String, String>, ParseError> {
|
||||||
let output = duct::cmd!("tmux", "show", "-g").read()?;
|
let output = duct::cmd!("tmux", "show-options", "-g").read()?;
|
||||||
let lines: Vec<&str> = output.split('\n').collect();
|
let lines: Vec<&str> = output.split('\n').collect();
|
||||||
|
|
||||||
let pattern = format!(r#"{prefix}([\w\-0-9]+) "?(\w+)"?"#, prefix = prefix);
|
let pattern = format!(r#"({prefix}[\w\-0-9]+) "?(\w+)"?"#, prefix = prefix);
|
||||||
let re = Regex::new(&pattern).unwrap();
|
let re = Regex::new(&pattern).unwrap();
|
||||||
|
|
||||||
let args: HashMap<String, String> = lines
|
let args: HashMap<String, String> = lines
|
||||||
|
|
@ -167,35 +167,41 @@ pub fn get_options(prefix: &str) -> Result<HashMap<String, String>, ParseError>
|
||||||
/// The provided `region` specifies if the visible area is captured, or the
|
/// The provided `region` specifies if the visible area is captured, or the
|
||||||
/// entire history.
|
/// entire history.
|
||||||
///
|
///
|
||||||
/// # TODO
|
|
||||||
///
|
|
||||||
/// Capture with `capture-pane -J` joins wrapped lines.
|
|
||||||
///
|
|
||||||
/// # Note
|
/// # Note
|
||||||
///
|
///
|
||||||
/// If the pane is in normal mode, capturing the visible area can be done
|
/// In Tmux, the start line is the line at the top of the pane. The end line
|
||||||
/// without extra arguments (default behavior of `capture-pane`), but if the
|
/// is the last line at the bottom of the pane.
|
||||||
/// pane is in copy mode, we need to take into account the current scroll
|
///
|
||||||
/// position. To support both cases, the implementation always provides those
|
/// - In normal mode, the index of the start line is always 0. The index of
|
||||||
/// parameters to tmux.
|
/// the end line is always the pane's height minus one. These do not need to
|
||||||
|
/// be specified when capturing the pane's content.
|
||||||
|
///
|
||||||
|
/// - If navigating history in copy mode, the index of the start line is the
|
||||||
|
/// opposite of the pane's scroll position. For instance a pane of 40 lines,
|
||||||
|
/// scrolled up by 3 lines. It is necessarily in copy mode. Its start line
|
||||||
|
/// index is `-3`. The index of the last line is `(40-1) - 3 = 36`.
|
||||||
|
///
|
||||||
pub fn capture_pane(pane: &Pane, region: &CaptureRegion) -> Result<String, ParseError> {
|
pub fn capture_pane(pane: &Pane, region: &CaptureRegion) -> Result<String, ParseError> {
|
||||||
let mut args = format!("capture-pane -t {pane_id} -J -p", pane_id = pane.id);
|
let mut args_str = format!("capture-pane -t {pane_id} -J -p", pane_id = pane.id);
|
||||||
|
|
||||||
let region_str = match region {
|
let region_str = match region {
|
||||||
CaptureRegion::VisibleArea => {
|
CaptureRegion::VisibleArea => {
|
||||||
// Providing start/end helps support both copy and normal modes.
|
if pane.is_copy_mode && pane.scroll_position > 0 {
|
||||||
format!(
|
format!(
|
||||||
" -S {start} -E {end}",
|
" -S {start} -E {end}",
|
||||||
start = pane.scroll_position,
|
start = -pane.scroll_position,
|
||||||
end = pane.height - pane.scroll_position - 1
|
end = pane.height - pane.scroll_position - 1
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CaptureRegion::EntireHistory => String::from(" -S - -E -"),
|
CaptureRegion::EntireHistory => String::from(" -S - -E -"),
|
||||||
};
|
};
|
||||||
|
|
||||||
args.push_str(®ion_str);
|
args_str.push_str(®ion_str);
|
||||||
|
|
||||||
let args: Vec<&str> = args.split(' ').collect();
|
let args: Vec<&str> = args_str.split(' ').collect();
|
||||||
|
|
||||||
let output = duct::cmd("tmux", &args).read()?;
|
let output = duct::cmd("tmux", &args).read()?;
|
||||||
Ok(output)
|
Ok(output)
|
||||||
|
|
@ -226,7 +232,7 @@ mod tests {
|
||||||
let expected = vec![
|
let expected = vec![
|
||||||
Pane {
|
Pane {
|
||||||
id: PaneId::from_str("%52").unwrap(),
|
id: PaneId::from_str("%52").unwrap(),
|
||||||
in_mode: false,
|
is_copy_mode: false,
|
||||||
height: 62,
|
height: 62,
|
||||||
scroll_position: 3,
|
scroll_position: 3,
|
||||||
is_active: false,
|
is_active: false,
|
||||||
|
|
@ -234,7 +240,7 @@ mod tests {
|
||||||
Pane {
|
Pane {
|
||||||
// id: PaneId::from_str("%53").unwrap(),
|
// id: PaneId::from_str("%53").unwrap(),
|
||||||
id: PaneId(String::from("%53")),
|
id: PaneId(String::from("%53")),
|
||||||
in_mode: false,
|
is_copy_mode: false,
|
||||||
height: 23,
|
height: 23,
|
||||||
scroll_position: 0,
|
scroll_position: 0,
|
||||||
is_active: true,
|
is_active: true,
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,15 @@ pub fn parse_color(src: &str) -> Result<Box<dyn color::Color>, error::ParseError
|
||||||
"magenta" => Ok(Box::new(color::Magenta)),
|
"magenta" => Ok(Box::new(color::Magenta)),
|
||||||
"cyan" => Ok(Box::new(color::Cyan)),
|
"cyan" => Ok(Box::new(color::Cyan)),
|
||||||
"white" => Ok(Box::new(color::White)),
|
"white" => Ok(Box::new(color::White)),
|
||||||
"bright-black" => Ok(Box::new(color::LightBlack)),
|
"bright-black" | "brightblack" => Ok(Box::new(color::LightBlack)),
|
||||||
"bright-red" => Ok(Box::new(color::LightRed)),
|
"bright-red" | "brightred" => Ok(Box::new(color::LightRed)),
|
||||||
"bright-green" => Ok(Box::new(color::LightGreen)),
|
"bright-green" | "brightgreen" => Ok(Box::new(color::LightGreen)),
|
||||||
"bright-yellow" => Ok(Box::new(color::LightYellow)),
|
"bright-yellow" | "brightyellow" => Ok(Box::new(color::LightYellow)),
|
||||||
"bright-blue" => Ok(Box::new(color::LightBlue)),
|
"bright-blue" | "brightblue" => Ok(Box::new(color::LightBlue)),
|
||||||
"bright-magenta" => Ok(Box::new(color::LightMagenta)),
|
"bright-magenta" | "brightmagenta" => Ok(Box::new(color::LightMagenta)),
|
||||||
"bright-cyan" => Ok(Box::new(color::LightCyan)),
|
"bright-cyan" | "brightcyan" => Ok(Box::new(color::LightCyan)),
|
||||||
"bright-white" => Ok(Box::new(color::LightWhite)),
|
"bright-white" | "brightwhite" => Ok(Box::new(color::LightWhite)),
|
||||||
// "default" => Ok(Box::new(color::Reset)),
|
"none" => Ok(Box::new(color::Reset)),
|
||||||
_ => Err(error::ParseError::UnknownColor),
|
_ => Err(error::ParseError::UnknownColor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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`.
|
||||||
|
|
@ -60,36 +60,36 @@ pub struct UiColors {
|
||||||
pub text_fg: Box<dyn color::Color>,
|
pub text_fg: Box<dyn color::Color>,
|
||||||
|
|
||||||
/// Background color for base text.
|
/// Background color for base text.
|
||||||
#[clap(long, default_value = "bright-white", parse(try_from_str = parse_color))]
|
#[clap(long, default_value = "none", 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 = "blue",
|
||||||
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 = "none",
|
||||||
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 = "none",
|
||||||
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>,
|
||||||
|
|
||||||
/// Foreground color for hints.
|
/// Foreground color for hints.
|
||||||
#[clap(long, default_value = "white",
|
#[clap(long, default_value = "yellow",
|
||||||
parse(try_from_str = parse_color))]
|
parse(try_from_str = parse_color))]
|
||||||
pub hint_fg: Box<dyn color::Color>,
|
pub hint_fg: Box<dyn color::Color>,
|
||||||
|
|
||||||
/// Background color for hints.
|
/// Background color for hints.
|
||||||
#[clap(long, default_value = "magenta",
|
#[clap(long, default_value = "none",
|
||||||
parse(try_from_str = parse_color))]
|
parse(try_from_str = parse_color))]
|
||||||
pub hint_bg: Box<dyn color::Color>,
|
pub hint_bg: Box<dyn color::Color>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
|
|
||||||
381
src/ui/vc.rs
381
src/ui/vc.rs
|
|
@ -9,10 +9,34 @@ use super::Selection;
|
||||||
use super::{HintAlignment, HintStyle};
|
use super::{HintAlignment, HintStyle};
|
||||||
use crate::{config::extended::OutputDestination, textbuf};
|
use crate::{config::extended::OutputDestination, textbuf};
|
||||||
|
|
||||||
|
/// Describes where a line from the buffer is displayed on the screen and how
|
||||||
|
/// much vertical lines it takes.
|
||||||
|
///
|
||||||
|
/// The `pos_y` field is the actual vertical position due to wrapped lines
|
||||||
|
/// before this line. The `size` field is the number of screen lines occupied
|
||||||
|
/// by this line.
|
||||||
|
///
|
||||||
|
/// For example, given a buffer in which
|
||||||
|
///
|
||||||
|
/// - the first line is smaller than the screen width,
|
||||||
|
/// - the second line is slightly larger,
|
||||||
|
/// - and the third line is smaller than the screen width,
|
||||||
|
///
|
||||||
|
/// The corresponding `WrappedLine`s are
|
||||||
|
///
|
||||||
|
/// - the first `WrappedLine` has `pos_y: 0` and `size: 1`
|
||||||
|
/// - the second `WrappedLine` has `pos_y: 1` and `size: 2` (larger than screen
|
||||||
|
/// width)
|
||||||
|
/// - the third `WrappedLine` has `pos_y: 3` and `size: 1`
|
||||||
|
///
|
||||||
|
struct WrappedLine {
|
||||||
|
pos_y: usize,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ViewController<'a> {
|
pub struct ViewController<'a> {
|
||||||
model: &'a textbuf::Model<'a>,
|
model: &'a textbuf::Model<'a>,
|
||||||
term_width: u16,
|
term_width: u16,
|
||||||
line_offsets: Vec<usize>,
|
wrapped_lines: Vec<WrappedLine>,
|
||||||
focus_index: usize,
|
focus_index: usize,
|
||||||
focus_wrap_around: bool,
|
focus_wrap_around: bool,
|
||||||
default_output_destination: OutputDestination,
|
default_output_destination: OutputDestination,
|
||||||
|
|
@ -33,18 +57,18 @@ 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
|
||||||
};
|
};
|
||||||
|
|
||||||
let (term_width, _) = termion::terminal_size().unwrap_or((80u16, 30u16)); // .expect("Cannot read the terminal size.");
|
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);
|
let wrapped_lines = compute_wrapped_lines(&model.lines, term_width);
|
||||||
|
|
||||||
ViewController {
|
ViewController {
|
||||||
model,
|
model,
|
||||||
term_width,
|
term_width,
|
||||||
line_offsets,
|
wrapped_lines,
|
||||||
focus_index,
|
focus_index,
|
||||||
focus_wrap_around,
|
focus_wrap_around,
|
||||||
default_output_destination,
|
default_output_destination,
|
||||||
|
|
@ -57,53 +81,56 @@ impl<'a> ViewController<'a> {
|
||||||
// }}}
|
// }}}
|
||||||
// Coordinates {{{1
|
// Coordinates {{{1
|
||||||
|
|
||||||
/// Convert the `Match` text into the coordinates of the wrapped lines.
|
/// Returns the adjusted position of a given `Span` within the buffer
|
||||||
|
/// line.
|
||||||
///
|
///
|
||||||
/// Compute the new x offset of the text as the remainder of the line width
|
/// This adjustment is necessary if multibyte characters occur before the
|
||||||
/// (e.g. the match could start at offset 120 in a 80-width terminal, the new
|
/// span (in the "prefix"). If this is the case then their compouding
|
||||||
/// offset being 40).
|
/// takes less space on screen when printed: for instance ´ + e = é.
|
||||||
|
/// Consequently the span position has to be adjusted to the left.
|
||||||
///
|
///
|
||||||
/// Compute the new y offset of the text as the initial y offset plus any
|
/// This computation must happen before mapping the span position to the
|
||||||
/// additional offset due to previous split lines. This is obtained thanks to
|
/// wrapped screen space.
|
||||||
/// the `offset_per_line` member.
|
fn adjusted_span_position(&self, span: &textbuf::Span<'a>) -> (usize, usize) {
|
||||||
fn map_coords_to_wrapped_space(&self, offset_x: usize, offset_y: usize) -> (usize, usize) {
|
let pos_x = {
|
||||||
let line_width = self.term_width as usize;
|
let line = &self.model.lines[span.y as usize];
|
||||||
|
let prefix = &line[0..span.x as usize];
|
||||||
|
let adjust = prefix.len() - prefix.chars().count();
|
||||||
|
(span.x as usize) - adjust
|
||||||
|
};
|
||||||
|
let pos_y = span.y as usize;
|
||||||
|
|
||||||
let new_offset_x = offset_x % line_width;
|
(pos_x, pos_y)
|
||||||
let new_offset_y =
|
|
||||||
self.line_offsets.get(offset_y as usize).unwrap() + offset_x / line_width;
|
|
||||||
|
|
||||||
(new_offset_x, new_offset_y)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns screen offset of a given `Match`.
|
/// Convert the `Span` text into the coordinates of the wrapped lines.
|
||||||
///
|
///
|
||||||
/// If multibyte characters occur before the hint (in the "prefix"), then
|
/// Compute the new x position of the text as the remainder of the line width
|
||||||
/// their compouding takes less space on screen when printed: for
|
/// (e.g. the Span could start at position 120 in a 80-width terminal, the new
|
||||||
/// instance ´ + e = é. Consequently the hint offset has to be adjusted
|
/// position being 40).
|
||||||
/// to the left.
|
///
|
||||||
fn match_offsets(&self, mat: &textbuf::Match<'a>) -> (usize, usize) {
|
/// Compute the new y position of the text as the initial y position plus any
|
||||||
let offset_x = {
|
/// additional offset due to previous split lines. This is obtained thanks to
|
||||||
let line = &self.model.lines[mat.y as usize];
|
/// the `wrapped_lines` field.
|
||||||
let prefix = &line[0..mat.x as usize];
|
fn map_coords_to_wrapped_space(&self, pos_x: usize, pos_y: usize) -> (usize, usize) {
|
||||||
let adjust = prefix.len() - prefix.chars().count();
|
let line_width = self.term_width as usize;
|
||||||
(mat.x as usize) - (adjust)
|
|
||||||
};
|
|
||||||
let offset_y = mat.y as usize;
|
|
||||||
|
|
||||||
(offset_x, offset_y)
|
let new_pos_x = pos_x % line_width;
|
||||||
|
let new_pos_y = self.wrapped_lines[pos_y as usize].pos_y + pos_x / line_width;
|
||||||
|
|
||||||
|
(new_pos_x, new_pos_y)
|
||||||
}
|
}
|
||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
// 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 +142,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 +163,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.
|
||||||
|
|
@ -144,7 +171,7 @@ impl<'a> ViewController<'a> {
|
||||||
fn render_base_text(
|
fn render_base_text(
|
||||||
stdout: &mut dyn io::Write,
|
stdout: &mut dyn io::Write,
|
||||||
lines: &[&str],
|
lines: &[&str],
|
||||||
line_offsets: &[usize],
|
wrapped_lines: &[WrappedLine],
|
||||||
colors: &UiColors,
|
colors: &UiColors,
|
||||||
) {
|
) {
|
||||||
write!(
|
write!(
|
||||||
|
|
@ -159,13 +186,12 @@ impl<'a> ViewController<'a> {
|
||||||
let trimmed_line = line.trim_end();
|
let trimmed_line = line.trim_end();
|
||||||
|
|
||||||
if !trimmed_line.is_empty() {
|
if !trimmed_line.is_empty() {
|
||||||
let offset_y: usize =
|
let pos_y: usize = wrapped_lines[line_index].pos_y;
|
||||||
*(line_offsets.get(line_index)).expect("Cannot get offset_per_line.");
|
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
stdout,
|
stdout,
|
||||||
"{goto}{text}",
|
"{goto}{text}",
|
||||||
goto = cursor::Goto(1, offset_y as u16 + 1),
|
goto = cursor::Goto(1, pos_y as u16 + 1),
|
||||||
text = &trimmed_line,
|
text = &trimmed_line,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -181,32 +207,32 @@ 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.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
///
|
///
|
||||||
/// This writes directly on the writer, avoiding extra allocation.
|
/// This writes directly on the writer, avoiding extra allocation.
|
||||||
fn render_matched_text(
|
fn render_span_text(
|
||||||
stdout: &mut dyn io::Write,
|
stdout: &mut dyn io::Write,
|
||||||
text: &str,
|
text: &str,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
offset: (usize, usize),
|
pos: (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}",
|
||||||
goto = cursor::Goto(offset.0 as u16 + 1, offset.1 as u16 + 1),
|
goto = cursor::Goto(pos.0 as u16 + 1, pos.1 as u16 + 1),
|
||||||
fg_color = color::Fg(fg_color.as_ref()),
|
fg_color = color::Fg(fg_color.as_ref()),
|
||||||
bg_color = color::Bg(bg_color.as_ref()),
|
bg_color = color::Bg(bg_color.as_ref()),
|
||||||
fg_reset = color::Fg(color::Reset),
|
fg_reset = color::Fg(color::Reset),
|
||||||
|
|
@ -216,20 +242,21 @@ 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
|
||||||
/// - underlined with colors
|
/// - styled (bold, italic, underlined) with colors
|
||||||
/// - surrounding the hint's text with some delimiters, see
|
/// - surrounding the hint's text with some delimiters, see
|
||||||
/// `HintStyle::Delimited`.
|
/// `HintStyle::Delimited`.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
|
///
|
||||||
/// This writes directly on the writer, avoiding extra allocation.
|
/// This writes directly on the writer, avoiding extra allocation.
|
||||||
fn render_matched_hint(
|
fn render_span_hint(
|
||||||
stdout: &mut dyn io::Write,
|
stdout: &mut dyn io::Write,
|
||||||
hint_text: &str,
|
hint_text: &str,
|
||||||
offset: (usize, usize),
|
pos: (usize, usize),
|
||||||
colors: &UiColors,
|
colors: &UiColors,
|
||||||
hint_style: &Option<HintStyle>,
|
hint_style: &Option<HintStyle>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -237,7 +264,7 @@ impl<'a> ViewController<'a> {
|
||||||
let bg_color = color::Bg(colors.hint_bg.as_ref());
|
let bg_color = color::Bg(colors.hint_bg.as_ref());
|
||||||
let fg_reset = color::Fg(color::Reset);
|
let fg_reset = color::Fg(color::Reset);
|
||||||
let bg_reset = color::Bg(color::Reset);
|
let bg_reset = color::Bg(color::Reset);
|
||||||
let goto = cursor::Goto(offset.0 as u16 + 1, offset.1 as u16 + 1);
|
let goto = cursor::Goto(pos.0 as u16 + 1, pos.1 as u16 + 1);
|
||||||
|
|
||||||
match hint_style {
|
match hint_style {
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -264,7 +291,7 @@ impl<'a> ViewController<'a> {
|
||||||
fg_reset = fg_reset,
|
fg_reset = fg_reset,
|
||||||
bg_reset = bg_reset,
|
bg_reset = bg_reset,
|
||||||
sty = style::Bold,
|
sty = style::Bold,
|
||||||
sty_reset = style::NoBold,
|
sty_reset = style::Reset, // NoBold is not sufficient
|
||||||
hint = hint_text,
|
hint = hint_text,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -318,35 +345,35 @@ 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 (pos_x, pos_y) = self.adjusted_span_position(span);
|
||||||
let (offset_x, offset_y) = self.map_coords_to_wrapped_space(offset_x, offset_y);
|
let (pos_x, pos_y) = self.map_coords_to_wrapped_space(pos_x, pos_y);
|
||||||
|
|
||||||
ViewController::render_matched_text(
|
ViewController::render_span_text(
|
||||||
stdout,
|
stdout,
|
||||||
text,
|
text,
|
||||||
focused,
|
focused,
|
||||||
(offset_x, offset_y),
|
(pos_x, pos_y),
|
||||||
&self.rendering_colors,
|
&self.rendering_colors,
|
||||||
);
|
);
|
||||||
|
|
||||||
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 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_span_hint(
|
||||||
stdout,
|
stdout,
|
||||||
&mat.hint,
|
&span.hint,
|
||||||
(offset_x + extra_offset, offset_y),
|
(pos_x + offset, pos_y),
|
||||||
&self.rendering_colors,
|
&self.rendering_colors,
|
||||||
&self.hint_style,
|
&self.hint_style,
|
||||||
);
|
);
|
||||||
|
|
@ -357,50 +384,51 @@ 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
|
||||||
/// on the leading edge of the underlying Match's `text`,
|
/// rendered on the leading edge of the underlying Span's `text`, or on
|
||||||
/// or on the trailing edge.
|
/// 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.
|
||||||
ViewController::render_base_text(
|
ViewController::render_base_text(
|
||||||
stdout,
|
stdout,
|
||||||
&self.model.lines,
|
&self.model.lines,
|
||||||
&self.line_offsets,
|
&self.wrapped_lines,
|
||||||
&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 +437,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 +445,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 +476,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 +512,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 +539,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 +563,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 +613,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();
|
||||||
|
|
@ -596,37 +624,40 @@ impl<'a> ViewController<'a> {
|
||||||
// }}}
|
// }}}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute each line's actual y offset if displayed in a terminal of width
|
/// Compute each line's actual y position and size if displayed in a terminal of width
|
||||||
/// `term_width`.
|
/// `term_width`.
|
||||||
fn get_line_offsets(lines: &[&str], term_width: u16) -> Vec<usize> {
|
fn compute_wrapped_lines(lines: &[&str], term_width: u16) -> Vec<WrappedLine> {
|
||||||
lines
|
lines
|
||||||
.iter()
|
.iter()
|
||||||
.scan(0, |offset, &line| {
|
.scan(0, |position, &line| {
|
||||||
// Save the value to return (yield is in unstable).
|
// Save the value to return (yield is in unstable).
|
||||||
let value = *offset;
|
let value = *position;
|
||||||
|
|
||||||
let line_width = line.trim_end().chars().count() as isize;
|
let line_width = line.trim_end().chars().count() as isize;
|
||||||
|
|
||||||
// Amount of extra y space taken by this line.
|
// Amount of extra y space taken by this line.
|
||||||
// If the line has n chars, on a term of width n, this does not
|
// 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.
|
// 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.
|
// In case the width is 0, we need to first clamp line_width - 1.
|
||||||
let extra = cmp::max(0, line_width - 1) as usize / term_width as usize;
|
let extra = cmp::max(0, line_width - 1) as usize / term_width as usize;
|
||||||
|
|
||||||
// Update the offset of the next line.
|
// Update the position of the next line.
|
||||||
*offset = *offset + 1 + extra;
|
*position += 1 + extra;
|
||||||
|
|
||||||
Some(value)
|
Some(WrappedLine {
|
||||||
|
pos_y: value,
|
||||||
|
// size: 1 + extra,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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)]
|
||||||
|
|
@ -643,21 +674,28 @@ path: /usr/local/bin/git
|
||||||
|
|
||||||
path: /usr/local/bin/cargo";
|
path: /usr/local/bin/cargo";
|
||||||
let lines: Vec<&str> = content.split('\n').collect();
|
let lines: Vec<&str> = content.split('\n').collect();
|
||||||
let line_offsets: Vec<usize> = (0..lines.len()).collect();
|
let wrapped_lines: Vec<WrappedLine> = vec![
|
||||||
|
WrappedLine { pos_y: 0 },
|
||||||
|
WrappedLine { pos_y: 1 },
|
||||||
|
WrappedLine { pos_y: 2 },
|
||||||
|
WrappedLine { pos_y: 3 },
|
||||||
|
WrappedLine { pos_y: 4 },
|
||||||
|
WrappedLine { pos_y: 5 },
|
||||||
|
];
|
||||||
|
|
||||||
let colors = UiColors {
|
let colors = UiColors {
|
||||||
text_fg: Box::new(color::Black),
|
text_fg: Box::new(color::Black),
|
||||||
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 mut writer = vec![];
|
let mut writer = vec![];
|
||||||
ViewController::render_base_text(&mut writer, &lines, &line_offsets, &colors);
|
ViewController::render_base_text(&mut writer, &lines, &wrapped_lines, &colors);
|
||||||
|
|
||||||
let goto1 = cursor::Goto(1, 1);
|
let goto1 = cursor::Goto(1, 1);
|
||||||
let goto2 = cursor::Goto(1, 2);
|
let goto2 = cursor::Goto(1, 2);
|
||||||
|
|
@ -678,23 +716,23 @@ path: /usr/local/bin/cargo";
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_focused_matched_text() {
|
fn test_render_focused_span_text() {
|
||||||
let mut writer = vec![];
|
let mut writer = vec![];
|
||||||
let text = "https://en.wikipedia.org/wiki/Barcelona";
|
let text = "https://en.wikipedia.org/wiki/Barcelona";
|
||||||
let focused = true;
|
let focused = true;
|
||||||
let offset: (usize, usize) = (3, 1);
|
let position: (usize, usize) = (3, 1);
|
||||||
let colors = UiColors {
|
let colors = UiColors {
|
||||||
text_fg: Box::new(color::Black),
|
text_fg: Box::new(color::Black),
|
||||||
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),
|
||||||
};
|
};
|
||||||
|
|
||||||
ViewController::render_matched_text(&mut writer, text, focused, offset, &colors);
|
ViewController::render_span_text(&mut writer, text, focused, position, &colors);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
writer,
|
writer,
|
||||||
|
|
@ -712,31 +750,31 @@ path: /usr/local/bin/cargo";
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_matched_text() {
|
fn test_render_span_text() {
|
||||||
let mut writer = vec![];
|
let mut writer = vec![];
|
||||||
let text = "https://en.wikipedia.org/wiki/Barcelona";
|
let text = "https://en.wikipedia.org/wiki/Barcelona";
|
||||||
let focused = false;
|
let focused = false;
|
||||||
let offset: (usize, usize) = (3, 1);
|
let position: (usize, usize) = (3, 1);
|
||||||
let colors = UiColors {
|
let colors = UiColors {
|
||||||
text_fg: Box::new(color::Black),
|
text_fg: Box::new(color::Black),
|
||||||
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),
|
||||||
};
|
};
|
||||||
|
|
||||||
ViewController::render_matched_text(&mut writer, text, focused, offset, &colors);
|
ViewController::render_span_text(&mut writer, text, focused, position, &colors);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
writer,
|
writer,
|
||||||
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,
|
||||||
|
|
@ -746,28 +784,28 @@ path: /usr/local/bin/cargo";
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_unstyled_matched_hint() {
|
fn test_render_unstyled_span_hint() {
|
||||||
let mut writer = vec![];
|
let mut writer = vec![];
|
||||||
let hint_text = "eo";
|
let hint_text = "eo";
|
||||||
let offset: (usize, usize) = (3, 1);
|
let position: (usize, usize) = (3, 1);
|
||||||
let colors = UiColors {
|
let colors = UiColors {
|
||||||
text_fg: Box::new(color::Black),
|
text_fg: Box::new(color::Black),
|
||||||
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 extra_offset = 0;
|
let offset = 0;
|
||||||
let hint_style = None;
|
let hint_style = None;
|
||||||
|
|
||||||
ViewController::render_matched_hint(
|
ViewController::render_span_hint(
|
||||||
&mut writer,
|
&mut writer,
|
||||||
hint_text,
|
hint_text,
|
||||||
(offset.0 + extra_offset, offset.1),
|
(position.0 + offset, position.1),
|
||||||
&colors,
|
&colors,
|
||||||
&hint_style,
|
&hint_style,
|
||||||
);
|
);
|
||||||
|
|
@ -788,28 +826,28 @@ path: /usr/local/bin/cargo";
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_underlined_matched_hint() {
|
fn test_render_underlined_span_hint() {
|
||||||
let mut writer = vec![];
|
let mut writer = vec![];
|
||||||
let hint_text = "eo";
|
let hint_text = "eo";
|
||||||
let offset: (usize, usize) = (3, 1);
|
let position: (usize, usize) = (3, 1);
|
||||||
let colors = UiColors {
|
let colors = UiColors {
|
||||||
text_fg: Box::new(color::Black),
|
text_fg: Box::new(color::Black),
|
||||||
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 extra_offset = 0;
|
let offset = 0;
|
||||||
let hint_style = Some(HintStyle::Underline);
|
let hint_style = Some(HintStyle::Underline);
|
||||||
|
|
||||||
ViewController::render_matched_hint(
|
ViewController::render_span_hint(
|
||||||
&mut writer,
|
&mut writer,
|
||||||
hint_text,
|
hint_text,
|
||||||
(offset.0 + extra_offset, offset.1),
|
(position.0 + offset, position.1),
|
||||||
&colors,
|
&colors,
|
||||||
&hint_style,
|
&hint_style,
|
||||||
);
|
);
|
||||||
|
|
@ -832,28 +870,28 @@ path: /usr/local/bin/cargo";
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_render_bracketed_matched_hint() {
|
fn test_render_bracketed_span_hint() {
|
||||||
let mut writer = vec![];
|
let mut writer = vec![];
|
||||||
let hint_text = "eo";
|
let hint_text = "eo";
|
||||||
let offset: (usize, usize) = (3, 1);
|
let position: (usize, usize) = (3, 1);
|
||||||
let colors = UiColors {
|
let colors = UiColors {
|
||||||
text_fg: Box::new(color::Black),
|
text_fg: Box::new(color::Black),
|
||||||
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 extra_offset = 0;
|
let offset = 0;
|
||||||
let hint_style = Some(HintStyle::Surround('{', '}'));
|
let hint_style = Some(HintStyle::Surround('{', '}'));
|
||||||
|
|
||||||
ViewController::render_matched_hint(
|
ViewController::render_span_hint(
|
||||||
&mut writer,
|
&mut writer,
|
||||||
hint_text,
|
hint_text,
|
||||||
(offset.0 + extra_offset, offset.1),
|
(position.0 + offset, position.1),
|
||||||
&colors,
|
&colors,
|
||||||
&hint_style,
|
&hint_style,
|
||||||
);
|
);
|
||||||
|
|
@ -876,8 +914,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 - ";
|
||||||
|
|
@ -899,24 +937,24 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
|
||||||
unique_hint,
|
unique_hint,
|
||||||
);
|
);
|
||||||
let term_width: u16 = 80;
|
let term_width: u16 = 80;
|
||||||
let line_offsets = get_line_offsets(&model.lines, term_width);
|
let wrapped_lines = compute_wrapped_lines(&model.lines, term_width);
|
||||||
let rendering_colors = UiColors {
|
let rendering_colors = UiColors {
|
||||||
text_fg: Box::new(color::Black),
|
text_fg: Box::new(color::Black),
|
||||||
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,
|
||||||
line_offsets,
|
wrapped_lines,
|
||||||
focus_index: 0,
|
focus_index: 0,
|
||||||
focus_wrap_around: false,
|
focus_wrap_around: false,
|
||||||
default_output_destination: OutputDestination::Tmux,
|
default_output_destination: OutputDestination::Tmux,
|
||||||
|
|
@ -945,15 +983,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 +1017,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),
|
||||||
};
|
};
|
||||||
|
|
@ -1018,19 +1053,19 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_match1_text = {
|
let expected_span1_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)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_match1_hint = {
|
let expected_span1_hint = {
|
||||||
let goto7_1 = cursor::Goto(7, 1);
|
let goto7_1 = cursor::Goto(7, 1);
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
|
|
@ -1043,7 +1078,7 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_match2_text = {
|
let expected_span2_text = {
|
||||||
let goto11_3 = cursor::Goto(11, 3);
|
let goto11_3 = cursor::Goto(11, 3);
|
||||||
format!(
|
format!(
|
||||||
"{goto11_3}{focus_bg}{focus_fg}https://en.wikipedia.org/wiki/Barcelona{fg_reset}{bg_reset}",
|
"{goto11_3}{focus_bg}{focus_fg}https://en.wikipedia.org/wiki/Barcelona{fg_reset}{bg_reset}",
|
||||||
|
|
@ -1055,10 +1090,10 @@ 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_span2_hint = {
|
||||||
// let goto11_3 = cursor::Goto(11, 3);
|
// let goto11_3 = cursor::Goto(11, 3);
|
||||||
|
|
||||||
// format!(
|
// format!(
|
||||||
|
|
@ -1073,10 +1108,10 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
|
||||||
|
|
||||||
let expected = [
|
let expected = [
|
||||||
expected_content,
|
expected_content,
|
||||||
expected_match1_text,
|
expected_span1_text,
|
||||||
expected_match1_hint,
|
expected_span1_hint,
|
||||||
expected_match2_text,
|
expected_span2_text,
|
||||||
// expected_match2_hint,
|
// expected_span2_hint,
|
||||||
]
|
]
|
||||||
.concat();
|
.concat();
|
||||||
|
|
||||||
|
|
@ -1090,7 +1125,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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue