gut-lib 0.1.1

Helper library that provides common functionality for Gut
Documentation
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;
  }
}