dwfv 0.5.0

A simple digital waveform viewer with vi-like key bindings
Documentation
// SPDX-License-Identifier: MIT
use std::collections::VecDeque;
use std::io;
use termion::event::Event as RawEvent;
use termion::event::{Key, MouseButton, MouseEvent};
use termion::input::TermRead;

const BUFFER_MAX_SIZE: usize = 8;

#[derive(Copy, Clone, Debug)]
pub enum SearchTarget {
    None,
    Signal,
    Event,
}

#[allow(clippy::enum_variant_names)]
#[derive(Clone)]
pub enum Event {
    None,
    Quit,
    Left,
    Right,
    Up,
    Down,
    ZoomIn,
    ZoomOut,
    ZoomFit,
    CenterWindow,
    GotoTop,
    GotoLast,
    GotoNextRisingEdge,
    GotoPreviousRisingEdge,
    GotoNextFallingEdge,
    GotoFirstEvent,
    GotoLastEvent,
    GotoZero,
    StartVisualMode,
    FitToSelection,
    StopVisualMode,
    Edit,
    PageDown,
    PageUp,
    PasteAfter,
    PasteBefore,
    Yank,
    Delete,
    Search(SearchTarget, String),
    SearchNext,
    SearchPrev,
    SetCursorVertical(u16),
    SetCursorHorizontal(u16),
    Undo,
    Redo,
    ShowClipboard,
}

pub enum InputMode {
    Command,
    Visual,
    Search(SearchTarget),
}

pub struct Events {
    buffer: String,
    previous_buffer: String,
    events: VecDeque<Event>,
    mode: InputMode,
}

type Command = &'static dyn Fn(&mut Events) -> Event;

impl Events {
    pub fn new() -> Events {
        Events {
            buffer: String::new(),
            previous_buffer: String::new(),
            events: VecDeque::new(),
            mode: InputMode::Command,
        }
    }

    pub fn in_visual_mode(&self) -> bool {
        matches!(self.mode, InputMode::Visual)
    }

    pub fn in_search_mode(&self) -> bool {
        matches!(self.mode, InputMode::Search(_))
    }

    pub fn get_search_target(&self) -> SearchTarget {
        if let InputMode::Search(target) = self.mode {
            target
        } else {
            SearchTarget::None
        }
    }

    fn clear_buffer(&mut self) {
        self.previous_buffer.clear();
        self.previous_buffer.push_str(&self.buffer);
        self.buffer.clear()
    }

