feat: UI styling and loading files

This commit is contained in:
Jeremy Wall 2024-10-29 19:47:50 -04:00
parent 63d8c47c1f
commit 43956ac0eb
5 changed files with 146 additions and 60 deletions

3
examples/test.csv Normal file
View File

@ -0,0 +1,3 @@
pi,3^5,"ref(0,0)",-(1/0)
12%5,"pow(3,5)",0/NaN,"""Apollo"""
A1+A2,"if(true , sqrt(25),round(if(false,1.1,2.5)))",D2+1969,
1 pi 3^5 ref(0,0) -(1/0)
2 12%5 pow(3,5) 0/NaN "Apollo"
3 A1+A2 if(true , sqrt(25),round(if(false,1.1,2.5))) D2+1969

View File

@ -1,17 +1,11 @@
use std::path::PathBuf;
use anyhow::Context;
use clap::Parser;
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use ratatui::{
self,
layout::{Constraint, Layout},
widgets::{Table, Tabs},
Frame,
};
use sheet::{Address, CellValue, Tbl};
use ratatui;
mod sheet;
mod ui;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
@ -20,9 +14,9 @@ pub struct Args {
workbook: PathBuf,
}
fn run(terminal: &mut ratatui::DefaultTerminal) -> std::io::Result<()> {
fn run(terminal: &mut ratatui::DefaultTerminal, name: PathBuf) -> std::io::Result<()> {
loop {
terminal.draw(|frame| draw(frame))?;
terminal.draw(|frame| ui::draw(frame, &name))?;
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
return Ok(());
@ -31,36 +25,12 @@ fn run(terminal: &mut ratatui::DefaultTerminal) -> std::io::Result<()> {
}
}
fn generate_default_table<'a>() -> Table<'a> {
let mut tbl = Tbl::new();
tbl.update_entry(Address::new(5, 5), CellValue::text("5,5"))
.context("Failed updating entry at 5,5")
.unwrap();
tbl.update_entry(Address::new(10, 10), CellValue::float(10.10))
.context("Failed updating entry at 10,10")
.unwrap();
tbl.update_entry(Address::new(0, 0), CellValue::other("0.0"))
.context("Failed updating entry at 0,0")
.unwrap();
tbl.into()
}
fn draw(frame: &mut Frame) {
use Constraint::{Min, Percentage};
let table = generate_default_table();
let tabs = Tabs::new(vec!["sheet1"]).select(0);
let rects = Layout::vertical([Min(1), Percentage(90)]).split(frame.area());
frame.render_widget(tabs, rects[0]);
frame.render_widget(table, rects[1]);
}
fn main() -> std::io::Result<()> {
let _ = Args::parse();
let args = Args::parse();
let mut terminal = ratatui::init();
terminal.clear()?;
let app_result = run(&mut terminal);
let app_result = run(&mut terminal, args.workbook);
ratatui::restore();
app_result
}

0
src/mod.rs Normal file
View File

View File

@ -8,7 +8,6 @@
use anyhow::{anyhow, Result};
use csvx;
use ratatui::widgets::{Cell, Row, Table};
use std::borrow::Borrow;
@ -16,7 +15,7 @@ pub enum CellValue {
Text(String),
Float(f64),
Integer(i64),
Other(String),
Formula(String),
}
impl CellValue {
@ -25,7 +24,7 @@ impl CellValue {
CellValue::Text(v) => format!("\"{}\"", v),
CellValue::Float(v) => format!("{}", v),
CellValue::Integer(v) => format!("{}", v),
CellValue::Other(v) => format!("{}", v),
CellValue::Formula(v) => format!("{}", v),
}
}
@ -33,8 +32,8 @@ impl CellValue {
CellValue::Text(Into::<String>::into(value))
}
pub fn other<S: Into<String>>(value: S) -> CellValue {
CellValue::Other(Into::<String>::into(value))
pub fn formula<S: Into<String>>(value: S) -> CellValue {
CellValue::Formula(Into::<String>::into(value))
}
pub fn float(value: f64) -> CellValue {
@ -61,14 +60,12 @@ impl Address {
/// A single table of addressable computable values.
pub struct Tbl {
csv: csvx::Table,
pub csv: csvx::Table,
}
impl Tbl {
pub fn new() -> Self {
Self {
csv: csvx::Table::new("").unwrap(),
}
Self::from_str("").unwrap()
}
pub fn dimensions(&self) -> (usize, usize) {
@ -108,20 +105,5 @@ impl Tbl {
}
}
impl<'t> From<Tbl> for Table<'t> {
fn from(value: Tbl) -> Self {
let rows: Vec<Row> = value
.csv
.get_calculated_table()
.iter()
.map(|r| {
let cells = r.iter().map(|v| Cell::new(format!("{}", v)));
Row::new(cells)
})
.collect();
Table::default().rows(rows)
}
}
#[cfg(test)]
mod tests;

131
src/ui/mod.rs Normal file
View File

@ -0,0 +1,131 @@
//! Ui rendering logic
use std::{fs::File, io::Read, path::PathBuf};
use super::sheet::{Address, CellValue, Tbl};
use anyhow::{Context, Result};
use ratatui::{
self,
layout::{Constraint, Flex, Layout},
style::{Color, Stylize},
text::Text,
widgets::{Block, Cell, Row, Table, Tabs, Widget},
Frame,
};
pub struct Workspace {
name: String,
tbl: Tbl,
}
impl Workspace {
pub fn new<S: Into<String>>(tbl: Tbl, name: S) -> Self {
Self {
tbl,
name: name.into(),
}
}
pub fn load(path: &PathBuf) -> Result<Self> {
let mut f = File::open(path)?;
let mut buf = Vec::new();
let _ = f.read_to_end(&mut buf)?;
let input = String::from_utf8(buf)
.context(format!("Error reading file: {:?}", path))?;
let tbl = Tbl::from_str(input)?;
Ok(Workspace::new(tbl, path.file_name().map(|p| p.to_string_lossy().to_string()).unwrap_or_else(|| "Unknown".to_string())))
}
}
impl Widget for Workspace {
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
where
Self: Sized,
{
use Constraint::{Min, Percentage};
let rects = Layout::vertical([Min(1), Percentage(90)]).split(area);
let table = Table::from(&self.tbl);
let tabs = Tabs::new(vec![self.name.clone()]).select(0);
tabs.render(rects[0], buf);
table.render(rects[1], buf);
}
}
fn generate_default_table<'a>() -> Tbl {
let mut tbl = Tbl::new();
tbl.update_entry(Address::new(3, 3), CellValue::text("3,3"))
.context("Failed updating entry at 5,5")
.expect("Unexpected fail to update entry");
tbl.update_entry(Address::new(6, 6), CellValue::float(6.6))
.context("Failed updating entry at 10,10")
.expect("Unexpected fail to update entry");
tbl.update_entry(Address::new(0, 0), CellValue::formula("0.0"))
.context("Failed updating entry at 0,0")
.expect("Unexpected fail to update entry");
tbl.update_entry(Address::new(1, 0), CellValue::formula("1.0"))
.context("Failed updating entry at 0,0")
.expect("Unexpected fail to update entry");
tbl.update_entry(Address::new(2, 0), CellValue::formula("2.0"))
.context("Failed updating entry at 0,0")
.expect("Unexpected fail to update entry");
tbl
}
const COLNAMES: [&'static str; 27] = [
"", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
"S", "T", "U", "V", "W", "X", "Y", "Z",
];
impl<'t> From<&Tbl> for Table<'t> {
fn from(value: &Tbl) -> Self {
let (_, cols) = value.dimensions();
let rows: Vec<Row> = value
.csv
.get_calculated_table()
.iter()
.enumerate()
.map(|(i, r)| {
let cells = vec![Cell::new(format!("{}", i))]
.into_iter()
.chain(r.iter().map(|v| {
let content = format!("{}", v);
Cell::new(Text::raw(content))
.bg(if i % 2 == 0 {
Color::Rgb(57, 61, 71)
} else {
Color::Rgb(165, 169, 160)
})
.fg(if i % 2 == 0 {
Color::White
} else {
Color::Rgb(31,32,34)
})
.underlined()
.bold()
}));
Row::new(cells)
})
.collect();
// TODO(zaphar): Handle the double letter column names
let header: Vec<Cell> = (0..=cols).map(|i| Cell::new(COLNAMES[i % 26])).collect();
let mut constraints: Vec<Constraint> = Vec::new();
constraints.push(Constraint::Max(5));
for _ in 0..cols {
constraints.push(Constraint::Min(5));
}
Table::new(rows, constraints)
.block(Block::bordered())
.header(Row::new(header).underlined())
.column_spacing(1)
.flex(Flex::SpaceAround)
}
}
pub fn draw(frame: &mut Frame, name: &PathBuf) {
let table = generate_default_table();
let ws = Workspace::load(name).unwrap();
frame.render_widget(ws, frame.area());
}