refactor(clean): better names

This commit is contained in:
graelo 2020-05-29 18:43:17 +02:00
parent d8386615a3
commit cab6ff3418
3 changed files with 43 additions and 85 deletions

View file

@ -16,7 +16,7 @@ pub mod view;
/// # Note /// # Note
/// ///
/// Maybe the decision to take ownership of the buffer is a bit bold. /// 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 lines: Vec<&str> = buffer.split('\n').collect();
let mut state = state::State::new(&lines, &opt.alphabet, &opt.custom_regex); 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( let mut viewbox = view::View::new(
&mut state, &mut state,
opt.multi_selection,
opt.reverse, opt.reverse,
opt.unique_hint, opt.unique_hint,
&opt.hint_alignment, &opt.hint_alignment,
@ -48,7 +47,7 @@ pub fn run(buffer: String, opt: &CliOpt) -> Vec<(String, bool)> {
viewbox.present() viewbox.present()
}; };
selections selection
} }
/// Main configuration, parsed from command line. /// Main configuration, parsed from command line.
@ -72,10 +71,6 @@ pub struct CliOpt {
#[clap(short = "X", long)] #[clap(short = "X", long)]
custom_regex: Vec<String>, custom_regex: Vec<String>,
/// Enable multi-selection.
#[clap(short, long)]
multi_selection: bool,
/// Reverse the order for assigned hints. /// Reverse the order for assigned hints.
#[clap(short, long)] #[clap(short, long)]
reverse: bool, reverse: bool,
@ -161,9 +156,6 @@ impl CliOpt {
} }
"@copyrat-regex-id" => (), // TODO "@copyrat-regex-id" => (), // TODO
"@copyrat-custom-regex" => self.custom_regex = vec![String::from(value)], "@copyrat-custom-regex" => self.custom_regex = vec![String::from(value)],
"@copyrat-multi-selection" => {
self.multi_selection = value.parse::<bool>()?;
}
"@copyrat-reverse" => { "@copyrat-reverse" => {
self.reverse = value.parse::<bool>()?; self.reverse = value.parse::<bool>()?;
} }

View file

@ -17,22 +17,18 @@ fn main() {
// Execute copyrat over the buffer (will take control over stdout). // Execute copyrat over the buffer (will take control over stdout).
// This returns the selected matches. // 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. // Early exit, signaling no selections were found.
if selections.is_empty() { if selection.is_none() {
std::process::exit(1); std::process::exit(1);
} }
let output = selections let (text, _) = selection.unwrap();
.iter()
.map(|(text, _)| text.as_str())
.collect::<Vec<&str>>()
.join("\n");
// Write output to a target_path if provided, else print to original stdout. // Write output to a target_path if provided, else print to original stdout.
match opt.target_path { match opt.target_path {
None => println!("{}", output), None => println!("{}", text),
Some(target) => { Some(target) => {
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.create(true) .create(true)
@ -41,7 +37,7 @@ fn main() {
.open(target) .open(target)
.expect("Unable to open the target file"); .expect("Unable to open the target file");
file.write(output.as_bytes()).unwrap(); file.write(text.as_bytes()).unwrap();
} }
} }
} }

View file