    const CMDS: [(&'static str, Command); 35] = [
        ("j", &|_| Event::Down),
        ("k", &|_| Event::Up),
        ("l", &|_| Event::Right),
        ("h", &|_| Event::Left),
        ("q", &|_| Event::Quit),
        ("-", &|_| Event::ZoomOut),
        ("+", &|_| Event::ZoomIn),
        ("=", &|_| Event::ZoomFit),
        ("zo", &|_| Event::ZoomOut),
        ("zi", &|_| Event::ZoomIn),
        ("zc", &|_| Event::ZoomFit),
        ("w", &|_| Event::GotoNextRisingEdge),
        ("b", &|_| Event::GotoPreviousRisingEdge),
        ("e", &|_| Event::GotoNextFallingEdge),
        ("zz", &|_| Event::CenterWindow),
        ("gg", &|_| Event::GotoTop),
        ("G", &|_| Event::GotoLast),
        ("0", &|_| Event::GotoZero),
        ("^", &|_| Event::GotoFirstEvent),
        ("$", &|_| Event::GotoLastEvent),
        ("o", &|_| Event::Edit),
        ("dd", &|_| Event::Delete),
        ("yy", &|_| Event::Yank),
        ("p", &|_| Event::PasteAfter),
        ("P", &|_| Event::PasteBefore),
        ("N", &|_| Event::SearchPrev),
        ("n", &|_| Event::SearchNext),
        ("u", &|_| Event::Undo),
        ("r", &|_| Event::Redo),
        ("c", &|_| Event::ShowClipboard),
        ("v", &|evt| {
            if let InputMode::Visual = evt.mode {
                evt.mode = InputMode::Command;
                Event::StopVisualMode
            } else {
                evt.mode = InputMode::Visual;
                Event::StartVisualMode
            }
        }),
        (" ", &|evt| {
            if let InputMode::Visual = evt.mode {
                evt.mode = InputMode::Command;
                Event::StopVisualMode
            } else {
                evt.mode = InputMode::Visual;
                Event::StartVisualMode
            }
        }),
        ("/", &|evt| {
            evt.mode = InputMode::Search(SearchTarget::Signal);
            evt.buffer.clear();
            Event::None
        }),
        ("f", &|evt| {
            evt.mode = InputMode::Search(SearchTarget::Event);
            evt.buffer.clear();
            Event::None
        }),
        (".", &|evt| {
            evt.buffer.clear();
            evt.buffer.push_str(&evt.previous_buffer);
            let _ = evt.parse_buffer();
            Event::None
        }),
    ];

    fn parse_buffer(&mut self) -> Result<(), ()> {
        let end = self
            .buffer
            .chars()
            .position(|ch| !ch.is_numeric())
            .ok_or(())?;
        let repeat = self.buffer[..end].parse().unwrap_or(1);
        let cmd_buff = self.buffer[end..].to_string();

        let mut cmd = Event::None;
        for (name, action) in Events::CMDS.iter() {
            if cmd_buff.contains(name) {
                cmd = action(self)
            }
        }

        if let Event::None = cmd {
            Err(())
        } else {
            for _ in 0..repeat {
                self.events.push_back(cmd.clone())
            }
            Ok(())
        }
    }

    pub fn update(&mut self) {
        let evt = io::stdin().events().next();
        if let Some(Ok(evt)) = evt {
            match evt {
                RawEvent::Key(key) => match key {
                    Key::Up => {
                        if let InputMode::Search(_) = self.mode {
                        } else {
                            self.events.push_back(Event::Up);
                            self.clear_buffer()
                        }
                    }
                    Key::Down => {
                        if let InputMode::Search(_) = self.mode {
                        } else {
                            self.events.push_back(Event::Down);
                            self.clear_buffer()
                        }
                    }
                    Key::Left => {
                        if let InputMode::Search(_) = self.mode {
                        } else {
                            self.events.push_back(Event::Left);
                            self.clear_buffer()
                        }
                    }
                    Key::Right => {
                        if let InputMode::Search(_) = self.mode {
                        } else {
                            self.events.push_back(Event::Right);
                            self.clear_buffer()
                        }
                    }
                    Key::PageUp => {
                        if let InputMode::Search(_) = self.mode {
                        } else {
                            self.events.push_back(Event::PageUp);
                            self.clear_buffer()
                        }
                    }
                    Key::PageDown => {
                        if let InputMode::Search(_) = self.mode {
                        } else {
                            self.events.push_back(Event::PageDown);
                            self.clear_buffer()
                        }
                    }
                    Key::Delete => {
                        if let InputMode::Search(_) = self.mode {
                        } else {
                            self.events.push_back(Event::Delete);
                            self.clear_buffer()
                        }
                    }
                    Key::Home => {
                        if let InputMode::Search(_) = self.mode {
                        } else {
                            self.events.push_back(Event::GotoFirstEvent);
                            self.clear_buffer()
                        }
                    }
                    Key::End => {
                        if let InputMode::Search(_) = self.mode {
                        } else {
                            self.events.push_back(Event::GotoLastEvent);
                            self.clear_buffer()
                        }
                    }
                    Key::Esc => {
                        self.mode = InputMode::Command;
                        self.clear_buffer()
                    }
                    Key::Backspace => {
                        if let InputMode::Search(_) = self.mode {
                            self.buffer.pop();
                        }
                    }
                    Key::Char(c) => {
                        if c == '\n' {
                            match self.mode {
                                InputMode::Visual => {
                                    self.mode = InputMode::Command;
                                    self.events.push_back(Event::FitToSelection)
                                }
                                InputMode::Command => {
                                    self.mode = InputMode::Visual;
                                    self.events.push_back(Event::StartVisualMode)
                                }
                                InputMode::Search(target) => {
                                    self.mode = InputMode::Command;
                                    self.events
                                        .push_back(Event::Search(target, self.buffer.clone()))
                                }
                            }
                            self.buffer.clear();
                        } else {
                            self.buffer.push(c);
                            match self.mode {
                                InputMode::Command | InputMode::Visual => {
                                    if self.buffer.len() >= BUFFER_MAX_SIZE {
                                        self.buffer.clear()
                                    }
                                }
                                _ => {}
                            }
                        }
                    }
                    _ => {}
                },
                RawEvent::Mouse(m) => {
                    if let InputMode::Search(_) = self.mode {
                    } else {
                        match m {
                            MouseEvent::Press(button, x, y) => match button {
                                MouseButton::WheelUp => {
                                    self.events.push_back(Event::ZoomIn);
                                    self.clear_buffer()
                                }
                                MouseButton::WheelDown => {
                                    self.events.push_back(Event::ZoomOut);
                                    self.clear_buffer()
                                }
                                MouseButton::Left => {
                                    self.events.push_back(Event::SetCursorHorizontal(x));
                                    self.events.push_back(Event::SetCursorVertical(y));
                                    self.clear_buffer()
                                }
                                MouseButton::Middle => {
                                    self.events.push_back(Event::SetCursorHorizontal(x));
                                    self.events.push_back(Event::SetCursorVertical(y));
                                    self.events.push_back(Event::PasteBefore);
                                    self.clear_buffer()
                                }
                                MouseButton::Right => {
                                    self.events.push_back(Event::SetCursorHorizontal(x));
                                    self.events.push_back(Event::SetCursorVertical(y));
                                    self.events.push_back(Event::Yank);
                                    self.clear_buffer()
                                }
                                _ => (),
                            },
                            MouseEvent::Release(x, _) => {
                                if let InputMode::Visual = self.mode {
                                    self.mode = InputMode::Command;
                                    self.events.push_back(Event::SetCursorHorizontal(x));
                                    self.events.push_back(Event::FitToSelection);
                                    self.clear_buffer()
                                }
                            }
                            MouseEvent::Hold(x, _) => {
                                if let InputMode::Visual = self.mode {
                                } else {
                                    self.mode = InputMode::Visual;
                                    self.events.push_back(Event::StartVisualMode);
                                }
                                self.events.push_back(Event::SetCursorHorizontal(x));
                                self.clear_buffer()
                            }
                        }
                    }
                }
                _ => {}
            }
        }
        if let InputMode::Search(_) = self.mode {
        } else if let Ok(()) = self.parse_buffer() {
            self.clear_buffer()
        }
    }

    pub fn get_event(&mut self) -> Event {
        if let Some(evt) = self.events.pop_front() {
            evt
        } else {
            Event::None
        }
    }

    pub fn get_buffer(&self) -> &str {
        &self.buffer
    }
}