1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use super::node_module;
use serde_json::Result;
use std::io::{stdin, stdout, Write};
use termion::event::{Event, Key};
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use termion::{color, style};

///
/// Function constructs TUI
///
fn build(
  stdout: &mut AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>,
  row: &usize,
  dependency_list: &[&node_module::NodeModule],
) -> Result<()> {
  for (i, node_module) in dependency_list.into_iter().enumerate() {
    let line_number = i + 1_usize;
    let is_current_line = (*row as u32) == (line_number as u32);

    write!(
      stdout,
      "{}",
      termion::cursor::Goto(1, (i as u16) * 2_u16 + 1_u16)
    )
    .expect("set the cursor position");

    if node_module.selected.get() {
      write!(stdout, "{}◉{}", color::Fg(color::Green), style::Reset)
        .expect("write the checkbox icon");
    } else {
      write!(stdout, "◯").expect("write the checkbox icon");
    }

    write!(stdout, " ").expect("just write a space for layout");

    write!(stdout, "{}", node_module.r#type.short).expect("write the type of the node_module");

    write!(stdout, " ").expect("just write a space for layout");

    if is_current_line {
      write!(stdout, "{}", termion::style::Underline).expect("emphasize the current line");
    }

    write!(stdout, "{}@{}", node_module.name, node_module.version)
      .expect("write a the name and version of a node module");

    if is_current_line {
      write!(stdout, "{}", termion::style::NoUnderline).expect("reset emphasized");
    }

    write!(stdout, "\r\n  ").expect("new line and indent");

    write!(
      stdout,
      "{}{}{}",
      color::Fg(color::LightBlack),
      node_module.path.to_str().unwrap(),
      termion::style::Reset
    )
    .expect("write path in gray");
  }

  let size = termion::terminal_size().expect("get the terminal size");
  write!(
    stdout,
    "{}{}",
    termion::cursor::Goto(size.0, size.1),
    termion::cursor::Hide
  )
  .expect("hide the cursor");
  stdout.flush().unwrap();
  Ok(())
}

///
/// Function renders TUI
///
pub fn render(dependency_list: &[&node_module::NodeModule]) {
  let stdin = stdin();
  let mut stdout = AlternateScreen::from(stdout().into_raw_mode().unwrap());
  let mut row: usize = 1;
  write!(stdout, "{}", termion::clear::All).expect("clear terminal");
  build(&mut stdout, &row, &dependency_list).expect("show dependencies checkboxes");
  stdout.flush().unwrap();

  for c in stdin.events() {
    let event = c.unwrap();

    match event {
      Event::Key(Key::Char('q')) | Event::Key(Key::Ctrl('c')) => break,
      Event::Key(Key::Char('k')) => {
        if row > 1 {
          row -= 1;
        }
      }
      Event::Key(Key::Char('j')) => {
        if row < dependency_list.len() {
          row += 1;
        }
      }
      // space
      Event::Key(Key::Char(' ')) => {
        dependency_list
          .get(row - 1_usize)
          .map(|&node_module| node_module.selected.set(!node_module.selected.get()));
      }
      // enter
      Event::Key(Key::Char('\n')) => {
        break;
      }
      Event::Key(_) | Event::Unsupported(_) | Event::Mouse(_) => {}
    }

    build(&mut stdout, &row, &dependency_list).expect("rendering");
  }
}