diff --git a/src/book/mod.rs b/src/book/mod.rs index 82e64d3..5e481b7 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -215,14 +215,15 @@ impl Book { /// Select a sheet by name. pub fn select_sheet_by_name(&mut self, name: &str) -> bool { - if let Some(sheet) = self + if let Some((idx, _sheet)) = self .model .workbook .worksheets .iter() - .find(|sheet| sheet.name == name) + .enumerate() + .find(|(_idx, sheet)| sheet.name == name) { - self.current_sheet = sheet.sheet_id; + self.current_sheet =idx as u32; return true; } false @@ -233,16 +234,37 @@ impl Book { self.model.workbook.get_worksheet_names() } + pub fn select_next_sheet(&mut self) { + let len = self.model.workbook.worksheets.len() as u32; + let mut next = self.current_sheet + 1; + if next == len { + next = 0; + } + self.current_sheet = next; + } + + pub fn select_prev_sheet(&mut self) { + let len = self.model.workbook.worksheets.len() as u32; + let next = if self.current_sheet == 0 { + len - 1 + } else { + self.current_sheet - 1 + }; + self.current_sheet = next; + } + + /// Select a sheet by id. pub fn select_sheet_by_id(&mut self, id: u32) -> bool { - if let Some(sheet) = self + if let Some((idx, _sheet)) = self .model .workbook .worksheets .iter() - .find(|sheet| sheet.sheet_id == id) + .enumerate() + .find(|(_idx, sheet)| sheet.sheet_id == id) { - self.current_sheet = sheet.sheet_id; + self.current_sheet = idx as u32; return true; } false @@ -254,7 +276,7 @@ impl Book { .model .workbook .worksheet(self.current_sheet) - .map_err(|s| anyhow!("Invalid Worksheet: {}", s))?) + .map_err(|s| anyhow!("Invalid Worksheet id: {}: error: {}", self.current_sheet, s))?) } pub(crate) fn get_sheet_mut(&mut self) -> Result<&mut Worksheet> { diff --git a/src/ui/cmd.rs b/src/ui/cmd.rs index 5b46fd4..f46cef0 100644 --- a/src/ui/cmd.rs +++ b/src/ui/cmd.rs @@ -9,6 +9,7 @@ pub enum Cmd<'a> { InsertColumns(usize), RenameSheet(Option, &'a str), NewSheet(Option<&'a str>), + SelectSheet(&'a str), Edit(&'a str), Help(Option<&'a str>), Quit, @@ -24,6 +25,9 @@ pub fn parse<'cmd, 'i: 'cmd>(input: &'i str) -> Result>, &'stat if let Some(cmd) = try_consume_new_sheet(cursor.clone())? { return Ok(Some(cmd)); } + if let Some(cmd) = try_consume_select_sheet(cursor.clone())? { + return Ok(Some(cmd)); + } // try consume insert-row command. if let Some(cmd) = try_consume_insert_row(cursor.clone())? { return Ok(Some(cmd)); @@ -114,6 +118,25 @@ fn try_consume_new_sheet<'cmd, 'i: 'cmd>( }))); } +fn try_consume_select_sheet<'cmd, 'i: 'cmd>( + mut input: StrCursor<'i>, +) -> Result>, &'static str> { + const LONG: &'static str = "select-sheet"; + + if compare(input.clone(), LONG) { + input.seek(LONG.len()); + } else { + return Ok(None); + } + if input.remaining() > 0 && !is_ws(&mut input) { + return Err("Invalid command: Did you mean to type `write `?"); + } + let arg = input.span(0..).trim(); + if arg.is_empty() { + return Err("Invalid command: Did you forget the sheet name? `write `?"); + } + return Ok(Some(Cmd::SelectSheet(arg))); +} fn try_consume_insert_row<'cmd, 'i: 'cmd>( mut input: StrCursor<'i>, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 45eefc4..18d6111 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -293,6 +293,10 @@ impl<'ws> Workspace<'ws> { self.book.new_sheet(name)?; Ok(true) } + Ok(Some(Cmd::SelectSheet(name))) => { + self.book.select_sheet_by_name(name); + Ok(true) + } Ok(Some(Cmd::Quit)) => { // TODO(zaphar): We probably need to do better than this std::process::exit(0); @@ -323,6 +327,12 @@ impl<'ws> Workspace<'ws> { KeyCode::Char('?') => { self.enter_dialog_mode(self.render_help_text()); } + KeyCode::Char('n') if key.modifiers == KeyModifiers::CONTROL => { + self.book.select_next_sheet(); + } + KeyCode::Char('p') if key.modifiers == KeyModifiers::CONTROL => { + self.book.select_prev_sheet(); + } KeyCode::Char('s') if key.modifiers == KeyModifiers::HYPER || key.modifiers == KeyModifiers::SUPER => @@ -467,47 +477,6 @@ impl<'ws> Workspace<'ws> { Ok(()) } - fn get_render_parts( - &mut self, - area: Rect, - ) -> Vec<(Rect, Box)> { - use ratatui::widgets::StatefulWidget; - let mut cs = vec![Constraint::Fill(4), Constraint::Fill(30)]; - let mut rs: Vec> = vec![ - Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| ws.text_area.render(rect, buf)), - Box::new(move |rect: Rect, buf: &mut Buffer, ws: &mut Self| { - let sheet_name = ws.book.get_sheet_name().unwrap_or("Unknown"); - let table_block = Block::bordered().title_top(sheet_name); - let viewport = Viewport::new(&ws.book).with_selected(ws.book.location.clone()).block(table_block); - StatefulWidget::render(viewport, rect, buf, &mut ws.state.viewport_state); - }), - ]; - - if self.state.modality() == &Modality::Command { - cs.push(Constraint::Max(1)); - rs.push(Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| { - StatefulWidget::render( - TextPrompt::from("Command"), - rect, - buf, - &mut ws.state.command_state, - ) - })); - } - let rects: Vec = Vec::from( - Layout::vertical(cs) - .vertical_margin(2) - .horizontal_margin(2) - .flex(Flex::Legacy) - .split(area.clone()) - .as_ref(), - ); - rects - .into_iter() - .zip(rs.into_iter()) - .map(|(rect, f)| (rect, f)) - .collect() - } } fn load_book(path: &PathBuf, locale: &str, tz: &str) -> Result { diff --git a/src/ui/render/mod.rs b/src/ui/render/mod.rs index facf854..caca080 100644 --- a/src/ui/render/mod.rs +++ b/src/ui/render/mod.rs @@ -2,7 +2,7 @@ use ratatui::{ self, layout::Rect, text::{Line, Text}, - widgets::{Block, Widget}, + widgets::{Block, Tabs, Widget}, Frame, }; use tui_popup::Popup; @@ -15,6 +15,61 @@ pub use viewport::Viewport; #[cfg(test)] mod test; +impl<'ws> Workspace<'ws> { + fn get_render_parts( + &mut self, + area: Rect, + ) -> Vec<(Rect, Box)> { + use ratatui::widgets::StatefulWidget; + let mut cs = vec![ + Constraint::Length(2), + Constraint::Length(3), + Constraint::Fill(1), + ]; + let mut rs: Vec> = vec![ + Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| { + let tabs = Tabs::new(ws.book.get_sheet_names()) + .select(Some(ws.book.current_sheet as usize)); + tabs.render(rect, buf); + }), + Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| ws.text_area.render(rect, buf)), + Box::new(move |rect: Rect, buf: &mut Buffer, ws: &mut Self| { + let sheet_name = ws.book.get_sheet_name().unwrap_or("Unknown"); + let table_block = Block::bordered().title_top(sheet_name); + let viewport = Viewport::new(&ws.book) + .with_selected(ws.book.location.clone()) + .block(table_block); + StatefulWidget::render(viewport, rect, buf, &mut ws.state.viewport_state); + }), + ]; + + if self.state.modality() == &Modality::Command { + cs.push(Constraint::Max(1)); + rs.push(Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| { + StatefulWidget::render( + TextPrompt::from("Command"), + rect, + buf, + &mut ws.state.command_state, + ) + })); + } + let rects: Vec = Vec::from( + Layout::vertical(cs) + .vertical_margin(2) + .horizontal_margin(2) + .flex(Flex::Legacy) + .split(area.clone()) + .as_ref(), + ); + rects + .into_iter() + .zip(rs.into_iter()) + .map(|(rect, f)| (rect, f)) + .collect() + } +} + impl<'widget, 'ws: 'widget> Widget for &'widget mut Workspace<'ws> { fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer) where