Skip to content

Commit

Permalink
perf: caching regex compilations and captures computations
Browse files Browse the repository at this point in the history
  • Loading branch information
rotmh committed Jan 2, 2025
1 parent 44f80e7 commit 77b150b
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 47 deletions.
48 changes: 48 additions & 0 deletions src/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use std::collections::HashMap;

use regex::Regex;

struct CapturesCache {
cache: HashMap<String, Vec<Vec<(usize, usize)>>>,
}

impl CapturesCache {
pub fn new() -> Self {
Self {
cache: HashMap::new(),
}
}

pub fn get_or_init(&mut self, re: &Regex, hay: &str) -> &Vec<Vec<(usize, usize)>> {
self.cache.entry(hay.to_owned()).or_insert_with(|| {
re.captures_iter(hay)
.map(|c| c.iter().flatten().map(|m| (m.start(), m.end())).collect())
.collect()
})
}
}

pub struct RegexCache {
cache: HashMap<String, Result<(Regex, CapturesCache), regex::Error>>,
}

impl RegexCache {
pub fn new() -> Self {
Self {
cache: HashMap::new(),
}
}

pub fn get_or_init(
&mut self,
re: &str,
hay: &str,
) -> Result<&Vec<Vec<(usize, usize)>>, &regex::Error> {
self.cache
.entry(re.to_owned())
.or_insert_with(|| Regex::new("re").map(|r| (r, CapturesCache::new())))
.as_mut()
.map(|(r, c)| c.get_or_init(r, hay))
.map_err(|err| &*err)
}
}
130 changes: 83 additions & 47 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
mod cache;

use std::{fmt::Display, io};

