memos-cli-ui 0.3.0

A command-line tool for Memos.
Documentation
mod detail;
mod list;

use anyhow::{Error, Result};
use crossterm::event::{self, KeyCode, KeyEventKind};
use memos_api::models::V1Memo;
use ratatui::prelude::*;
use ratatui::DefaultTerminal;
use std::cell::RefCell;
use std::{cmp, io};

use self::detail::Detail;
use self::list::List;

pub struct ListView<'a> {
    memos: &'a Vec<V1Memo>,
    current_index: RefCell<usize>,
}

impl<'a> ListView<'a> {
    pub fn try_new(value: &'a Vec<V1Memo>) -> Result<Self> {
        if value.len() < 1 {
            return Err(Error::msg("List is empty."));
        }

        let list_view = ListView {
            memos: value,
            current_index: 0.into(),
        };

        Ok(list_view)
    }

    pub fn draw(&self) -> io::Result<()> {
        let mut terminal = ratatui::init();

        terminal.clear()?;

        let result = self.run(terminal);

        ratatui::restore();

        result
    }

    fn run(&self, mut terminal: DefaultTerminal) -> io::Result<()> {
        loop {
            terminal.draw(|frame| {
                let layout = Layout::default()
                    .constraints([Constraint::Min(2)])
                    .horizontal_margin(20)
                    .vertical_margin(10)
                    .direction(Direction::Horizontal)
                    .constraints(vec![Constraint::Percentage(25), Constraint::Percentage(50)])
                    .split(frame.area());

                let list = List::new(&self);
                let detail = Detail::new(&self);

                frame.render_widget(list, layout[0]);
                frame.render_widget(detail, layout[1]);
            })?;

            if let event::Event::Key(key) = event::read()? {
                match (key.kind, key.code) {
                    (KeyEventKind::Press, KeyCode::Char('j')) => {
                        let current_index = *self.current_index.borrow();
                        let current_index = current_index.saturating_add(1);
                        let current_index = cmp::min(self.memos.len() - 1, current_index);
                        self.current_index.replace(current_index);
                    }
                    (KeyEventKind::Press, KeyCode::Char('k')) => {
                        let current_index = *self.current_index.borrow();
                        let current_index = current_index.saturating_sub(1);
                        self.current_index.replace(current_index);
                    }
                    (KeyEventKind::Press, KeyCode::Char('q')) => return Ok(()),
                    _ => (),
                }
            }
        }
    }

    fn get_current_memo(&self) -> &V1Memo {
        let current_index = *self.current_index.borrow();
        &self.memos[current_index]
    }
}