Skip to content

Commit

Permalink
json editor part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
Kacperacy committed Apr 21, 2024
1 parent 2f5341f commit 1b030d8
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 167 deletions.
32 changes: 32 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ edition = "2021"
color-eyre = "0.6.3"
crossterm = "0.27.0"
ratatui = "0.26.2"
serde_json = "1.0.116"
19 changes: 19 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::collections::HashMap;

use serde_json::Result;

pub enum CurrentScreen {
Main,
Editing,
Expand Down Expand Up @@ -38,4 +40,21 @@ impl App {
self.value_input = String::new();
self.currently_editing = None;
}

pub fn toggle_edititng(&mut self) {
if let Some(edit_mode) = &self.currently_editing {
match edit_mode {
CurrentlyEditing::Key => self.currently_editing = Some(CurrentlyEditing::Value),
CurrentlyEditing::Value => self.currently_editing = Some(CurrentlyEditing::Key),
};
} else {
self.currently_editing = Some(CurrentlyEditing::Key);
}
}

pub fn print_json(&self) -> Result<()> {
let output = serde_json::to_string(&self.pairs)?;
println!("{}", output);
Ok(())
}
}
208 changes: 41 additions & 167 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,178 +1,52 @@
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
use ratatui::{
prelude::*,
symbols::border,
widgets::{block::*, *},
};

use color_eyre::{
eyre::{bail, Ok, WrapErr},
Result,
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use crossterm::execute;
use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use ratatui::backend::CrosstermBackend;
use ratatui::Terminal;
use std::error::Error;
use std::io;

mod app;
mod errors;
mod tui;

fn main() -> Result<()> {
errors::install_hooks()?;
let mut terminal = tui::init()?;
let app_result = App::default().run(&mut terminal);
tui::restore()?;
app_result
}

#[derive(Debug, Default)]
pub struct App {
counter: u8,
exit: bool,
}

impl App {
pub fn run(&mut self, terminal: &mut tui::Tui) -> Result<()> {
while !self.exit {
terminal.draw(|frame| self.render_frame(frame))?;
self.handle_events().wrap_err("handle events failed")?;
}
Ok(())
}

fn render_frame(&self, frame: &mut Frame) {
frame.render_widget(self, frame.size());
}

fn handle_events(&mut self) -> Result<()> {
match event::read()? {
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => self
.handle_key_event(key_event)
.wrap_err_with(|| format!("handling key event failed:\n{key_event:#?}")),
_ => Ok(()),
}
}

fn handle_key_event(&mut self, key_event: KeyEvent) -> Result<()> {
match key_event.code {
KeyCode::Char('q') => self.exit(),
KeyCode::Left => self.decrement_counter()?,
KeyCode::Right => self.increment_counter()?,
_ => {}
}
Ok(())
}

fn exit(&mut self) {
self.exit = true;
}

fn increment_counter(&mut self) -> Result<()> {
self.counter += 1;
if self.counter > 2 {
bail!("counter overflow!");
use app::App;

fn main() -> Result<(), Box<dyn Error>> {
enable_raw_mode()?;
let mut stderr = io::stderr();
execute!(stderr, EnterAlternateScreen, EnableMouseCapture)?;

let backend = CrosstermBackend::new(stderr);
let mut terminal = Terminal::new(backend)?;

let mut app = App::new();
let res = run_app(&mut terminal, &mut app);

disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;

if let Ok(do_print) = res {
if do_print {
app.print_json()?;
}
Ok(())
}

fn decrement_counter(&mut self) -> Result<()> {
self.counter -= 1;
Ok(())
} else if let Err(err) = res {
println!("{err:?}");
}
}

impl Widget for &App {
fn render(self, area: Rect, buf: &mut Buffer) {
let title = Title::from(" Counter App Tutorial ".bold());
let instructions = Title::from(Line::from(vec![
" Decrement ".into(),
"<Left>".blue().bold(),
" Increment ".into(),
"<Right>".blue().bold(),
" Quit ".into(),
"<Q> ".blue().bold(),
]));
let block = Block::default()
.title(title.alignment(Alignment::Center))
.title(
instructions
.alignment(Alignment::Center)
.position(Position::Bottom),
)
.borders(Borders::ALL)
.border_set(border::THICK);

let counter_text = Text::from(vec![Line::from(vec![
"Value: ".into(),
self.counter.to_string().yellow(),
])]);

Paragraph::new(counter_text)
.centered()
.block(block)
.render(area, buf);
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn render() {
let app = App::default();
let mut buf = Buffer::empty(Rect::new(0, 0, 50, 4));

app.render(buf.area, &mut buf);

let mut expected = Buffer::with_lines(vec![
"┏━━━━━━━━━━━━━ Counter App Tutorial ━━━━━━━━━━━━━┓",
"┃ Value: 0 ┃",
"┃ ┃",
"┗━ Decrement <Left> Increment <Right> Quit <Q> ━━┛",
]);
let title_style = Style::new().bold();
let counter_style = Style::new().yellow();
let key_style = Style::new().blue().bold();
expected.set_style(Rect::new(14, 0, 22, 1), title_style);
expected.set_style(Rect::new(28, 1, 1, 1), counter_style);
expected.set_style(Rect::new(13, 3, 6, 1), key_style);
expected.set_style(Rect::new(30, 3, 7, 1), key_style);
expected.set_style(Rect::new(43, 3, 4, 1), key_style);
fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> io::Result<bool> {
loop {
terminal.draw(|f| ui(f, app))?;

// note ratatui also has an assert_buffer_eq! macro that can be used to
// compare buffers and display the differences in a more readable way
assert_eq!(buf, expected);
}

#[test]
fn handle_key_event() {
let mut app = App::default();
app.handle_key_event(KeyCode::Right.into()).unwrap();
assert_eq!(app.counter, 1);

app.handle_key_event(KeyCode::Left.into()).unwrap();
assert_eq!(app.counter, 0);

let mut app = App::default();
app.handle_key_event(KeyCode::Char('q').into()).unwrap();
assert_eq!(app.exit, true);
}

#[test]
#[should_panic(expected = "attempt to subtract with overflow")]
fn handle_key_event_panic() {
let mut app = App::default();
let _ = app.handle_key_event(KeyCode::Left.into());
}

#[test]
fn handle_key_event_overflow() {
let mut app = App::default();
assert!(app.handle_key_event(KeyCode::Right.into()).is_ok());
assert!(app.handle_key_event(KeyCode::Right.into()).is_ok());
assert_eq!(
app.handle_key_event(KeyCode::Right.into())
.unwrap_err()
.to_string(),
"counter overflow!"
);
if let Event::Key(key) = event::read()? {
dbg!(key.code)
}
}
}

0 comments on commit 1b030d8

Please sign in to comment.