use cache::RegexCache;
use crossterm::{
cursor::MoveTo,
event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
Expand Down Expand Up @@ -29,6 +32,30 @@ const fn max(a: usize, b: usize) -> usize {
[a, b][(a < b) as usize]
}

struct Change {
content: bool,
cursor: bool,
}

impl Change {
pub fn new() -> Self {
Self {
content: false,
cursor: false,
}
}

pub fn content(mut self) -> Self {
self.content = true;
self
}

pub fn cursor(mut self) -> Self {
self.cursor = true;
self
}
}

enum Type {
Re,
Hay,
Expand Down Expand Up @@ -94,6 +121,7 @@ impl Input {

struct App {
typ: Type,
regex: RegexCache,
re: Input,
hay: Input,
exit: bool,
Expand All @@ -104,68 +132,88 @@ impl App {
where
W: io::Write,
{
let mut change = Change::new().cursor().content();

while !self.exit {
self.draw(w)?;
self.handle_events()?;
if change.content {
self.draw(w)?;
}
if change.cursor {
let (col, row) = self.pos();
execute!(w, MoveTo(col, row))?;
}
if change.content || change.cursor {
w.flush()?;
}

change = self.handle_events()?;
}

// clear the screen after exiting
execute!(w, MoveTo(0, 0), Clear(ClearType::All))?;
w.flush()
}

fn draw<W>(&self, w: &mut W) -> io::Result<()>
fn draw<W>(&mut self, w: &mut W) -> io::Result<()>
where
W: io::Write,
{
execute!(w, Clear(ClearType::All))?;

print_at(w, Color::Reset, RE_TITLE, 0, 0)?;
print_at(w, Color::Reset, HAY_TITLE, 0, LINES_BETWEEN)?;
print_layers_color(w, LEFT_PADDING, LINES_BETWEEN * 3)?;

self.draw_re(w, LEFT_PADDING, 0)?;
self.draw_hay(w, LEFT_PADDING, LINES_BETWEEN)?;

let (col, row) = self.pos();
execute!(w, MoveTo(col, row))?;

w.flush()
self.draw_hay(w, LEFT_PADDING, LINES_BETWEEN)
}

fn handle_events(&mut self) -> io::Result<()> {
match event::read()? {
fn handle_events(&mut self) -> io::Result<Change> {
let change = match event::read()? {
// it's important to check that the event is a key press event as
// crossterm also emits key release and repeat events on Windows.
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
self.handle_key_event(key_event)
}
_ => {}
_ => Change::new(),
};
Ok(())
Ok(change)
}

fn handle_key_event(&mut self, key_event: KeyEvent) {
fn handle_key_event(&mut self, key_event: KeyEvent) -> Change {
match key_event.code {
KeyCode::Char(ch) => self.current_mut().insert(ch),
KeyCode::Backspace => self.current_mut().delete_char(),
KeyCode::Char(ch) => {
self.current_mut().insert(ch);
Change::new().content().cursor()
}
KeyCode::Backspace => {
self.current_mut().delete_char();
Change::new().content().cursor()
}
KeyCode::Left => {
if key_event.modifiers.intersects(KeyModifiers::CONTROL) {
self.current_mut().move_cursor_start()
self.current_mut().move_cursor_start();
} else {
self.current_mut().move_cursor_left()
self.current_mut().move_cursor_left();
}
Change::new().cursor()
}
KeyCode::Right => {
if key_event.modifiers.intersects(KeyModifiers::CONTROL) {
self.current_mut().move_cursor_end()
self.current_mut().move_cursor_end();
} else {
self.current_mut().move_cursor_right()
self.current_mut().move_cursor_right();
}
Change::new().cursor()
}
KeyCode::Tab | KeyCode::Up | KeyCode::Down => {
self.switch();
Change::new().cursor()
}
KeyCode::Esc => {
self.exit();
Change::new()
}
KeyCode::Tab | KeyCode::Up | KeyCode::Down => self.switch(),
KeyCode::Esc => self.exit(),
_ => {}
_ => Change::new(),
}
}

Expand All @@ -183,6 +231,7 @@ impl App {
pub fn new() -> Self {
Self {
typ: Type::Re,
regex: RegexCache::new(),
re: Input::default(),
hay: Input::default(),
exit: false,
Expand Down Expand Up @@ -223,12 +272,12 @@ impl App {
Ok(())
}

fn draw_hay<W>(&self, w: &mut W, col: u16, row: u16) -> io::Result<()>
fn draw_hay<W>(&mut self, w: &mut W, col: u16, row: u16) -> io::Result<()>
where
W: io::Write,
{
let re = match Regex::new(&self.re.string) {
Ok(re) => re,
let caps = match self.regex.get_or_init(&self.re.string, &self.hay.string) {
Ok(caps) => caps,
Err(err) => {
print_at(w, Color::DarkRed, "ERROR:", col, row)?;
for (i, line) in err.to_string().lines().enumerate() {
Expand All @@ -237,36 +286,35 @@ impl App {
return Ok(());
}
};
let caps = re.captures_iter(&self.hay.string);

print_at(w, Color::Reset, &self.hay.string, col, row)?;

for cap in caps {
let mut layers = Vec::new();
let mut infos = Vec::new();

for mat in cap.iter().flatten() {
while layers.last().is_some_and(|l| *l <= mat.start()) {
for (start, end) in cap {
while layers.last().is_some_and(|l| *l <= start) {
layers.pop();
}
layers.push(mat.end());
layers.push(end);

let color = LAYER_COLORS[layers.len() - 1];

print_at(
w,
color,
&self.hay.string[mat.start()..mat.end()],
col + mat.start() as u16,
&self.hay.string[*start..*end],
col + *start as u16,
row,
)?;

infos.push((mat.start(), layers.len() - 1));
infos.push((start, layers.len() - 1));
}

for (i, (idx, layer)) in infos.iter().enumerate() {
let color = LAYER_COLORS[*layer];
let col = col + *idx as u16;
let col = col + **idx as u16;

for line in 1..=infos.len() - i + 1 {
print_at(w, color, '|', col, row + line as u16)?;
Expand Down Expand Up @@ -309,18 +357,6 @@ where
print(w, bg, text)
}

fn print_layers_color<W>(w: &mut W, col: u16, row: u16) -> io::Result<()>
where
W: io::Write,
{
execute!(w, MoveTo(col, row))?;
for color in LAYER_COLORS {
print(w, color, " ")?;
}

Ok(())
}

fn main() -> io::Result<()> {
terminal::enable_raw_mode()?;
let app_result = App::new().run(&mut io::stdout());
Expand Down

0 comments on commit 77b150b

Please sign in to comment.