mirror of
https://github.com/TECHNOFAB11/tmux-copyrat.git
synced 2025-12-13 00:20:08 +01:00
refactor(clean): better names
This commit is contained in:
parent
d8386615a3
commit
cab6ff3418
3 changed files with 43 additions and 85 deletions
14
src/lib.rs
14
src/lib.rs
|
|
@ -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>()?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
src/main.rs
14
src/main.rs
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
100
src/view.rs
100
src/view.rs
|
|
@ -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,68 +404,39 @@ 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() {
|
break;
|
||||||
typed_hint.clear();
|
|
||||||
} else {
|
|
||||||
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(),
|
||||||
event::Key::Down => self.next(),
|
event::Key::Down => self.next(),
|
||||||
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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue