refactor: add comments & change some names

This commit is contained in:
graelo 2020-05-21 08:28:27 +02:00
parent 34d0bb5a35
commit 777a460ec9
2 changed files with 177 additions and 143 deletions

View file

@ -12,6 +12,8 @@ use std::fs::OpenOptions;
use std::io::prelude::*;
use std::io::{self, Read};
// TODO: position as an enum ::Leading ::Trailing
fn app_args<'a>() -> clap::ArgMatches<'a> {
App::new("thumbs")
.version(crate_version!())
@ -138,17 +140,18 @@ fn main() {
let select_foreground_color = colors::get_color(args.value_of("select_foreground_color").unwrap());
let select_background_color = colors::get_color(args.value_of("select_background_color").unwrap());
// Copy the pane contents (piped in via stdin) into a buffer, and split lines.
let mut buffer = String::new();
let stdin = io::stdin();
let mut handle = stdin.lock();
let mut output = String::new();
handle.read_to_string(&mut output).unwrap();
handle.read_to_string(&mut buffer).unwrap();
let lines = output.split('\n').collect::<Vec<&str>>();
let lines: Vec<&str> = buffer.split('\n').collect();
let mut state = state::State::new(&lines, alphabet, &regexp);
let selected = {
let selections = {
let mut viewbox = view::View::new(
&mut state,
multi,
@ -167,8 +170,12 @@ fn main() {
viewbox.present()
};
if !selected.is_empty() {
let output = selected
// Early exit, signaling tmux we had no selections.
if selections.is_empty() {
::std::process::exit(1);
}
let output = selections
.iter()
.map(|(text, upcase)| {
let upcase_value = if *upcase { "true" } else { "false" };
@ -182,7 +189,9 @@ fn main() {
.collect::<Vec<_>>()
.join("\n");
if let Some(target) = target {
match target {
None => println!("{}", output),
Some(target) => {
let mut file = OpenOptions::new()
.create(true)
.truncate(true)
@ -191,10 +200,6 @@ fn main() {
.expect("Unable to open the target file");
file.write(output.as_bytes()).unwrap();
} else {
print!("{}", output);
}
} else {
::std::process::exit(1);
}
}

View file

@ -62,97 +62,124 @@ impl<'a> View<'a> {
}
}
/// Move focus onto the previous hint.
pub fn prev(&mut self) {
if self.skip > 0 {
self.skip -= 1;
}
}
/// Move focus onto the next hint.
pub fn next(&mut self) {
if self.skip < self.matches.len() - 1 {
self.skip += 1;
}
}
fn make_hint_text(&self, hint: &str) -> String {
if self.contrast {
format!("[{}]", hint)
} else {
hint.to_string()
}
}
// /// TODO remove
// fn make_hint_text(&self, hint: &str) -> String {
// if self.contrast {
// format!("[{}]", hint)
// } else {
// hint.to_string()
// }
// }
/// Render the view on stdout.
fn render(&self, stdout: &mut dyn Write) -> () {
write!(stdout, "{}", cursor::Hide).unwrap();
// Trim all lines and render non-empty ones.
for (index, line) in self.state.lines.iter().enumerate() {
let clean = line.trim_end_matches(|c: char| c.is_whitespace());
// remove trailing whitespaces
let cleaned_line = line.trim_end_matches(|c: char| c.is_whitespace());
if !clean.is_empty() {
let text = self.make_hint_text(line);
print!("{goto}{text}", goto = cursor::Goto(1, index as u16 + 1), text = &text);
}
if cleaned_line.is_empty() {
continue; // Don't render empty lines.
}
let selected = self.matches.get(self.skip);
// let text = self.make_hint_text(line);
// print!(
write!(
stdout,
"{goto}{text}",
goto = cursor::Goto(1, index as u16 + 1),
text = &cleaned_line,
)
.unwrap();
}
for mat in self.matches.iter() {
let selected_color = if selected == Some(mat) {
&self.select_foreground_color
// let focused = self.matches.get(self.skip);
for (index, mat) in self.matches.iter().enumerate() {
// 1. Render the match's text.
//
// To help identify it, the match thas has focus is rendered with a dedicated color.
// let (text_fg_color, text_bg_color) = if focused == Some(mat) {
let (text_fg_color, text_bg_color) = if index == self.skip {
(&self.select_foreground_color, &self.select_background_color)
} else {
&self.foreground_color
};
let selected_background_color = if selected == Some(mat) {
&self.select_background_color
} else {
&self.background_color
(&self.foreground_color, &self.background_color)
};
// Find long utf sequences and extract it from mat.x
// If multibyte characters occur before the hint (in the "prefix"), then
// their compouding takes less space on screen when printed: for
// instance ´ + e = é. Consequently the hint offset has to be adjusted
// to the left.
let line = &self.state.lines[mat.y as usize];
let prefix = &line[0..mat.x as usize];
let extra = prefix.len() - prefix.chars().count();
let offset = (mat.x as u16) - (extra as u16);
let text = self.make_hint_text(mat.text);
let adjust = prefix.len() - prefix.chars().count();
let offset = (mat.x as u16) - (adjust as u16);
let text = &mat.text; //self.make_hint_text(mat.text);
print!(
"{goto}{background}{foregroud}{text}{resetf}{resetb}",
// Render just the match's text on top of existing content.
write!(
stdout,
"{goto}{bg_color}{fg_color}{text}{fg_reset}{bg_reset}",
goto = cursor::Goto(offset + 1, mat.y as u16 + 1),
foregroud = color::Fg(**selected_color),
background = color::Bg(**selected_background_color),
resetf = color::Fg(color::Reset),
resetb = color::Bg(color::Reset),
text = &text
);
fg_color = color::Fg(**text_fg_color),
bg_color = color::Bg(**text_bg_color),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset),
text = &text,
)
.unwrap();
// 2. Render the hint (e.g. ";k") on top of the text at the beginning or the end.
//
if let Some(ref hint) = mat.hint {
let extra_position = if self.position == "left" {
let extra_offset = if self.position == "left" {
0
} else {
text.len() - mat.hint.clone().unwrap().len()
text.len() - hint.len()
};
let text = self.make_hint_text(hint.as_str());
print!(
"{goto}{background}{foregroud}{text}{resetf}{resetb}",
goto = cursor::Goto(offset + extra_position as u16 + 1, mat.y as u16 + 1),
foregroud = color::Fg(*self.hint_foreground_color),
background = color::Bg(*self.hint_background_color),
resetf = color::Fg(color::Reset),
resetb = color::Bg(color::Reset),
text = &text
);
write!(
stdout,
"{goto}{bg_color}{fg_color}{hint}{fg_reset}{bg_reset}",
goto = cursor::Goto(offset + extra_offset as u16 + 1, mat.y as u16 + 1),
fg_color = color::Fg(*self.hint_foreground_color),
bg_color = color::Bg(*self.hint_background_color),
fg_reset = color::Fg(color::Reset),
bg_reset = color::Bg(color::Reset),
hint = hint,
)
.unwrap();
}
}
stdout.flush().unwrap();
}
/// Listen to keys entered on stdin
///
/// # 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, stdin: &mut dyn Read, stdout: &mut dyn Write) -> CaptureEvent {
if self.matches.is_empty() {
return CaptureEvent::Exit
return CaptureEvent::Exit;
}
let mut chosen = vec![];
@ -168,11 +195,23 @@ impl<'a> View<'a> {
self.render(stdout);
loop {
match stdin.keys().next() {
Some(key) => {
match key {
Ok(key) => {
match key {
// This is an option of a result of a key... Let's pop error cases first.
let next_key = stdin.keys().next();
if next_key.is_none() {
// Nothing in the buffer. Wait for a bit...
std::thread::sleep(std::time::Duration::from_millis(100));
continue;
}
let key_res = next_key.unwrap();
if let Err(err) = key_res {
// Termion not being able to read from stdin is an unrecoverable error.
panic!(err);
}
match key_res.unwrap() {
// Clears an ongoing multi-hint selection, or exit.
Key::Esc => {
if self.multi && !typed_hint.is_empty() {
typed_hint.clear();
@ -180,28 +219,27 @@ impl<'a> View<'a> {
break;
}
}
Key::Insert => match self.matches.iter().enumerate().find(|&h| h.0 == self.skip) {
Some(hm) => {
chosen.push((hm.1.text.to_string(), false));
// TODO: What does this do?
Key::Insert => match self.matches.iter().enumerate().find(|&(idx, _)| idx == self.skip) {
Some((_idx, mtch)) => {
chosen.push((mtch.text.to_string(), false));
if !self.multi {
return CaptureEvent::Hint(chosen);
}
}
_ => panic!("Match not found?"),
None => panic!("Match not found?"),
},
Key::Up => {
self.prev();
}
Key::Down => {
self.next();
}
Key::Left => {
self.prev();
}
Key::Right => {
self.next();
}
// Move focus to next/prev hint.
Key::Up => self.prev(),
Key::Down => self.next(),
Key::Left => self.prev(),
Key::Right => self.next(),
// Pressing space finalizes an ongoing multi-hint selection.
// Others characters attempt the corresponding hint.
Key::Char(ch) => {
if ch == ' ' && self.multi {
return CaptureEvent::Hint(chosen);
@ -210,13 +248,13 @@ impl<'a> View<'a> {
let key = ch.to_string();
let lower_key = key.to_lowercase();
typed_hint.push_str(lower_key.as_str());
typed_hint.push_str(&lower_key);
let selection = self.matches.iter().find(|mat| mat.hint == Some(typed_hint.clone()));
let selection = self.matches.iter().find(|&mtch| mtch.hint == Some(typed_hint.clone()));
match selection {
Some(mat) => {
chosen.push((mat.text.to_string(), key != lower_key));
Some(mtch) => {
chosen.push((mtch.text.to_string(), key != lower_key));
if self.multi {
typed_hint.clear();
@ -231,18 +269,9 @@ impl<'a> View<'a> {
}
}
}
_ => {
// Unknown key
}
}
}
Err(err) => panic!(err),
}
}
_ => {
// Nothing in the buffer. Wait for a bit...
std::thread::sleep(std::time::Duration::from_millis(100));
}
// Unknown keys are ignored.
_ => (),
}
self.render(stdout);
@ -294,11 +323,11 @@ mod tests {
hint_foreground_color: colors::get_color("default"),
};
let result = view.make_hint_text("a");
assert_eq!(result, "a".to_string());
// let result = view.make_hint_text("a");
// assert_eq!(result, "a".to_string());
view.contrast = true;
let result = view.make_hint_text("a");
assert_eq!(result, "[a]".to_string());
// view.contrast = true;
// let result = view.make_hint_text("a");
// assert_eq!(result, "[a]".to_string());
}
}