@ -11,7 +11,6 @@ pub struct View<'a> {
state: &'a mut state::State<'a>, state: &'a mut state::State<'a>,
matches: Vec<state::Match<'a>>, matches: Vec<state::Match<'a>>,
focus_index: usize, focus_index: usize,
multi: bool,
hint_alignment: &'a HintAlignment, hint_alignment: &'a HintAlignment,
rendering_colors: &'a ViewColors, rendering_colors: &'a ViewColors,
hint_style: Option<HintStyle>, hint_style: Option<HintStyle>,
@ -94,17 +93,16 @@ pub enum HintStyle {
} }
/// Returned value after the `View` has finished listening to events. /// Returned value after the `View` has finished listening to events.
enum CaptureEvent { enum Event {
/// Exit with no selected matches, /// Exit with no selected matches,
Exit, Exit,
/// A vector of matched text and whether it was selected with uppercase. /// A vector of matched text and whether it was selected with uppercase.
Hint(Vec<(String, bool)>), Match((String, bool)),
} }
impl<'a> View<'a> { impl<'a> View<'a> {
pub fn new( pub fn new(
state: &'a mut state::State<'a>, state: &'a mut state::State<'a>,
multi: bool,
reversed: bool, reversed: bool,
unique_hint: bool, unique_hint: bool,
hint_alignment: &'a HintAlignment, hint_alignment: &'a HintAlignment,
@ -118,7 +116,6 @@ impl<'a> View<'a> {
state, state,
matches, matches,
focus_index, focus_index,
multi,
hint_alignment, hint_alignment,
rendering_colors, rendering_colors,
hint_style, hint_style,
@ -312,8 +309,6 @@ impl<'a> View<'a> {
/// Multibyte characters are taken into account, so that the Match's `text` /// Multibyte characters are taken into account, so that the Match's `text`
/// and `hint` are rendered in their proper position. /// and `hint` are rendered in their proper position.
fn render(&self, stdout: &mut dyn io::Write) -> () { fn render(&self, stdout: &mut dyn io::Write) -> () {
write!(stdout, "{}", cursor::Hide).unwrap();
// 1. Trim all lines and render non-empty ones. // 1. Trim all lines and render non-empty ones.
View::render_lines(stdout, self.state.lines); View::render_lines(stdout, self.state.lines);
@ -365,21 +360,23 @@ impl<'a> View<'a> {
stdout.flush().unwrap(); stdout.flush().unwrap();
} }
/// Listen to keys entered on stdin, moving focus accordingly, and selecting /// Listen to keys entered on stdin, moving focus accordingly, or
/// one or multiple matches. /// selecting one match.
/// ///
/// # Panics /// # 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. /// - This function panics if termion cannot read the entered keys on stdin.
fn listen(&mut self, reader: &mut dyn io::Read, writer: &mut dyn io::Write) -> CaptureEvent { /// - 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()`. use termion::input::TermRead; // Trait for `reader.keys().next()`.
if self.matches.is_empty() { if self.matches.is_empty() {
return CaptureEvent::Exit; return Event::Exit;
} }
let mut chosen = vec![]; let mut typed_hint = String::new();
let mut typed_hint: String = "".to_owned();
// TODO: simplify
let longest_hint = self let longest_hint = self
.matches .matches
.iter() .iter()
@ -407,28 +404,9 @@ impl<'a> View<'a> {
} }
match key_res.unwrap() { match key_res.unwrap() {
// Clears an ongoing multi-hint selection, or exit.
event::Key::Esc => { 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. // Move focus to next/prev match.
event::Key::Up => self.prev(), event::Key::Up => self.prev(),
@ -436,39 +414,29 @@ impl<'a> View<'a> {
event::Key::Left => self.prev(), event::Key::Left => self.prev(),
event::Key::Right => self.next(), event::Key::Right => self.next(),
// Pressing space finalizes an ongoing multi-hint selection (without // TODO: use a Trie or another data structure to determine
// selecting the focused match). Pressing other characters attempts at // if the entered key belongs to a longer hint.
// finding a match with a corresponding hint. // Attempts at finding a match with a corresponding hint.
event::Key::Char(ch) => { event::Key::Char(ch) => {
if ch == ' ' && self.multi {
return CaptureEvent::Hint(chosen);
}
let key = ch.to_string(); let key = ch.to_string();
let lower_key = key.to_lowercase(); let lower_key = key.to_lowercase();
typed_hint.push_str(&lower_key); typed_hint.push_str(&lower_key);
// Find the match that corresponds to the entered key. // Find the match that corresponds to the entered key.
let selection = self.matches let found = self.matches
.iter() .iter()
// Avoid cloning typed_hint for comparison. // Avoid cloning typed_hint for comparison.
.find(|&mat| mat.hint.as_deref().unwrap_or_default() == &typed_hint); .find(|&mat| mat.hint.as_deref().unwrap_or_default() == &typed_hint);
match selection { match found {
Some(mat) => { Some(mat) => {
chosen.push((mat.text.to_string(), key != lower_key)); let text = mat.text.to_string();
let uppercased = key != lower_key;
if self.multi { return Event::Match((text, uppercased));
typed_hint.clear();
} else {
return CaptureEvent::Hint(chosen);
}
} }
None => { None => {
// TODO: use a Trie or another data structure to determine if typed_hint.len() >= longest_hint.len() {
// if the entered key belongs to a longer hint.
if !self.multi && typed_hint.len() >= longest_hint.len() {
break; break;
} }
} }
@ -479,15 +447,18 @@ impl<'a> View<'a> {
_ => (), _ => (),
} }
// Render on stdout if we did not exit earlier (move focus, // Render on stdout if we did not exit earlier.
// multi-selection).
self.render(writer); 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 std::io::Write;
use termion::raw::IntoRawMode; use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen; use termion::screen::AlternateScreen;
@ -499,14 +470,16 @@ impl<'a> View<'a> {
.expect("Cannot access alternate screen."), .expect("Cannot access alternate screen."),
); );
let hints = match self.listen(&mut stdin, &mut stdout) { write!(stdout, "{}", cursor::Hide).unwrap();
CaptureEvent::Exit => vec![],
CaptureEvent::Hint(chosen) => chosen, let selection = match self.listen(&mut stdin, &mut stdout) {
Event::Exit => None,
Event::Match((text, uppercased)) => Some((text, uppercased)),
}; };
write!(stdout, "{}", cursor::Show).unwrap(); write!(stdout, "{}", cursor::Show).unwrap();
hints selection
} }
} }
@ -757,7 +730,6 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
state: &mut state, state: &mut state,
matches: vec![], // no matches matches: vec![], // no matches
focus_index: 0, focus_index: 0,
multi: false,
hint_alignment: &hint_alignment, hint_alignment: &hint_alignment,
rendering_colors: &rendering_colors, rendering_colors: &rendering_colors,
hint_style: None, hint_style: None,
@ -799,7 +771,6 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
let custom_regexes = [].to_vec(); let custom_regexes = [].to_vec();
let alphabet = alphabets::Alphabet("abcd".to_string()); let alphabet = alphabets::Alphabet("abcd".to_string());
let mut state = state::State::new(&lines, &alphabet, &custom_regexes); let mut state = state::State::new(&lines, &alphabet, &custom_regexes);
let multi = false;
let reversed = true; let reversed = true;
let unique_hint = false; let unique_hint = false;
@ -816,7 +787,6 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
let view = View::new( let view = View::new(
&mut state, &mut state,
multi,
reversed, reversed,
unique_hint, unique_hint,
&hint_alignment, &hint_alignment,