mirror of
https://github.com/TECHNOFAB11/tmux-copyrat.git
synced 2025-12-13 08:30:07 +01:00
chore: a new start
This commit is contained in:
commit
34d0bb5a35
21 changed files with 2319 additions and 0 deletions
304
src/view.rs
Normal file
304
src/view.rs
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
use super::*;
|
||||
use std::char;
|
||||
use std::io::{stdout, Read, Write};
|
||||
use termion::async_stdin;
|
||||
use termion::event::Key;
|
||||
use termion::input::TermRead;
|
||||
use termion::raw::IntoRawMode;
|
||||
use termion::screen::AlternateScreen;
|
||||
use termion::{color, cursor};
|
||||
|
||||
pub struct View<'a> {
|
||||
state: &'a mut state::State<'a>,
|
||||
skip: usize,
|
||||
multi: bool,
|
||||
contrast: bool,
|
||||
position: &'a str,
|
||||
matches: Vec<state::Match<'a>>,
|
||||
select_foreground_color: Box<&'a dyn color::Color>,
|
||||
select_background_color: Box<&'a dyn color::Color>,
|
||||
foreground_color: Box<&'a dyn color::Color>,
|
||||
background_color: Box<&'a dyn color::Color>,
|
||||
hint_background_color: Box<&'a dyn color::Color>,
|
||||
hint_foreground_color: Box<&'a dyn color::Color>,
|
||||
}
|
||||
|
||||
enum CaptureEvent {
|
||||
Exit,
|
||||
Hint(Vec<(String, bool)>),
|
||||
}
|
||||
|
||||
impl<'a> View<'a> {
|
||||
pub fn new(
|
||||
state: &'a mut state::State<'a>,
|
||||
multi: bool,
|
||||
reverse: bool,
|
||||
unique: bool,
|
||||
contrast: bool,
|
||||
position: &'a str,
|
||||
select_foreground_color: Box<&'a dyn color::Color>,
|
||||
select_background_color: Box<&'a dyn color::Color>,
|
||||
foreground_color: Box<&'a dyn color::Color>,
|
||||
background_color: Box<&'a dyn color::Color>,
|
||||
hint_foreground_color: Box<&'a dyn color::Color>,
|
||||
hint_background_color: Box<&'a dyn color::Color>,
|
||||
) -> View<'a> {
|
||||
let matches = state.matches(reverse, unique);
|
||||
let skip = if reverse { matches.len() - 1 } else { 0 };
|
||||
|
||||
View {
|
||||
state,
|
||||
skip,
|
||||
multi,
|
||||
contrast,
|
||||
position,
|
||||
matches,
|
||||
select_foreground_color,
|
||||
select_background_color,
|
||||
foreground_color,
|
||||
background_color,
|
||||
hint_foreground_color,
|
||||
hint_background_color,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prev(&mut self) {
|
||||
if self.skip > 0 {
|
||||
self.skip -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self, stdout: &mut dyn Write) -> () {
|
||||
write!(stdout, "{}", cursor::Hide).unwrap();
|
||||
|
||||
for (index, line) in self.state.lines.iter().enumerate() {
|
||||
let clean = 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);
|
||||
}
|
||||
}
|
||||
|
||||
let selected = self.matches.get(self.skip);
|
||||
|
||||
for mat in self.matches.iter() {
|
||||
let selected_color = if selected == Some(mat) {
|
||||
&self.select_foreground_color
|
||||
} else {
|
||||
&self.foreground_color
|
||||
};
|
||||
let selected_background_color = if selected == Some(mat) {
|
||||
&self.select_background_color
|
||||
} else {
|
||||
&self.background_color
|
||||
};
|
||||
|
||||
// Find long utf sequences and extract it from mat.x
|
||||
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);
|
||||
|
||||
print!(
|
||||
"{goto}{background}{foregroud}{text}{resetf}{resetb}",
|
||||
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
|
||||
);
|
||||
|
||||
if let Some(ref hint) = mat.hint {
|
||||
let extra_position = if self.position == "left" {
|
||||
0
|
||||
} else {
|
||||
text.len() - mat.hint.clone().unwrap().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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
|
||||
fn listen(&mut self, stdin: &mut dyn Read, stdout: &mut dyn Write) -> CaptureEvent {
|
||||
if self.matches.is_empty() {
|
||||
return CaptureEvent::Exit
|
||||
}
|
||||
|
||||
let mut chosen = vec![];
|
||||
let mut typed_hint: String = "".to_owned();
|
||||
let longest_hint = self
|
||||
.matches
|
||||
.iter()
|
||||
.filter_map(|m| m.hint.clone())
|
||||
.max_by(|x, y| x.len().cmp(&y.len()))
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
self.render(stdout);
|
||||
|
||||
loop {
|
||||
match stdin.keys().next() {
|
||||
Some(key) => {
|
||||
match key {
|
||||
Ok(key) => {
|
||||
match key {
|
||||
Key::Esc => {
|
||||
if self.multi && !typed_hint.is_empty() {
|
||||
typed_hint.clear();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Key::Insert => match self.matches.iter().enumerate().find(|&h| h.0 == self.skip) {
|
||||
Some(hm) => {
|
||||
chosen.push((hm.1.text.to_string(), false));
|
||||
|
||||
if !self.multi {
|
||||
return CaptureEvent::Hint(chosen);
|
||||
}
|
||||
}
|
||||
_ => panic!("Match not found?"),
|
||||
},
|
||||
Key::Up => {
|
||||
self.prev();
|
||||
}
|
||||
Key::Down => {
|
||||
self.next();
|
||||
}
|
||||
Key::Left => {
|
||||
self.prev();
|
||||
}
|
||||
Key::Right => {
|
||||
self.next();
|
||||
}
|
||||
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.as_str());
|
||||
|
||||
let selection = self.matches.iter().find(|mat| mat.hint == Some(typed_hint.clone()));
|
||||
|
||||
match selection {
|
||||
Some(mat) => {
|
||||
chosen.push((mat.text.to_string(), key != lower_key));
|
||||
|
||||
if self.multi {
|
||||
typed_hint.clear();
|
||||
} else {
|
||||
return CaptureEvent::Hint(chosen);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if !self.multi && typed_hint.len() >= longest_hint.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Unknown key
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => panic!(err),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Nothing in the buffer. Wait for a bit...
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
||||
self.render(stdout);
|
||||
}
|
||||
|
||||
CaptureEvent::Exit
|
||||
}
|
||||
|
||||
pub fn present(&mut self) -> Vec<(String, bool)> {
|
||||
let mut stdin = async_stdin();
|
||||
let mut stdout = AlternateScreen::from(stdout().into_raw_mode().unwrap());
|
||||
|
||||
let hints = match self.listen(&mut stdin, &mut stdout) {
|
||||
CaptureEvent::Exit => vec![],
|
||||
CaptureEvent::Hint(chosen) => chosen,
|
||||
};
|
||||
|
||||
write!(stdout, "{}", cursor::Show).unwrap();
|
||||
|
||||
hints
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn split(output: &str) -> Vec<&str> {
|
||||
output.split("\n").collect::<Vec<&str>>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hint_text() {
|
||||
let lines = split("lorem 127.0.0.1 lorem");
|
||||
let custom = [].to_vec();
|
||||
let mut state = state::State::new(&lines, "abcd", &custom);
|
||||
let mut view = View {
|
||||
state: &mut state,
|
||||
skip: 0,
|
||||
multi: false,
|
||||
contrast: false,
|
||||
position: &"",
|
||||
matches: vec![],
|
||||
select_foreground_color: colors::get_color("default"),
|
||||
select_background_color: colors::get_color("default"),
|
||||
foreground_color: colors::get_color("default"),
|
||||
background_color: colors::get_color("default"),
|
||||
hint_background_color: colors::get_color("default"),
|
||||
hint_foreground_color: colors::get_color("default"),
|
||||
};
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue