From 3be517ecb0699d802b20ec93b749b6a73482c6cc Mon Sep 17 00:00:00 2001 From: Rotem Horesh Date: Tue, 31 Dec 2024 15:53:17 +0200 Subject: [PATCH] feat: allow moving the cursor with arrow keys --- Cargo.lock | 405 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 335 ++++++++++++++++++++++++++----------------- 2 files changed, 607 insertions(+), 133 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50ecef5..7f7ae81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "autocfg" version = "1.4.0" @@ -23,12 +29,41 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "crossterm" version = "0.27.0" @@ -38,13 +73,29 @@ dependencies = [ "bitflags", "crossterm_winapi", "libc", - "mio", + "mio 0.8.11", "parking_lot", "signal-hook", "signal-hook-mio", "winapi", ] +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio 1.0.3", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + [[package]] name = "crossterm_winapi" version = "0.9.1" @@ -54,12 +105,151 @@ dependencies = [ "winapi", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "instability" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898e106451f7335950c9cc64f8ec67b5f65698679ac67ed00619aeef14e1cf75" +dependencies = [ + "darling", + "indoc", + "pretty_assertions", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "lock_api" version = "0.4.12" @@ -76,6 +266,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + [[package]] name = "memchr" version = "2.7.4" @@ -91,7 +290,19 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.52.0", ] [[package]] @@ -117,6 +328,61 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm 0.28.1", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + [[package]] name = "redox_syscall" version = "0.5.8" @@ -159,10 +425,36 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" name = "replay" version = "0.1.0" dependencies = [ - "crossterm", + "crossterm 0.27.0", + "ratatui", "regex", ] +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "scopeguard" version = "1.2.0" @@ -186,7 +478,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio", + "mio 0.8.11", + "mio 1.0.3", "signal-hook", ] @@ -205,6 +498,86 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -242,6 +615,24 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -362,3 +753,9 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/src/main.rs b/src/main.rs index c6191a0..36f20cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ -use std::io::{self, Write, stdout}; +use std::{fmt::Display, io}; use crossterm::{ - cursor, - event::{Event, KeyCode, read}, - execute, style, - terminal::{self, ClearType}, + cursor::MoveTo, + event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}, + execute, + style::{Color, Print, SetBackgroundColor}, + terminal::{self, Clear, ClearType}, }; use regex::Regex; @@ -15,13 +16,13 @@ const HAY_TITLE: &str = "TEST STRING: "; const LEFT_PADDING: u16 = max(RE_TITLE.len(), HAY_TITLE.len()) as u16; -const LAYER_COLORS: [style::Color; 6] = [ - style::Color::DarkGrey, // marks the main match itself - style::Color::DarkGreen, - style::Color::DarkYellow, - style::Color::DarkBlue, - style::Color::DarkMagenta, - style::Color::DarkCyan, +const LAYER_COLORS: [Color; 6] = [ + Color::DarkGrey, // marks the main match itself + Color::DarkGreen, + Color::DarkYellow, + Color::DarkBlue, + Color::DarkMagenta, + Color::DarkCyan, ]; const fn max(a: usize, b: usize) -> usize { @@ -33,134 +34,227 @@ enum Type { Hay, } +#[derive(Default)] struct Input { - typ: Type, - re: String, - hay: String, + string: String, + cursor: usize, } impl Input { - pub fn new() -> Self { - Self { - typ: Type::Re, - re: String::new(), - hay: String::new(), + pub fn insert(&mut self, ch: char) { + let index = self.byte_index(); + self.string.insert(index, ch); + self.move_cursor_right(); + } + + pub fn delete_char(&mut self) { + if self.cursor > 0 { + let before = self.string.chars().take(self.cursor - 1); + let after = self.string.chars().skip(self.cursor); + + self.string = before.chain(after).collect(); + self.move_cursor_left(); } } - pub fn pop(&mut self) { - match self.typ { - Type::Re => self.re.pop(), - Type::Hay => self.hay.pop(), + pub fn move_cursor_end(&mut self) { + self.cursor = self.string.len(); + } + + pub fn move_cursor_start(&mut self) { + self.cursor = 0; + } + + pub fn move_cursor_left(&mut self) { + let cursor_moved_left = self.cursor.saturating_sub(1); + self.cursor = self.clamp_cursor(cursor_moved_left); + } + + pub fn move_cursor_right(&mut self) { + let cursor_moved_right = self.cursor.saturating_add(1); + self.cursor = self.clamp_cursor(cursor_moved_right); + } + + fn clamp_cursor(&self, new_cursor_pos: usize) -> usize { + new_cursor_pos.clamp(0, self.string.chars().count()) + } + + /// Returns the byte index based on the character position. + /// + /// Since each character in a string can be contain multiple bytes, it's necessary to calculate + /// the byte index based on the index of the character. + fn byte_index(&self) -> usize { + self.string + .char_indices() + .map(|(i, _)| i) + .nth(self.cursor) + .unwrap_or(self.string.len()) + } +} + +struct App { + typ: Type, + re: Input, + hay: Input, + exit: bool, +} + +impl App { + pub fn run(&mut self, w: &mut W) -> io::Result<()> + where + W: io::Write, + { + while !self.exit { + self.draw(w)?; + self.handle_events()?; + } + + // clear the screen after exiting + execute!(w, MoveTo(0, 0), Clear(ClearType::All))?; + w.flush() + } + + fn draw(&self, w: &mut W) -> io::Result<()> + where + W: io::Write, + { + execute!(w, Clear(ClearType::All))?; + + print_at(w, Color::Reset, RE_TITLE, 0, 0)?; + self.draw_re(w, LEFT_PADDING, 0)?; + + print_at(w, Color::Reset, HAY_TITLE, 0, LINES_BETWEEN)?; + self.draw_hay(w, LEFT_PADDING, LINES_BETWEEN)?; + + let (col, row) = self.pos(); + execute!(w, MoveTo(col, row))?; + + w.flush() + } + + fn handle_events(&mut self) -> io::Result<()> { + 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) + } + _ => {} }; + Ok(()) + } + + fn handle_key_event(&mut self, key_event: KeyEvent) { + match key_event.code { + KeyCode::Char(ch) => self.current_mut().insert(ch), + KeyCode::Backspace => self.current_mut().delete_char(), + KeyCode::Left => { + if key_event.modifiers.intersects(KeyModifiers::CONTROL) { + self.current_mut().move_cursor_start() + } else { + self.current_mut().move_cursor_left() + } + } + KeyCode::Right => { + if key_event.modifiers.intersects(KeyModifiers::CONTROL) { + self.current_mut().move_cursor_end() + } else { + self.current_mut().move_cursor_right() + } + } + KeyCode::Tab | KeyCode::Up | KeyCode::Down => self.switch(), + KeyCode::Esc => self.exit(), + _ => {} + } } - pub fn push(&mut self, ch: char) { + fn exit(&mut self) { + self.exit = true; + } + + fn current_mut(&mut self) -> &mut Input { match self.typ { - Type::Re => self.re.push(ch), - Type::Hay => self.hay.push(ch), - }; + Type::Re => &mut self.re, + Type::Hay => &mut self.hay, + } + } + + pub fn new() -> Self { + Self { + typ: Type::Re, + re: Input::default(), + hay: Input::default(), + exit: false, + } } - pub fn switch(&mut self) { + fn switch(&mut self) { self.typ = match self.typ { Type::Re => Type::Hay, Type::Hay => Type::Re, }; } - pub fn print_re(&self, w: &mut W) -> io::Result<()> + pub fn draw_re(&self, w: &mut W, col: u16, row: u16) -> io::Result<()> where W: io::Write, { - execute!(w, cursor::MoveTo(LEFT_PADDING, 0))?; + execute!(w, MoveTo(col, row))?; let mut layer = 0; - for ch in self.re.chars() { - match ch { + for ch in self.re.string.chars() { + let color = match ch { '(' => { layer += 1; - execute!( - w, - style::SetBackgroundColor(LAYER_COLORS[layer]), - style::Print(ch), - style::SetBackgroundColor(style::Color::Reset) - )?; + LAYER_COLORS[layer] } ')' => { - execute!( - w, - style::SetBackgroundColor(LAYER_COLORS[layer]), - style::Print(ch), - style::SetBackgroundColor(style::Color::Reset) - )?; + let color = LAYER_COLORS[layer]; layer = layer.saturating_sub(1); + color } - _ => { - execute!( - w, - style::SetBackgroundColor(style::Color::DarkGrey), - style::Print(ch), - style::SetBackgroundColor(style::Color::Reset) - )?; - } - } + _ => Color::DarkGrey, + }; + print(w, color, ch)?; } Ok(()) } - pub fn print_hay(&self, w: &mut W) -> io::Result<()> + fn draw_hay(&self, w: &mut W, col: u16, row: u16) -> io::Result<()> where W: io::Write, { - let re = match Regex::new(&self.re) { + let re = match Regex::new(&self.re.string) { Ok(re) => re, Err(err) => { - execute!( - w, - cursor::MoveTo(LEFT_PADDING, LINES_BETWEEN), - style::SetBackgroundColor(style::Color::DarkRed), - style::Print("ERROR:"), - style::SetBackgroundColor(style::Color::Reset), - )?; + print_at(w, Color::DarkRed, "ERROR:", col, row)?; for (i, line) in err.to_string().lines().enumerate() { - execute!( - w, - cursor::MoveTo(LEFT_PADDING, LINES_BETWEEN + 1 + i as u16), - style::Print(line), - )?; + print_at(w, Color::Reset, line, col, row + 1 + i as u16)?; } return Ok(()); } }; - let caps = re.captures_iter(&self.hay); + let caps = re.captures_iter(&self.hay.string); - execute!( - w, - cursor::MoveTo(LEFT_PADDING, LINES_BETWEEN), - style::Print(&self.hay), - )?; + print_at(w, Color::Reset, &self.hay.string, col, row)?; for cap in caps { - let mut layers: Vec = Vec::new(); + let mut layers = Vec::new(); for mat in cap.iter().flatten() { - let start = mat.start(); - let end = mat.end(); - - while layers.last().is_some_and(|l| *l <= start) { + while layers.last().is_some_and(|l| *l <= mat.start()) { layers.pop(); } - layers.push(end); + layers.push(mat.end()); - execute!( + print_at( w, - cursor::MoveTo(LEFT_PADDING + start as u16, LINES_BETWEEN), - style::SetBackgroundColor(LAYER_COLORS[layers.len() - 1]), - style::Print(&self.hay[start..end]), - style::SetBackgroundColor(style::Color::Reset) + LAYER_COLORS[layers.len() - 1], + &self.hay.string[mat.start()..mat.end()], + col + mat.start() as u16, + row, )?; } } @@ -168,56 +262,39 @@ impl Input { Ok(()) } - pub fn pos(&self) -> (u16, u16) { + fn pos(&self) -> (u16, u16) { match self.typ { - Type::Re => (self.re.len() as u16, 0), - Type::Hay => (self.hay.len() as u16, LINES_BETWEEN), + Type::Re => (LEFT_PADDING + self.re.cursor as u16, 0), + Type::Hay => (LEFT_PADDING + self.hay.cursor as u16, LINES_BETWEEN), } } } -fn main() -> io::Result<()> { - let mut stdout = stdout(); - let (_, _) = terminal::size()?; - - terminal::enable_raw_mode()?; - - let mut input = Input::new(); - - loop { - let (col, row) = input.pos(); - execute!( - stdout, - terminal::Clear(ClearType::All), - cursor::MoveTo(0, 0), - style::Print(RE_TITLE), - cursor::MoveTo(LEFT_PADDING, 0), - cursor::MoveTo(0, LINES_BETWEEN), - style::Print(HAY_TITLE), - )?; - input.print_re(&mut stdout)?; - input.print_hay(&mut stdout)?; - execute!(stdout, cursor::MoveTo(col + LEFT_PADDING, row))?; - stdout.flush()?; - - if let Event::Key(event) = read()? { - match event.code { - KeyCode::Esc => break, - KeyCode::Backspace => input.pop(), - KeyCode::Char(ch) => input.push(ch), - KeyCode::Tab => input.switch(), - _ => (), - } - } - } - +fn print(w: &mut W, bg: Color, text: T) -> io::Result<()> +where + W: io::Write, + T: Display, +{ execute!( - stdout, - terminal::Clear(ClearType::All), - cursor::MoveTo(0, 0), - )?; + w, + SetBackgroundColor(bg), + Print(text), + SetBackgroundColor(Color::Reset) + ) +} - terminal::disable_raw_mode()?; +fn print_at(w: &mut W, bg: Color, text: T, col: u16, row: u16) -> io::Result<()> +where + W: io::Write, + T: Display, +{ + execute!(w, MoveTo(col, row))?; + print(w, bg, text) +} - Ok(()) +fn main() -> io::Result<()> { + terminal::enable_raw_mode()?; + let app_result = App::new().run(&mut io::stdout()); + terminal::disable_raw_mode()?; + app_result }