animaterm 0.2.9

An easy to use terminal user interface library with keyboard macros support.
Documentation
use crate::helpers::map_bytes_to_key;

use super::key::Key;
use std::collections::HashMap;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;
use std::time::{Duration, Instant};

#[derive(Clone)]
pub struct MacroSequence(bool, Vec<(Key, Duration)>);
impl MacroSequence {
    pub fn empty() -> Self {
        MacroSequence(false, vec![])
    }
    pub fn new(looped: bool, sequence: Vec<(Key, Duration)>) -> Self {
        MacroSequence(looped, sequence)
    }
    pub fn from_text(text: String, delay: Duration, looped: bool) -> Self {
        let mut sequence = Vec::with_capacity(text.len());
        for ch in text.chars() {
            if let Some(key) = map_bytes_to_key(vec![ch as u8]) {
                sequence.push((key, delay))
            }
        }
        MacroSequence(looped, sequence)
    }
}

pub struct Macros {
    pub enabled: bool,
    record_key: Option<Key>,
    pub running: Option<Key>,
    pub recording: Option<(Option<Key>, MacroSequence, Instant)>,
    macros: HashMap<Key, MacroSequence>,
    pub key_recv: Receiver<Key>,
    terminate_send: Sender<()>,
}

impl Macros {
    pub fn new(macros: Option<Vec<(Key, MacroSequence)>>) -> Self {
        let (_key_send, key_recv) = channel();
        let (terminate_send, _recv) = channel();
        if let Some(macros_vec) = macros {
            let mut macros: HashMap<Key, MacroSequence> = HashMap::with_capacity(macros_vec.len());
            let mut macros_iter = macros_vec.into_iter();
            let (macro_key, MacroSequence(looped, key_sequence)) = macros_iter.next().unwrap();
            let record_key = if key_sequence.is_empty() {
                Some(macro_key)
            } else {
                macros.insert(macro_key, MacroSequence::new(looped, key_sequence));
                None
            };
            for (macro_trigger, MacroSequence(looped, sequence)) in macros_iter {
                macros.insert(macro_trigger, MacroSequence::new(looped, sequence));
            }
            Macros {
                enabled: true,
                record_key,
                running: None,
                recording: None,
                macros,
                key_recv,
                terminate_send,
            }
        } else {
            Macros {
                enabled: false,
                record_key: None,
                running: None,
                recording: None,
                macros: HashMap::new(),
                key_recv,
                terminate_send,
            }
        }
    }

    pub fn record(&mut self, key: &Key) {
        if self.recording.is_none() {
            if self.is_record_key(key) {
                self.recording = Some((None, MacroSequence::empty(), Instant::now()))
            } else {
                println!("Unexpected key upon recording start: {}", key);
            }
        } else if let Some((rec_key, MacroSequence(looped, mut sequence), timestamp)) =
            self.recording.take()
        {
            if self.is_record_key(key) {
                if rec_key.is_none() {
                    self.recording = Some((
                        rec_key,
                        MacroSequence::new(!looped, sequence),
                        Instant::now(),
                    ));
                } else {
                    if sequence.is_empty() {
                        let _ = self.macros.remove(&rec_key.unwrap());
                    } else {
                        self.macros
                            .insert(rec_key.unwrap(), MacroSequence::new(looped, sequence));
                    }
                    self.recording = None;
                }
            } else if rec_key.is_none() {
                self.recording = Some((
                    Some(key.clone()),
                    MacroSequence::new(looped, sequence),
                    Instant::now(),
                ));
            } else {
                // We apply delay to previous key
                if let Some((old_key, _timestamp)) = sequence.pop() {
                    sequence.push((old_key, timestamp.elapsed()));
                }
                sequence.push((key.clone(), timestamp.elapsed()));
                self.recording = Some((
                    rec_key,
                    MacroSequence::new(looped, sequence),
                    Instant::now(),
                ));
            }
        }
    }

    pub fn run(&mut self, key: &Key) -> bool {
        if self.macros.contains_key(key) {
            match self.running {
                None => {}
                Some(ref running_key) => {
                    if running_key == key {
                        self.stop();
                        return true;
                    }
                }
            }
            self.stop();
            self.running = Some(key.clone());
            let (key_send, key_recv) = channel();
            self.key_recv = key_recv;
            let MacroSequence(looped, sequence) = self.macros.get(key).cloned().unwrap();
            let (terminate_send, terminate_recv) = channel();
            self.terminate_send = terminate_send;
            thread::spawn(move || {
                if looped {
                    'external: loop {
                        for (key, sleep_time) in sequence.iter() {
                            let terminate_result = terminate_recv.try_recv();
                            match terminate_result {
                                Ok(()) => break 'external,
                                Err(std::sync::mpsc::TryRecvError::Disconnected) => break 'external,
                                Err(_) => {}
                            }
                            if key_send.send(key.clone()).is_err() {
                                break;
                            }
                            thread::sleep(*sleep_time);
                        }
                    }
                } else {
                    for (key, sleep_time) in sequence.iter() {
                        let terminate_result = terminate_recv.try_recv();
                        match terminate_result {
                            Ok(()) => break,
                            Err(std::sync::mpsc::TryRecvError::Disconnected) => break,
                            Err(_) => {}
                        }
                        if key_send.send(key.clone()).is_err() {
                            break;
                        }
                        thread::sleep(*sleep_time);
                    }
                    drop(terminate_recv);
                }
            });
            true
        } else {
            false
        }
    }
    pub fn stop(&mut self) {
        let _ = self.terminate_send.send(());
        self.running = None;
    }
    pub fn is_record_key(&self, key: &Key) -> bool {
        match &self.record_key {
            None => false,
            Some(r_key) => r_key == key,
        }
    }
}