mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-23 05:19:48 -04:00
wip: cell editing
This commit is contained in:
parent
a5177bda18
commit
1aa9224c15
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -660,6 +660,7 @@ dependencies = [
|
||||
"futures",
|
||||
"ratatui",
|
||||
"thiserror",
|
||||
"tui-textarea",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -772,6 +773,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tui-textarea"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"ratatui",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
|
@ -13,3 +13,4 @@ csvx = "0.1.17"
|
||||
futures = "0.3.31"
|
||||
ratatui = "0.29.0"
|
||||
thiserror = "1.0.65"
|
||||
tui-textarea = "0.7.0"
|
||||
|
@ -11,40 +11,6 @@ use csvx;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
pub enum CellValue {
|
||||
Text(String),
|
||||
Float(f64),
|
||||
Integer(i64),
|
||||
Formula(String),
|
||||
}
|
||||
|
||||
impl CellValue {
|
||||
pub fn to_csv_value(&self) -> String {
|
||||
match self {
|
||||
CellValue::Text(v) => format!("\"{}\"", v),
|
||||
CellValue::Float(v) => format!("{}", v),
|
||||
CellValue::Integer(v) => format!("{}", v),
|
||||
CellValue::Formula(v) => format!("{}", v),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text<S: Into<String>>(value: S) -> CellValue {
|
||||
CellValue::Text(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 {
|
||||
CellValue::Float(value)
|
||||
}
|
||||
|
||||
pub fn int(value: i64) -> CellValue {
|
||||
CellValue::Integer(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// The Address in a [Tbl].
|
||||
#[derive(Default, Debug, PartialEq, PartialOrd, Ord, Eq, Clone)]
|
||||
pub struct Address {
|
||||
@ -87,6 +53,10 @@ impl Tbl {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_raw_value(&self, Address {row, col}: &Address) -> String {
|
||||
self.csv.get_raw_table()[*row][*col].clone()
|
||||
}
|
||||
|
||||
pub fn move_to(&mut self, addr: Address) -> Result<()> {
|
||||
let (row, col) = self.dimensions();
|
||||
if addr.row >= row || addr.col >= col {
|
||||
@ -96,8 +66,7 @@ impl Tbl {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_entry(&mut self, address: Address, value: CellValue) -> Result<()> {
|
||||
// TODO(zaphar): At some point we'll need to store the graph of computation
|
||||
pub fn update_entry(&mut self, address: &Address, value: String) -> Result<()> {
|
||||
let (row, col) = self.dimensions();
|
||||
if address.row >= row {
|
||||
// then we need to add rows.
|
||||
@ -112,7 +81,7 @@ impl Tbl {
|
||||
}
|
||||
Ok(self
|
||||
.csv
|
||||
.update(address.col, address.row, value.to_csv_value())?)
|
||||
.update(address.col, address.row, value.trim())?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,11 @@ use super::*;
|
||||
#[test]
|
||||
fn test_dimensions_calculation() {
|
||||
let mut tbl = Tbl::new();
|
||||
tbl.update_entry(Address::new(0, 0), CellValue::Text(String::new())).unwrap();
|
||||
tbl.update_entry(&Address::new(0, 0), String::new()).unwrap();
|
||||
assert_eq!((1, 1), tbl.dimensions());
|
||||
tbl.update_entry(Address::new(0, 10), CellValue::Text(String::new())).unwrap();
|
||||
tbl.update_entry(&Address::new(0, 10), String::new()).unwrap();
|
||||
assert_eq!((1, 11), tbl.dimensions());
|
||||
tbl.update_entry(Address::new(20, 5), CellValue::Text(String::new())).unwrap();
|
||||
tbl.update_entry(&Address::new(20, 5), String::new()).unwrap();
|
||||
assert_eq!((21, 11), tbl.dimensions());
|
||||
}
|
||||
|
||||
|
138
src/ui/mod.rs
138
src/ui/mod.rs
@ -8,12 +8,13 @@ use anyhow::{Context, Result};
|
||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
|
||||
use ratatui::{
|
||||
self,
|
||||
layout::{Constraint, Flex},
|
||||
style::{Color, Stylize},
|
||||
layout::{Constraint, Flex, Layout},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
text::{Line, Text},
|
||||
widgets::{Block, Cell, Row, Table, Widget},
|
||||
Frame,
|
||||
};
|
||||
use tui_textarea::TextArea;
|
||||
|
||||
#[derive(Default, Debug, PartialEq)]
|
||||
pub enum Modality {
|
||||
@ -30,19 +31,25 @@ pub struct AppState {
|
||||
// Interaction Modalities
|
||||
// * Navigate
|
||||
// * Edit
|
||||
pub struct Workspace {
|
||||
pub struct Workspace<'ws> {
|
||||
name: String,
|
||||
tbl: Tbl,
|
||||
state: AppState,
|
||||
text_area: TextArea<'ws>,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
impl<'ws> Workspace<'ws> {
|
||||
pub fn new<S: Into<String>>(tbl: Tbl, name: S) -> Self {
|
||||
Self {
|
||||
let mut ws = Self {
|
||||
tbl,
|
||||
name: name.into(),
|
||||
state: AppState::default(),
|
||||
}
|
||||
text_area: reset_text_area("".to_owned()),
|
||||
dirty: false,
|
||||
};
|
||||
ws.handle_movement_change();
|
||||
ws
|
||||
}
|
||||
|
||||
pub fn load(path: &PathBuf) -> Result<Self> {
|
||||
@ -112,26 +119,20 @@ impl Workspace {
|
||||
|
||||
fn handle_edit_event(&mut self, key: event::KeyEvent) -> Result<Option<ExitCode>> {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Esc => {
|
||||
if let KeyCode::Esc = key.code {
|
||||
self.state.modality = Modality::Navigate;
|
||||
},
|
||||
KeyCode::Char('j') => {
|
||||
self.move_down()?;
|
||||
},
|
||||
KeyCode::Char('k') => {
|
||||
self.move_up()?;
|
||||
},
|
||||
KeyCode::Char('h') => {
|
||||
self.move_left()?;
|
||||
},
|
||||
KeyCode::Char('l') => {
|
||||
self.move_right()?;
|
||||
},
|
||||
_ => {
|
||||
// noop
|
||||
self.text_area.set_cursor_line_style(Style::default());
|
||||
self.text_area.set_cursor_style(Style::default());
|
||||
let contents = self.text_area.lines().join("\n");
|
||||
if self.dirty {
|
||||
let loc = self.tbl.location.clone();
|
||||
self.tbl.update_entry(&loc, contents)?;
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
if self.text_area.input(key) {
|
||||
self.dirty = true;
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
@ -139,27 +140,32 @@ impl Workspace {
|
||||
fn handle_navigation_event(&mut self, key: event::KeyEvent) -> Result<Option<ExitCode>> {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Esc => {
|
||||
self.state.modality = Modality::Navigate;
|
||||
},
|
||||
KeyCode::Char('q') => {
|
||||
return Ok(Some(ExitCode::SUCCESS));
|
||||
},
|
||||
KeyCode::Char('j') => {
|
||||
self.move_down()?;
|
||||
},
|
||||
KeyCode::Char('k') => {
|
||||
self.move_up()?;
|
||||
},
|
||||
KeyCode::Char('h') => {
|
||||
self.move_left()?;
|
||||
},
|
||||
KeyCode::Char('l') => {
|
||||
self.move_right()?;
|
||||
},
|
||||
KeyCode::Char('e') => {
|
||||
self.state.modality = Modality::CellEdit;
|
||||
},
|
||||
self.text_area
|
||||
.set_cursor_line_style(Style::default().add_modifier(Modifier::UNDERLINED));
|
||||
self.text_area
|
||||
.set_cursor_style(Style::default().add_modifier(Modifier::SLOW_BLINK));
|
||||
}
|
||||
KeyCode::Char('q') => {
|
||||
return Ok(Some(ExitCode::SUCCESS));
|
||||
}
|
||||
KeyCode::Char('j') => {
|
||||
self.move_down()?;
|
||||
self.handle_movement_change();
|
||||
}
|
||||
KeyCode::Char('k') => {
|
||||
self.move_up()?;
|
||||
self.handle_movement_change();
|
||||
}
|
||||
KeyCode::Char('h') => {
|
||||
self.move_left()?;
|
||||
self.handle_movement_change();
|
||||
}
|
||||
KeyCode::Char('l') => {
|
||||
self.move_right()?;
|
||||
self.handle_movement_change();
|
||||
}
|
||||
_ => {
|
||||
// noop
|
||||
}
|
||||
@ -168,23 +174,48 @@ impl Workspace {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// navigation methods left, right, up, down
|
||||
fn handle_movement_change(&mut self) {
|
||||
let contents = self.tbl.get_raw_value(&self.tbl.location);
|
||||
self.text_area = reset_text_area(contents);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for &'a Workspace {
|
||||
fn reset_text_area<'a>(content: String) -> TextArea<'a> {
|
||||
let mut text_area = TextArea::from(content.lines());
|
||||
text_area.set_cursor_line_style(Style::default());
|
||||
text_area.set_cursor_style(Style::default());
|
||||
text_area
|
||||
}
|
||||
|
||||
impl<'widget, 'ws: 'widget> Widget for &'widget Workspace<'ws> {
|
||||
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let block = Block::bordered()
|
||||
let outer_block = Block::bordered()
|
||||
.title(Line::from(self.name.as_str()))
|
||||
.title_bottom(match &self.state.modality {
|
||||
Modality::Navigate => "navigate",
|
||||
Modality::CellEdit => "edit",
|
||||
})
|
||||
.title_bottom(Line::from(format!("{},{}", self.tbl.location.row, self.tbl.location.col)).right_aligned());
|
||||
let table = Table::from(&self.tbl).block(block);
|
||||
table.render(area, buf);
|
||||
.title_bottom(
|
||||
Line::from(format!(
|
||||
"{},{}",
|
||||
self.tbl.location.row, self.tbl.location.col
|
||||
))
|
||||
.right_aligned(),
|
||||
);
|
||||
let [edit_rect, table_rect] =
|
||||
Layout::vertical(&[Constraint::Fill(1), Constraint::Fill(20)])
|
||||
.vertical_margin(2)
|
||||
.horizontal_margin(2)
|
||||
.flex(Flex::Legacy)
|
||||
.areas(area.clone());
|
||||
outer_block.render(area, buf);
|
||||
self.text_area.render(edit_rect, buf);
|
||||
let table_block = Block::bordered();
|
||||
let table = Table::from(&self.tbl).block(table_block);
|
||||
table.render(table_rect, buf);
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,11 +240,9 @@ impl<'t> From<&Tbl> for Table<'t> {
|
||||
let content = format!("{}", v);
|
||||
let cell = Cell::new(Text::raw(content));
|
||||
match (value.location.row == ri, value.location.col == ci) {
|
||||
(true, true) =>
|
||||
cell
|
||||
.fg(Color::White).underlined(),
|
||||
_ =>
|
||||
cell.bg(if ri % 2 == 0 {
|
||||
(true, true) => cell.fg(Color::White).underlined(),
|
||||
_ => cell
|
||||
.bg(if ri % 2 == 0 {
|
||||
Color::Rgb(57, 61, 71)
|
||||
} else {
|
||||
Color::Rgb(165, 169, 160)
|
||||
@ -223,7 +252,8 @@ impl<'t> From<&Tbl> for Table<'t> {
|
||||
} else {
|
||||
Color::Rgb(31, 32, 34)
|
||||
}),
|
||||
}.bold()
|
||||
}
|
||||
.bold()
|
||||
}));
|
||||
Row::new(cells)
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user