diff --git a/src/lib.rs b/src/lib.rs index 8454e33..05fd230 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ pub mod view; /// # Note /// /// Maybe the decision to take ownership of the buffer is a bit bold. -pub fn run(buffer: String, opt: &CliOpt) -> Vec<(String, bool)> { +pub fn run(buffer: String, opt: &CliOpt) -> Option<(String, bool)> { let lines: Vec<&str> = buffer.split('\n').collect(); let mut state = state::State::new(&lines, &opt.alphabet, &opt.custom_regex); @@ -34,10 +34,9 @@ pub fn run(buffer: String, opt: &CliOpt) -> Vec<(String, bool)> { }, }; - let selections: Vec<(String, bool)> = { + let selection: Option<(String, bool)> = { let mut viewbox = view::View::new( &mut state, - opt.multi_selection, opt.reverse, opt.unique_hint, &opt.hint_alignment, @@ -48,7 +47,7 @@ pub fn run(buffer: String, opt: &CliOpt) -> Vec<(String, bool)> { viewbox.present() }; - selections + selection } /// Main configuration, parsed from command line. @@ -72,10 +71,6 @@ pub struct CliOpt { #[clap(short = "X", long)] custom_regex: Vec, - /// Enable multi-selection. - #[clap(short, long)] - multi_selection: bool, - /// Reverse the order for assigned hints. #[clap(short, long)] reverse: bool, @@ -161,9 +156,6 @@ impl CliOpt { } "@copyrat-regex-id" => (), // TODO "@copyrat-custom-regex" => self.custom_regex = vec![String::from(value)], - "@copyrat-multi-selection" => { - self.multi_selection = value.parse::()?; - } "@copyrat-reverse" => { self.reverse = value.parse::()?; } diff --git a/src/main.rs b/src/main.rs index 5c7f128..2cee8da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,22 +17,18 @@ fn main() { // Execute copyrat over the buffer (will take control over stdout). // This returns the selected matches. - let selections: Vec<(String, bool)> = run(buffer, &opt); + let selection: Option<(String, bool)> = run(buffer, &opt); // Early exit, signaling no selections were found. - if selections.is_empty() { + if selection.is_none() { std::process::exit(1); } - let output = selections - .iter() - .map(|(text, _)| text.as_str()) - .collect::>() - .join("\n"); + let (text, _) = selection.unwrap(); // Write output to a target_path if provided, else print to original stdout. match opt.target_path { - None => println!("{}", output), + None => println!("{}", text), Some(target) => { let mut file = OpenOptions::new() .create(true) @@ -41,7 +37,7 @@ fn main() { .open(target) .expect("Unable to open the target file"); - file.write(output.as_bytes()).unwrap(); + file.write(text.as_bytes()).unwrap(); } } } diff --git a/src/view.rs b/src/view.rs index 181045d..6edc9a1 100644 --- a/src/view.rs +++ b/src/view.rs @@ -11,7 +11,6 @@ pub struct View<'a> { state: &'a mut state::State<'a>, matches: Vec>, focus_index: usize, - multi: bool, hint_alignment: &'a HintAlignment, rendering_colors: &'a ViewColors, hint_style: Option, @@ -94,17 +93,16 @@ pub enum HintStyle { } /// Returned value after the `View` has finished listening to events. -enum CaptureEvent { +enum Event { /// Exit with no selected matches, Exit, /// A vector of matched text and whether it was selected with uppercase. - Hint(Vec<(String, bool)>), + Match((String, bool)), } impl<'a> View<'a> { pub fn new( state: &'a mut state::State<'a>, - multi: bool, reversed: bool, unique_hint: bool, hint_alignment: &'a HintAlignment, @@ -118,7 +116,6 @@ impl<'a> View<'a> { state, matches, focus_index, - multi, hint_alignment, rendering_colors, hint_style, @@ -312,8 +309,6 @@ impl<'a> View<'a> { /// Multibyte characters are taken into account, so that the Match's `text` /// and `hint` are rendered in their proper position. fn render(&self, stdout: &mut dyn io::Write) -> () { - write!(stdout, "{}", cursor::Hide).unwrap(); - // 1. Trim all lines and render non-empty ones. View::render_lines(stdout, self.state.lines); @@ -365,21 +360,23 @@ impl<'a> View<'a> { stdout.flush().unwrap(); } - /// Listen to keys entered on stdin, moving focus accordingly, and selecting - /// one or multiple matches. + /// Listen to keys entered on stdin, moving focus accordingly, or + /// selecting one match. /// /// # Panics - /// This function panics if termion cannot read the entered keys on stdin. - /// This function also panics if the user types Insert on a line without hints. - fn listen(&mut self, reader: &mut dyn io::Read, writer: &mut dyn io::Write) -> CaptureEvent { + /// + /// - This function panics if termion cannot read the entered keys on stdin. + /// - This function also panics if the user types Insert on a line without hints. + fn listen(&mut self, reader: &mut dyn io::Read, writer: &mut dyn io::Write) -> Event { use termion::input::TermRead; // Trait for `reader.keys().next()`. if self.matches.is_empty() { - return CaptureEvent::Exit; + return Event::Exit; } - let mut chosen = vec![]; - let mut typed_hint: String = "".to_owned(); + let mut typed_hint = String::new(); + + // TODO: simplify let longest_hint = self .matches .iter() @@ -407,68 +404,39 @@ impl<'a> View<'a> { } match key_res.unwrap() { - // Clears an ongoing multi-hint selection, or exit. event::Key::Esc => { - if self.multi && !typed_hint.is_empty() { - typed_hint.clear(); - } else { - break; - } + break; } - // In multi-selection mode, this appends the selected hint to the - // vector of selections. In normal mode, this returns with the hint - // selected. - event::Key::Insert => match self.matches.get(self.focus_index) { - Some(mat) => { - chosen.push((mat.text.to_string(), false)); - - if !self.multi { - return CaptureEvent::Hint(chosen); - } - } - None => panic!("Match not found?"), - }, - // Move focus to next/prev match. event::Key::Up => self.prev(), event::Key::Down => self.next(), event::Key::Left => self.prev(), event::Key::Right => self.next(), - // Pressing space finalizes an ongoing multi-hint selection (without - // selecting the focused match). Pressing other characters attempts at - // finding a match with a corresponding hint. + // TODO: use a Trie or another data structure to determine + // if the entered key belongs to a longer hint. + // Attempts at finding a match with a corresponding hint. event::Key::Char(ch) => { - if ch == ' ' && self.multi { - return CaptureEvent::Hint(chosen); - } - let key = ch.to_string(); let lower_key = key.to_lowercase(); typed_hint.push_str(&lower_key); // Find the match that corresponds to the entered key. - let selection = self.matches + let found = self.matches .iter() // Avoid cloning typed_hint for comparison. .find(|&mat| mat.hint.as_deref().unwrap_or_default() == &typed_hint); - match selection { + match found { Some(mat) => { - chosen.push((mat.text.to_string(), key != lower_key)); - - if self.multi { - typed_hint.clear(); - } else { - return CaptureEvent::Hint(chosen); - } + let text = mat.text.to_string(); + let uppercased = key != lower_key; + return Event::Match((text, uppercased)); } None => { - // TODO: use a Trie or another data structure to determine - // if the entered key belongs to a longer hint. - if !self.multi && typed_hint.len() >= longest_hint.len() { + if typed_hint.len() >= longest_hint.len() { break; } } @@ -479,15 +447,18 @@ impl<'a> View<'a> { _ => (), } - // Render on stdout if we did not exit earlier (move focus, - // multi-selection). + // Render on stdout if we did not exit earlier. self.render(writer); } - CaptureEvent::Exit + Event::Exit } - pub fn present(&mut self) -> Vec<(String, bool)> { + /// Configure the terminal and display the `View`. + /// + /// - Setup steps: switch to alternate screen, switch to raw mode, hide the cursor. + /// - Teardown steps: show cursor, back to main screen. + pub fn present(&mut self) -> Option<(String, bool)> { use std::io::Write; use termion::raw::IntoRawMode; use termion::screen::AlternateScreen; @@ -499,14 +470,16 @@ impl<'a> View<'a> { .expect("Cannot access alternate screen."), ); - let hints = match self.listen(&mut stdin, &mut stdout) { - CaptureEvent::Exit => vec![], - CaptureEvent::Hint(chosen) => chosen, + write!(stdout, "{}", cursor::Hide).unwrap(); + + let selection = match self.listen(&mut stdin, &mut stdout) { + Event::Exit => None, + Event::Match((text, uppercased)) => Some((text, uppercased)), }; write!(stdout, "{}", cursor::Show).unwrap(); - hints + selection } } @@ -757,7 +730,6 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; state: &mut state, matches: vec![], // no matches focus_index: 0, - multi: false, hint_alignment: &hint_alignment, rendering_colors: &rendering_colors, hint_style: None, @@ -799,7 +771,6 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let custom_regexes = [].to_vec(); let alphabet = alphabets::Alphabet("abcd".to_string()); let mut state = state::State::new(&lines, &alphabet, &custom_regexes); - let multi = false; let reversed = true; let unique_hint = false; @@ -816,7 +787,6 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let view = View::new( &mut state, - multi, reversed, unique_hint, &hint_alignment,