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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use crossterm::{
  cursor::{Hide, MoveLeft, MoveRight, MoveUp, Show},
  event::{self, Event, KeyCode, KeyEvent},
  execute,
  style::{Color, Print, ResetColor, SetForegroundColor},
  terminal::{disable_raw_mode, enable_raw_mode},
};
use unicode_segmentation::UnicodeSegmentation;

use std::io::stdout;
use std::{thread, time};

/// Displays a selection list in the terminal
///
/// The function will use the provided String array and list the items in the terminal.
/// The user can then make a selection from the list using the arrow keys.
/// The index of the provided array is returned as the selection.
///
/// This function will panic if it fails to write to the terminal.
pub fn select_from_list(list: &[String], selected: Option<usize>) -> usize {
  let mut selection = selected.unwrap_or(0);

  // get selection color
  let color = get_color();

  // hide cursor
  execute!(stdout(), Hide).expect("Failed to write hide cursor");

  // enable raw mode
  enable_raw_mode().expect("Failed to enable raw mode");

  loop {
    // write list
    write_list(&list, selection, color);

    // read user input
    if let Event::Key(KeyEvent { code, .. }) = event::read().expect("Failed to read event") {
      match code {
        KeyCode::Esc => {
          // show cursor
          execute!(stdout(), Show).expect("Failed to write show cursor");
          // disable raw mode
          disable_raw_mode().expect("Failed to enable raw mode");
          // exit application
          std::process::exit(0)
        }
        KeyCode::Enter => break,
        KeyCode::Up => {
          if selection > 0 {
            selection -= 1;
          }
        }
        KeyCode::Down => {
          if selection < (list.len() - 1) {
            selection += 1;
          }
        }
        _ => {}
      }
    }

    // clear list
    clear_list(&list);

    thread::sleep(time::Duration::from_millis(50));
  }

  // show cursor
  execute!(stdout(), Show).expect("Failed to write show cursor");
  // disable raw mode
  disable_raw_mode().expect("Failed to enable raw mode");

  selection
}

/// Displays a two column text in the terminal
///
/// The function will use the provided Strings and output them in two columns.
///
/// This function will panic if it fails to write to the terminal.
pub fn write_column(first: String, second: String, offset: Option<usize>) {
  // calculate offset
  let offset = offset.unwrap_or(20);
  let diff = offset - first.graphemes(true).count();

  execute!(stdout(), MoveLeft(1), Print(first)).expect("Failed to write first column");
  execute!(
    stdout(),
    MoveRight(diff as u16),
    Print(format!("{}\n\r", second))
  )
  .expect("Failed to write second column");
}

/// Displays the provided String array and current selection
fn write_list(list: &[String], selection: usize, color: Color) {
  // print the list out
  for (i, item) in list.iter().enumerate() {
    if i == selection {
      execute!(
        stdout(),
        SetForegroundColor(color),
        Print(format!("{}\n\r", item)),
        ResetColor
      )
      .expect("Failed to write list");
    } else {
      execute!(stdout(), ResetColor, Print(format!("{}\n\r", item))).expect("Failed to write list");
    }
  }

  // reset color
  execute!(stdout(), ResetColor).expect("Failed to write reset color");
}

/// Moves the terminal cursor back up to be able re-display the list
fn clear_list(list: &[String]) {
  // move cursor up
  for _ in list {
    execute!(stdout(), MoveUp(1)).expect("Failed to write cursor up");
  }
}

fn get_color() -> Color {
  let conf = crate::config::get_gut_config();

  let selected = match conf.get("color") {
    Some(color) => color.to_string(),
    None => "Red".to_string(),
  };

  if selected.contains("Black") {
    return Color::Black;
  } else if selected.contains("Red") {
    return Color::Red;
  } else if selected.contains("Green") {
    return Color::Green;
  } else if selected.contains("Yellow") {
    return Color::Yellow;
  } else if selected.contains("Blue") {
    return Color::Blue;
  } else if selected.contains("Magenta") {
    return Color::Magenta;
  } else if selected.contains("Cyan") {
    return Color::Cyan;
  } else if selected.contains("White") {
    return Color::White;
  } else {
    return Color::Red;
  }
}