summaryrefslogtreecommitdiff
path: root/src/widgets/sheetview/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/widgets/sheetview/mod.rs')
-rw-r--r--src/widgets/sheetview/mod.rs571
1 files changed, 142 insertions, 429 deletions
diff --git a/src/widgets/sheetview/mod.rs b/src/widgets/sheetview/mod.rs
index 4ae37f0..aac2080 100644
--- a/src/widgets/sheetview/mod.rs
+++ b/src/widgets/sheetview/mod.rs
@@ -1,49 +1,27 @@
-use std::thread::JoinHandle;
-
use ratatui::{
- crossterm::event::{KeyCode, KeyEvent, KeyModifiers},
+ crossterm::event::{KeyCode, KeyEvent},
prelude::*,
style::Stylize,
text::ToLine,
- widgets::{Block, Borders, Clear, Gauge, Paragraph, Widget, Wrap},
+ widgets::Widget,
};
use crate::{
config::GlobalConfig,
cursor::CursorMove,
lua,
- sheet::{
- cell::Cell,
- eval::EvalFunction,
- Sheet,
- },
- state::GlobalState,
+ sheet::cell::Cell,
+ state::{view::mode::Mode, window::Window, GlobalState},
};
-use super::{luaeditor::LuaEditor, statusbar::StatusBar};
+use super::statusbar::StatusBar;
const DEFAULT_COLUMN_WIDTH: u16 = 10;
#[derive(Default)]
-enum SheetViewMode {
- #[default]
- Normal,
- Insert,
- Visual,
- Command,
- CommandError(String),
- Script,
- Processing(Option<JoinHandle<Result<Sheet, String>>>),
- EditorError(String),
-}
-
-#[derive(Default)]
pub struct SheetView {
bar: StatusBar,
- scroll: (u16, u16),
- selection_anchor: Option<(u16, u16)>,
- mode: SheetViewMode,
- editor: LuaEditor,
+ scroll: (usize, usize),
}
impl SheetView {
@@ -56,314 +34,126 @@ impl SheetView {
.right("")
.right_style(Style::default().on_red()),
scroll: (0, 0),
- mode: SheetViewMode::Normal,
- selection_anchor: None,
- editor: LuaEditor::new(),
}
}
- pub fn move_cursor_by(&mut self, delta: (isize, isize)) {
- let mut state = GlobalState::instance_mut();
- state
- .sheetview
- .cursor
- .move_checked(CursorMove::Relative(delta));
+ fn set_mode(&self, mode: Mode) {
+ GlobalState::instance_mut().sheetview.mode = mode;
}
- pub fn selection(&self) -> Vec<(u16, u16)> {
- let mut selection = Vec::new();
-
- if let Some((row, column)) = self.selection_range() {
- for i in row.0..=row.1 {
- for j in column.0..=column.1 {
- selection.push((i, j))
- }
- }
- }
-
- selection
- }
-
- fn is_in_selection(&mut self, row: u16, column: u16) -> bool {
- if let Some((row_range, column_range)) = self.selection_range() {
- row >= row_range.0
- && row <= row_range.1
- && column >= column_range.0
- && column <= column_range.1
- } else {
- false
- }
+ fn move_cursor(&self, cm: CursorMove) {
+ GlobalState::instance_mut()
+ .sheetview
+ .cursor
+ .move_checked(cm)
}
- fn selection_range(&self) -> Option<((u16, u16), (u16, u16))> {
+ fn cursor(&self) -> (usize, usize) {
let state = GlobalState::instance();
- let cursor = &state.sheetview.cursor;
- if let Some(selection) = self.selection_anchor {
- let row = if selection.0 as usize > cursor.y() {
- (cursor.y() as u16, selection.0)
- } else {
- (selection.0, cursor.y() as u16)
- };
-
- let column = if selection.1 as usize > cursor.x() {
- (cursor.x() as u16, selection.1)
- } else {
- (selection.1, cursor.x() as u16)
- };
+ (state.sheetview.cursor.y(), state.sheetview.cursor.x())
+ }
- Some((row, column))
- } else {
- None
- }
+ fn start_selection(&self) {
+ let mut state = GlobalState::instance_mut();
+ state.sheetview.selection_anchor = Some(self.cursor());
+ state.sheetview.mode = Mode::Visual;
}
- fn join_process_handle_on_finished(&mut self) {
- if let SheetViewMode::Processing(opt) = &mut self.mode {
- let handle = opt.take().unwrap();
- if handle.is_finished() {
- match handle.join().unwrap() {
- Ok(sheet) => {
- GlobalState::instance()
- .sheetview
- .active_sheet()
- .unwrap()
- .write()
- .unwrap()
- .apply(sheet);
- self.mode = SheetViewMode::Script;
- }
- Err(message) => self.mode = SheetViewMode::EditorError(message),
- };
- } else {
- self.mode = SheetViewMode::Processing(Some(handle));
- }
- }
+ fn open_editor(&self) {
+ let mut state = GlobalState::instance_mut();
+ state.editor.buffer.set_lines_from_string(
+r#"require('neosheet')
+ .state
+ .view
+ .active:foreach(function(cell)
+ return ""
+end)"#,
+ );
+ state.set_focus(Window::Editor)
}
pub fn handle_key_event(&mut self, event: KeyEvent) {
- match &self.mode {
- SheetViewMode::Normal => match event.code {
- KeyCode::Char('j') => self.move_cursor_by((0, 1)),
- KeyCode::Char('k') => self.move_cursor_by((0, -1)),
- KeyCode::Char('h') => self.move_cursor_by((-1, 0)),
- KeyCode::Char('l') => self.move_cursor_by((1, 0)),
- KeyCode::Char('v') => {
- let state = GlobalState::instance();
- let cursor = &state.sheetview.cursor;
- self.selection_anchor = Some((cursor.y() as u16, cursor.x() as u16));
- self.mode = SheetViewMode::Visual
- }
- KeyCode::Char('s') => {
- self.editor.set_text("function(cell)\n\treturn \"\"\nend");
- self.mode = SheetViewMode::Script;
- }
+ let mode = { GlobalState::instance().sheetview.mode };
+ match mode {
+ Mode::Normal => match event.code {
+ KeyCode::Char('j') => self.move_cursor(CursorMove::Down(1)),
+ KeyCode::Char('k') => self.move_cursor(CursorMove::Up(1)),
+ KeyCode::Char('h') => self.move_cursor(CursorMove::Left(1)),
+ KeyCode::Char('l') => self.move_cursor(CursorMove::Right(1)),
+ KeyCode::Char('v') => self.start_selection(),
+ KeyCode::Char('s') => self.open_editor(),
KeyCode::Char(':') => {
- self.mode = SheetViewMode::Command;
- self.bar.set_input_mode(true)
+ self.set_mode(Mode::Command);
+ self.bar.set_input_mode(true);
}
KeyCode::Enter => {
- self.mode = SheetViewMode::Insert;
+ self.set_mode(Mode::Insert);
self.bar.set_input_mode(true);
}
_ => {}
},
- SheetViewMode::Insert => match event.code {
+ Mode::Insert => match event.code {
KeyCode::Enter => {
- let lock = GlobalState::instance().sheetview.active_sheet().unwrap();
+ let mut state = GlobalState::instance_mut();
+ let lock = state.sheetview.active_sheet().unwrap();
let mut sheet = lock.write().unwrap();
- let state = GlobalState::instance();
- let cursor = &state.sheetview.cursor;
-
- if self.selection_anchor.is_some() {
- self.selection().iter().map(|(r, c)| (*r, *c)).collect()
- } else {
- vec![(cursor.y() as u16, cursor.x() as u16)]
- }
- .into_iter()
- .for_each(|(r, c)| {
- sheet.set_cell(r as usize, c as usize, {
- let s = self.bar.input().unwrap();
- match s.parse() {
- Ok(n) => Cell::Number(n),
- Err(_) => Cell::String(s.to_string()),
- }
- })
- });
+ state
+ .sheetview
+ .selection_or_cursor()
+ .into_iter()
+ .for_each(|(r, c)| {
+ sheet.set_cell(r as usize, c as usize, {
+ let s = self.bar.input().unwrap();
+ match s.parse() {
+ Ok(n) => Cell::Number(n),
+ Err(_) => Cell::String(s.to_string()),
+ }
+ })
+ });
+ state.sheetview.cancel_mode();
self.bar.set_input_mode(false);
- if self.selection_anchor.is_some() {
- self.mode = SheetViewMode::Visual;
- } else {
- self.mode = SheetViewMode::Normal;
- }
}
KeyCode::Esc => {
- if self.selection_anchor.is_some() {
- self.mode = SheetViewMode::Visual;
- } else {
- self.mode = SheetViewMode::Normal;
- }
+ GlobalState::instance_mut().sheetview.cancel_mode();
self.bar.set_input_mode(false);
}
_ => self.bar.handle_keyevent(event),
},
- SheetViewMode::Visual => match event.code {
- KeyCode::Char('j') => self.move_cursor_by((0, 1)),
- KeyCode::Char('k') => self.move_cursor_by((0, -1)),
- KeyCode::Char('h') => self.move_cursor_by((-1, 0)),
- KeyCode::Char('l') => self.move_cursor_by((1, 0)),
- KeyCode::Esc | KeyCode::Char('v') => {
- self.selection_anchor = None;
- self.mode = SheetViewMode::Normal;
- }
- KeyCode::Char('s') => {
- self.editor.set_text("function(cell)\n\treturn \"\"\nend");
- self.mode = SheetViewMode::Script;
- }
+ Mode::Visual => match event.code {
+ KeyCode::Char('j') => self.move_cursor(CursorMove::Down(1)),
+ KeyCode::Char('k') => self.move_cursor(CursorMove::Up(1)),
+ KeyCode::Char('h') => self.move_cursor(CursorMove::Left(1)),
+ KeyCode::Char('l') => self.move_cursor(CursorMove::Right(1)),
+ KeyCode::Char('v') => self.start_selection(),
+ KeyCode::Char('s') => self.open_editor(),
KeyCode::Char(':') => {
- self.mode = SheetViewMode::Command;
- self.bar.set_input_mode(true)
+ self.set_mode(Mode::Command);
+ self.bar.set_input_mode(true);
}
KeyCode::Enter => {
- self.mode = SheetViewMode::Insert;
+ self.set_mode(Mode::Insert);
self.bar.set_input_mode(true);
}
_ => {}
},
- SheetViewMode::Command => match event.code {
+ Mode::Command => match event.code {
KeyCode::Enter => {
- if let Err(error) = lua::get().load(self.bar.input().unwrap_or("")).exec() {
- self.mode = SheetViewMode::CommandError(error.to_string())
- } else if self.selection_anchor.is_some() {
- self.mode = SheetViewMode::Visual;
- } else {
- self.mode = SheetViewMode::Normal;
+ if let Err(_error) = lua::get().load(self.bar.input().unwrap_or("")).exec() {
+ // TODO: push errors to buffer
}
+ GlobalState::instance_mut().sheetview.cancel_mode();
self.bar.set_input_mode(false);
}
KeyCode::Esc => {
- if self.selection_anchor.is_some() {
- self.mode = SheetViewMode::Visual;
- } else {
- self.mode = SheetViewMode::Normal;
- }
+ GlobalState::instance_mut().sheetview.cancel_mode();
self.bar.set_input_mode(false);
}
_ => self.bar.handle_keyevent(event),
},
- SheetViewMode::CommandError(_) => match event.code {
- KeyCode::Esc | KeyCode::Enter => self.mode = SheetViewMode::Normal,
- _ => {}
- },
- SheetViewMode::Script => match event.code {
- KeyCode::Char('r') if event.modifiers == KeyModifiers::CONTROL => {
- let script = self.editor.text();
-
- let (width, height) = {
- let lock = GlobalState::instance().sheetview.active_sheet().unwrap();
- let sheet = lock.read().unwrap();
- (sheet.width(), sheet.height())
- };
-
- let mut cells = Vec::new();
-
- if self.selection_anchor.is_some() {
- cells = self
- .selection()
- .iter()
- .map(|(r, c)| (*r as usize, *c as usize))
- .collect()
- } else {
- for row in 0..height {
- for column in 0..width {
- cells.push((row, column));
- }
- }
- }
-
- self.mode = SheetViewMode::Processing(Some(
- GlobalState::instance()
- .sheetview
- .active_sheet()
- .unwrap()
- .eval_function(script, cells),
- ));
- }
- KeyCode::Esc => {
- if self.selection_anchor.is_some() {
- self.mode = SheetViewMode::Visual;
- } else {
- self.mode = SheetViewMode::Normal;
- }
- }
- _ => self.editor.handle_key_event(event),
- },
- SheetViewMode::Processing(_) => {
- self.join_process_handle_on_finished();
- }
- SheetViewMode::EditorError(_) => match event.code {
- KeyCode::Esc | KeyCode::Enter => self.mode = SheetViewMode::Script,
- _ => {}
- },
- }
- }
-
- fn is_editor_visible(&self) -> bool {
- matches!(
- self.mode,
- SheetViewMode::Script | SheetViewMode::Processing(_) | SheetViewMode::EditorError(_),
- )
- }
-
- fn is_error_window_visible(&self) -> bool {
- matches!(self.mode, SheetViewMode::EditorError(_))
- }
-
- fn is_progress_visible(&self) -> bool {
- matches!(self.mode, SheetViewMode::Processing(_))
- }
-
- fn areas(&self, area: Rect) -> (Rect, Rect, Rect, Rect, Rect) {
- let mut sheet = area;
- let mut editor = Rect::default();
- let mut error = Rect::default();
- let mut progress = Rect::default();
- let mut command_error = Rect::default();
-
- if let SheetViewMode::CommandError(message) = &self.mode {
- let layout = Layout::vertical([
- Constraint::Min(1),
- Constraint::Length(message.lines().count().min(15) as u16 + 1),
- ])
- .split(sheet);
- sheet = layout[0];
- command_error = layout[1];
}
-
- if self.is_editor_visible() {
- let layout =
- Layout::horizontal([Constraint::Min(1), Constraint::Length(50)]).split(sheet);
- sheet = layout[0];
- editor = layout[1];
-
- if self.is_error_window_visible() {
- let layout =
- Layout::vertical([Constraint::Min(1), Constraint::Length(10)]).split(editor);
- editor = layout[0];
- error = layout[1];
- }
-
- if self.is_progress_visible() {
- let layout =
- Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).split(editor);
- editor = layout[0];
- progress = layout[1];
- }
- }
-
- (sheet, editor, error, progress, command_error)
}
}
@@ -373,168 +163,91 @@ impl Widget for &mut SheetView {
Self: Sized,
{
let theme = GlobalConfig::instance().theme.sheetview.clone();
- let progress;
- let (sheet_area, editor_area, error_area, progress_area, cmd_error_area) = self.areas(area);
- let separator = Block::default().borders(Borders::RIGHT).border_style(
- Style::default()
- .bg(Color::Rgb(29, 32, 33))
- .fg(Color::Rgb(29, 32, 33)),
- );
-
- {
- let lock = GlobalState::instance().sheetview.active_sheet().unwrap();
- let sheet = lock.read().unwrap();
-
- let state = GlobalState::instance();
- let cursor = &state.sheetview.cursor;
-
- let mut sheet_area_inner = self.bar.area(sheet_area);
-
- if self.is_editor_visible() {
- sheet_area_inner = separator.inner(sheet_area_inner);
- }
-
- let viewport_rows = sheet.height().min(sheet_area_inner.height as usize);
- let viewport_columns = sheet
- .width()
- .min((sheet_area_inner.width / DEFAULT_COLUMN_WIDTH) as usize);
- if cursor.y() >= viewport_rows + self.scroll.0 as usize {
- self.scroll.0 = (cursor.y() - viewport_rows) as u16 + 1;
- } else if cursor.y() < self.scroll.0 as usize {
- self.scroll.0 = cursor.y() as u16;
- }
+ let state = GlobalState::instance();
+ let lock = state.sheetview.active_sheet().unwrap();
+ let sheet = lock.read().unwrap();
+ let cursor = &state.sheetview.cursor;
- if cursor.x() >= viewport_columns + self.scroll.1 as usize {
- self.scroll.1 = (cursor.x() - viewport_columns) as u16 + 1;
- } else if cursor.x() < self.scroll.1 as usize {
- self.scroll.1 = cursor.x() as u16;
- }
+ let sheet_area_inner = self.bar.area(area);
- for row in 0..viewport_rows as u16 {
- for column in 0..(viewport_columns + 1) as u16 {
- let (cell_pos_y, cell_pos_x) = (row + self.scroll.0, column + self.scroll.1);
+ let viewport_rows = sheet.height().min(sheet_area_inner.height as usize);
+ let viewport_columns = sheet
+ .width()
+ .min((sheet_area_inner.width / DEFAULT_COLUMN_WIDTH) as usize);
- if let Some(cell_ref) = sheet.get_ref(cell_pos_y as usize, cell_pos_x as usize)
- {
- let cell = cell_ref.value().to_string() + " ";
+ if cursor.y() >= viewport_rows + self.scroll.0 {
+ self.scroll.0 = (cursor.y() - viewport_rows) + 1;
+ } else if cursor.y() < self.scroll.0 {
+ self.scroll.0 = cursor.y();
+ }
- let line =
- if (cell_pos_y, cell_pos_x) == (cursor.y() as u16, cursor.x() as u16) {
- theme
- .cursor
- .get(cell_ref, &lua::get())
- .unwrap_or_default()
- .apply(cell.to_line())
- } else if self.is_in_selection(cell_pos_y, cell_pos_x) {
- theme
- .selection
- .get(cell_ref, &lua::get())
- .unwrap_or_default()
- .apply(cell.to_line())
- } else {
- theme
- .cell
- .get(cell_ref, &lua::get())
- .unwrap_or_default()
- .apply(cell.to_line())
- };
+ if cursor.x() >= viewport_columns + self.scroll.1 as usize {
+ self.scroll.1 = (cursor.x() - viewport_columns) + 1;
+ } else if cursor.x() < self.scroll.1 {
+ self.scroll.1 = cursor.x();
+ }
- let rect = Rect::new(
- sheet_area_inner.x + column * DEFAULT_COLUMN_WIDTH,
- sheet_area_inner.y + row,
- (sheet_area_inner.width - column * DEFAULT_COLUMN_WIDTH)
- .min(DEFAULT_COLUMN_WIDTH),
- 1,
- );
+ for row in 0..viewport_rows {
+ for column in 0..(viewport_columns + 1) {
+ let (cell_pos_y, cell_pos_x) = (row + self.scroll.0, column + self.scroll.1);
- line.render(rect, buf);
- }
- }
- }
+ if let Some(cell_ref) = sheet.get_ref(cell_pos_y as usize, cell_pos_x as usize) {
+ let cell = cell_ref.value().to_string() + " ";
- match &self.mode {
- SheetViewMode::Command => {
- self.bar.set_left(" COMMAND ");
- }
+ let line = if (cell_pos_y, cell_pos_x) == (cursor.y(), cursor.x()) {
+ theme
+ .cursor
+ .get(cell_ref, &lua::get())
+ .unwrap_or_default()
+ .apply(cell.to_line())
+ } else if state.sheetview.selection_contains(cell_pos_y, cell_pos_x) {
+ theme
+ .selection
+ .get(cell_ref, &lua::get())
+ .unwrap_or_default()
+ .apply(cell.to_line())
+ } else {
+ theme
+ .cell
+ .get(cell_ref, &lua::get())
+ .unwrap_or_default()
+ .apply(cell.to_line())
+ };
- SheetViewMode::Insert => {
- self.bar.set_left(" INSERT ");
- }
+ let rect = Rect::new(
+ sheet_area_inner.x + (column as u16) * DEFAULT_COLUMN_WIDTH,
+ sheet_area_inner.y + (row as u16),
+ (sheet_area_inner.width - (column as u16) * DEFAULT_COLUMN_WIDTH)
+ .min(DEFAULT_COLUMN_WIDTH),
+ 1,
+ );
- _ => {
- if self.selection_anchor.is_some() {
- self.bar.set_left(" VISUAL ");
- } else {
- self.bar.set_left(" NORMAL ");
- self.bar.set_middle_alignment(Alignment::Center);
- self.bar.set_middle("Sheet");
- }
+ line.render(rect, buf);
}
}
+ }
- self.bar.render(sheet_area, buf);
-
- if self.is_editor_visible() {
- separator.render(sheet_area, buf);
- self.editor.render(editor_area, buf)
+ match state.sheetview.mode {
+ Mode::Command => {
+ self.bar.set_left(" COMMAND ");
}
- if let SheetViewMode::EditorError(error_msg) = &self.mode {
- let lines = error_msg.lines().collect::<Vec<_>>();
-
- let block = Block::default()
- .title_bottom(" Error ")
- .title_style(Style::default().on_red())
- .border_style(Style::default().black().on_black())
- .borders(Borders::BOTTOM);
- let error_inner = block.inner(error_area);
- block.render(error_area, buf);
-
- let text = Text::from_iter(lines.iter().map(|s| s.to_line().white()));
-
- Paragraph::new(text)
- .wrap(Wrap { trim: true })
- .bg(Color::Rgb(70, 25, 25))
- .render(error_inner, buf);
+ Mode::Insert => {
+ self.bar.set_left(" INSERT ");
}
- if let SheetViewMode::CommandError(message) = &self.mode {
- let lines = message.lines().collect::<Vec<_>>();
-
- let block = Block::default()
- .title_bottom(" Error ")
- .title_style(Style::default().on_red())
- .border_style(Style::default().black().on_black())
- .borders(Borders::BOTTOM);
- let error_inner = block.inner(cmd_error_area);
- block.render(cmd_error_area, buf);
-
- let text = Text::from_iter(lines.iter().map(|s| s.to_line().white()));
-
- Paragraph::new(text)
- .wrap(Wrap { trim: true })
- .bg(Color::Rgb(70, 25, 25))
- .render(error_inner, buf);
+ _ => {
+ if state.sheetview.selection_anchor.is_some() {
+ self.bar.set_left(" VISUAL ");
+ } else {
+ self.bar.set_left(" NORMAL ");
+ self.bar.set_middle_alignment(Alignment::Center);
+ self.bar.set_middle("Sheet");
+ }
}
-
- progress = sheet.progress();
}
- self.join_process_handle_on_finished();
-
- if self.is_progress_visible() {
- let gauge = Gauge::default()
- .gauge_style(
- Style::default()
- .fg(Color::White)
- .bg(Color::DarkGray)
- .add_modifier(Modifier::ITALIC),
- )
- .percent(progress as u16);
-
- Clear.render(progress_area, buf);
- gauge.render(progress_area, buf);
- }
+ self.bar.render(area, buf);
}
}