amen 0.0.3

A CLI menu tool utilizing abbreviations
Documentation
use amen::error::AmenError;
use amen::layout::Layout;
use amen::run_amen;
use nix::unistd::{dup, dup2};
use std::error::Error;
use std::fs::File;
use std::io::Write;
use std::os::unix::io::AsRawFd;
use std::process;
use termion::cursor::DetectCursorPos;
use termion::raw::IntoRawMode;
use termion::{cursor, get_tty, terminal_size};

fn main() -> Result<(), Box<dyn Error>> {
    let phrases = read_input_phrases()?;
    let phrases = &*phrases.iter().map(AsRef::as_ref).collect::<Vec<_>>();

    let stdout_fd = std::io::stdout().as_raw_fd();
    let prev_stdout = dup(stdout_fd)?;
    dup2(File::create("/dev/tty")?.as_raw_fd(), stdout_fd)?;

    let mut tty = &get_tty()?;
    let terminal = tty.into_raw_mode()?;

    let term_size = terminal_size()?;
    let mut layout = Layout::new(phrases, term_size);

    let remaining_lines = term_size.1 - tty.cursor_pos()?.1;
    let scroll_amount = layout.size.1 - remaining_lines.min(layout.size.1 - 1);
    layout.offset((0, tty.cursor_pos()?.1 - scroll_amount));

    write!(tty, "{}{}", cursor::Hide, cursor::Save)?;
    let result = run_amen(tty, terminal, phrases, &layout);
    write!(tty, "{}{}", cursor::Restore, cursor::Show)?;

    dup2(prev_stdout, stdout_fd)?;

    match result {
        Ok(phrase) => {
            println!("{}", phrase);
            Ok(())
        }
        Err(AmenError::NoneSelected) => process::exit(1),
        Err(AmenError::Internal(error)) => Err(error),
    }
}

fn read_input_phrases() -> Result<Vec<String>, Box<dyn Error>> {
    let mut phrases = vec![];
    let mut line_buffer = String::new();
    while let Ok(n) = std::io::stdin().read_line(&mut line_buffer) {
        if n == 0 {
            break;
        }
        phrases.push(line_buffer.trim_end().to_string());
        line_buffer.clear();
    }

    if phrases.is_empty() {
        return Ok(vec![]);
    }

    Ok(phrases)
}