Skip to content

Commit

Permalink
Merge pull request #6 from jnises/agepenalty
Browse files Browse the repository at this point in the history
Penalize older matches when highlighting
  • Loading branch information
jnises authored Nov 25, 2023
2 parents 2ea8f9b + 7abfcc2 commit a112b86
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 61 deletions.
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "colight"
version = "0.2.0"
version = "0.3.0"
edition = "2021"

[dependencies]
Expand Down
79 changes: 26 additions & 53 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,39 +20,49 @@ struct Args {
/// How long back in history to look for matches
#[arg(long, default_value_t = 1024)]
window_size: usize,

/// How much to penalize age of matches, higher values will make older matches less likely to be highlighted
#[arg(long, default_value_t = 0.01)]
age_penalty: f32,
}

fn main() -> anyhow::Result<()> {
let args = Args::parse();
assert!(args.window_size > 0);
assert!(args.age_penalty >= 0.0);
let stdin = std::io::stdin();
let si = stdin.lock();
let stdout = StandardStream::stdout(ColorChoice::Auto);
let so = stdout.lock();
print_comp(si, so, args.window_size)?;
print_comp(si, so, args)?;
Ok(())
}

fn print_comp<I, O>(si: I, so: O, window_size: usize) -> anyhow::Result<()>
fn print_comp<I, O>(si: I, so: O, args: Args) -> anyhow::Result<()>
where
O: WriteColor,
I: Read,
{
let Args {
window_size,
age_penalty,
} = args;
// refcell so we can reset on scope exit
let soc = RefCell::new(so);
defer! {
soc.borrow_mut().reset().unwrap();
}
let pr = |buffered: VecDeque<u8>| -> anyhow::Result<()> {
let pr = |buffered: VecDeque<u8>, age: Option<usize>| -> anyhow::Result<()> {
if !buffered.is_empty() {
let compression = 1f32 / buffered.len() as f32;
let score = 1f32 / (buffered.len() as f32 + age.unwrap_or(0) as f32 * age_penalty);
let (a, b) = buffered.as_slices();
for line in a
.split_inclusive(|&c| c == b'\n')
.chain(b.split_inclusive(|&c| c == b'\n'))
{
// set the color at the start of each line as some terminals seem to reset
soc.borrow_mut()
.set_color(ColorSpec::new().set_fg(Some(color_map(compression))))?;
.set_color(ColorSpec::new().set_fg(Some(color_map(score))))?;
soc.borrow_mut().write_all(line)?;
}
}
Expand All @@ -63,16 +73,17 @@ where
loop {
let mut byte_buf = [0; 1];
if color_stripper.read_exact(&mut byte_buf).is_err() {
pr(searcher.flush())?;
pr(searcher.flush(), None)?;
break;
};
// keep going until we find no more matches
match searcher.search(byte_buf[0]) {
SearchState::Buffering => {}
SearchState::Flushed {
buffer: last_found_needle,
age,
} => {
pr(last_found_needle)?;
pr(last_found_needle, age)?;
}
}
}
Expand All @@ -89,50 +100,6 @@ mod tests {
use super::*;
use std::io;

#[test]
fn test_window_searcher() {
let mut s = WindowSearcher::new(4);
assert_eq!(
s.search(b'a'),
SearchState::Flushed {
buffer: VecDeque::from(*b"")
}
);
assert_eq!(
s.search(b'b'),
SearchState::Flushed {
buffer: VecDeque::from(*b"a")
}
);
assert_eq!(
s.search(b'a'),
SearchState::Flushed {
buffer: VecDeque::from(*b"b")
}
);
assert_eq!(s.search(b'b'), SearchState::Buffering);
assert_eq!(
s.search(b'c'),
SearchState::Flushed {
buffer: VecDeque::from(*b"ab")
}
);
assert_eq!(
s.search(b'a'),
SearchState::Flushed {
buffer: VecDeque::from(*b"c")
}
);
assert_eq!(
s.search(b'a'),
SearchState::Flushed {
buffer: VecDeque::from(*b"a")
}
);
assert_eq!(s.search(b'b'), SearchState::Buffering);
assert_eq!(s.flush(), VecDeque::from(*b"ab"));
}

struct NullStdout;
impl WriteColor for NullStdout {
fn supports_color(&self) -> bool {
Expand Down Expand Up @@ -166,7 +133,10 @@ mod tests {
[Mon Mar 1 09:20:01 CET 2021] start new app: /Applications/app.app",
),
&mut s,
1024,
Args {
window_size: 1024,
age_penalty: 0.0,
},
)
.unwrap();
}
Expand All @@ -183,7 +153,10 @@ ab
",
),
&mut s,
1024,
Args {
window_size: 1024,
age_penalty: 0.0,
},
)
.unwrap();
}
Expand Down
75 changes: 73 additions & 2 deletions src/window_searcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ pub(crate) struct WindowSearcher {
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum SearchState {
Buffering,
Flushed { buffer: VecDeque<u8> },
Flushed {
buffer: VecDeque<u8>,
/// How long ago in bytes the buffer was found in the haystack. Or None if no match was found.
age: Option<usize>,
},
}

impl WindowSearcher {
Expand All @@ -24,6 +28,10 @@ impl WindowSearcher {
}

pub(crate) fn search(&mut self, next_byte: u8) -> SearchState {
let age = self
.matches
.last()
.map(|&i| self.haystack.len() - i - self.needle.len());
self.matches
.retain_mut(|i| self.haystack.get(*i + self.needle.len()) == Some(&next_byte));
let r = if self.matches.is_empty() {
Expand All @@ -35,7 +43,7 @@ impl WindowSearcher {
self.matches = (0..self.haystack.len())
.filter(|&i| self.haystack[i] == next_byte)
.collect();
SearchState::Flushed { buffer }
SearchState::Flushed { buffer, age }
} else {
SearchState::Buffering
};
Expand All @@ -47,3 +55,66 @@ impl WindowSearcher {
std::mem::take(&mut self.needle)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_window_searcher() {
let mut s = WindowSearcher::new(4);
let r = s.search(b'a');
assert_eq!(
r,
SearchState::Flushed {
buffer: VecDeque::from(*b""),
age: None,
}
);
let r = s.search(b'b');
assert_eq!(
r,
SearchState::Flushed {
buffer: VecDeque::from(*b"a"),
age: None,
}
);
let r = s.search(b'a');
assert_eq!(
r,
SearchState::Flushed {
buffer: VecDeque::from(*b"b"),
age: None,
}
);
assert_eq!(s.search(b'b'), SearchState::Buffering);
assert_eq!(
s.search(b'c'),
SearchState::Flushed {
buffer: VecDeque::from(*b"ab"),
age: Some(0),
}
);
let r = s.search(b'a');
assert_eq!(
r,
SearchState::Flushed {
buffer: VecDeque::from(*b"c"),
age: None,
}
);
let r = s.search(b'a');
assert_eq!(
r,
SearchState::Flushed {
buffer: VecDeque::from(*b"a"),
// 2 since the needle isn't added to the haystack until it is flushed
age: Some(2),
}
);
assert_eq!(s.search(b'b'), SearchState::Buffering);
assert_eq!(s.flush(), VecDeque::from(*b"ab"));
}

// TODO: test that the window size is respected
}

0 comments on commit a112b86

Please sign in to comment.