From 04a5a938994ddb95cfaa9a74b180e457d3a2b5d0 Mon Sep 17 00:00:00 2001 From: Nathan Reiner Date: Fri, 2 Aug 2024 11:38:19 +0200 Subject: implement new lua interface --- src/app.rs | 76 ++++-- src/cursor.rs | 4 +- src/lua/mod.rs | 9 +- src/sheet/eval.rs | 55 ---- src/sheet/foreach.rs | 37 +++ src/sheet/luaref.rs | 82 ++++++ src/sheet/mod.rs | 65 +---- src/sheet/register.rs | 2 +- src/state/editor/buffer.rs | 191 +++++++++++++ src/state/editor/mod.rs | 57 ++++ src/state/log.rs | 35 +++ src/state/mod.rs | 36 ++- src/state/sheetview.rs | 97 ------- src/state/view/mod.rs | 186 +++++++++++++ src/state/view/mode.rs | 48 ++++ src/state/window.rs | 47 ++++ src/widgets/logview.rs | 4 +- src/widgets/luaeditor/buffer.rs | 180 ------------- src/widgets/luaeditor/mod.rs | 96 +++---- src/widgets/sheetview/mod.rs | 577 ++++++++++------------------------------ 20 files changed, 982 insertions(+), 902 deletions(-) delete mode 100644 src/sheet/eval.rs create mode 100644 src/sheet/foreach.rs create mode 100644 src/sheet/luaref.rs create mode 100644 src/state/editor/buffer.rs create mode 100644 src/state/editor/mod.rs create mode 100644 src/state/log.rs delete mode 100644 src/state/sheetview.rs create mode 100644 src/state/view/mod.rs create mode 100644 src/state/view/mode.rs create mode 100644 src/state/window.rs delete mode 100644 src/widgets/luaeditor/buffer.rs diff --git a/src/app.rs b/src/app.rs index b245b5b..89b19dc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,11 @@ use std::{io, time::Duration}; use crate::{ - config, lua, sheet::register::Register, state::GlobalState, tui, widgets::{logview::LogView, sheetview::SheetView} + config, lua, + sheet::register::Register, + state::{window::Window, GlobalState}, + tui, + widgets::{logview::LogView, luaeditor::LuaEditor, sheetview::SheetView}, }; use ratatui::{ @@ -13,19 +17,22 @@ use ratatui::{ pub struct App { exit: bool, view: SheetView, - logview: Option, + editor: LuaEditor, + logview: LogView, } impl App { pub fn new() -> Self { let sheet_id = Register::create(10, 50); - GlobalState::instance_mut().sheetview.set_active_sheet(Some(sheet_id)); - let view = SheetView::new(); + GlobalState::instance_mut() + .sheetview + .set_active_sheet(Some(sheet_id)); Self { exit: false, - view, - logview: None, + view: SheetView::new(), + editor: LuaEditor::new(), + logview: LogView::new(), } } @@ -61,23 +68,48 @@ impl App { } fn handle_key_event(&mut self, key_event: KeyEvent) { + let focus = { GlobalState::instance().active_window }; match key_event.code { KeyCode::Char('q') if key_event.modifiers == KeyModifiers::CONTROL => self.exit(), KeyCode::Char('l') if key_event.modifiers == KeyModifiers::CONTROL => { - match self.logview { - Some(_) => self.logview = None, - None => { - self.logview = Some(LogView::new()) - } - } + let mut state = GlobalState::instance_mut(); + state.log.visible = !state.log.visible; } - _ => self.view.handle_key_event(key_event), + _ => { + match focus { + Window::View => self.view.handle_key_event(key_event), + Window::Editor => self.editor.handle_key_event(key_event), + Window::Log => self.logview.handle_key_event(key_event), + Window::Error => {}, + } + }, } } fn exit(&mut self) { self.exit = true; } + + fn area(&self, area: Rect) -> (Rect, Option, Option) { + let state = GlobalState::instance(); + let mut view = area; + let mut editor = None; + let mut log = None; + + if state.log.visible { + let layout = Layout::vertical([Constraint::Min(1), Constraint::Length(15)]).split(view); + view = layout[0]; + log = Some(layout[1]); + } + + if state.editor.visible { + let layout = Layout::horizontal([Constraint::Min(1), Constraint::Length(80)]).split(view); + view = layout[0]; + editor = Some(layout[1]); + } + + (view, editor, log) + } } impl Widget for &mut App { @@ -85,14 +117,16 @@ impl Widget for &mut App { where Self: Sized, { - match &mut self.logview { - Some(logview) => { - let layout = - Layout::vertical([Constraint::Min(2), Constraint::Length(15)]).split(area); - self.view.render(layout[0], buf); - logview.render(layout[1], buf); - } - None => self.view.render(area, buf), + let (view, editor, log) = self.area(area); + + self.view.render(view, buf); + + if let Some(editor) = editor { + self.editor.render(editor, buf); + } + + if let Some(log) = log { + self.logview.render(log, buf); } } } diff --git a/src/cursor.rs b/src/cursor.rs index ad6703e..8ab538b 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -30,13 +30,13 @@ impl Cursor { } } - pub fn with_position(mut self, x: isize, y: isize) -> Self { + pub const fn with_position(mut self, x: isize, y: isize) -> Self { self.x = x; self.y = y; self } - pub fn with_max(mut self, x: isize, y: isize) -> Self { + pub const fn with_max(mut self, x: isize, y: isize) -> Self { self.x_max = x; self.y_max = y; self diff --git a/src/lua/mod.rs b/src/lua/mod.rs index fe3555e..7d61e5b 100644 --- a/src/lua/mod.rs +++ b/src/lua/mod.rs @@ -6,7 +6,14 @@ use std::{ use lazy_static::lazy_static; use mlua::prelude::*; -use crate::{config::{theme::{sheetview::SheetViewTheme, Theme}, GlobalConfig}, sheet::cell::CellRef, state::{sheetview::SheetViewState, GlobalState}}; +use crate::{ + config::{ + theme::{sheetview::SheetViewTheme, Theme}, + GlobalConfig, + }, + sheet::cell::CellRef, + state::{view::SheetViewState, GlobalState}, +}; pub mod evalsto; pub mod iobuffer; diff --git a/src/sheet/eval.rs b/src/sheet/eval.rs deleted file mode 100644 index 2108e48..0000000 --- a/src/sheet/eval.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::{ - sync::{Arc, RwLock}, - thread::JoinHandle, -}; - -use mlua::prelude::*; - -use crate::lua; - -use super::{cell::Cell, Sheet}; - -pub trait EvalFunction { - fn eval_function( - &self, - func: String, - range: Vec<(usize, usize)>, - ) -> JoinHandle>; -} - -impl EvalFunction for Arc> { - fn eval_function( - &self, - func_text: String, - range: Vec<(usize, usize)>, - ) -> JoinHandle> { - *self.read().unwrap().progress.lock().unwrap() = 0; - let this = Arc::clone(self); - std::thread::spawn(move || { - let lua = lua::get(); - let read_sheet = this.read().unwrap(); - let mut sheet = read_sheet.clone(); - let result = lua.load(func_text).eval::(); - - match result { - Ok(func) => { - let read_sheet = this.read().unwrap(); - - for (i, (row, column)) in range.iter().enumerate() { - *read_sheet.progress.lock().unwrap() = (i * 100 / range.len()) as u8; - - let cellref = read_sheet.get_ref(*row, *column); - - match func.call::<_, Cell>(cellref) { - Ok(cell) => sheet.set_cell(*row, *column, cell), - Err(error) => return Err(error.to_string()), - } - } - } - Err(error) => return Err(error.to_string()), - } - - Ok(sheet) - }) - } -} diff --git a/src/sheet/foreach.rs b/src/sheet/foreach.rs new file mode 100644 index 0000000..a2a3f90 --- /dev/null +++ b/src/sheet/foreach.rs @@ -0,0 +1,37 @@ +use std::sync::{Arc, RwLock}; + +use mlua::prelude::*; + +use super::{cell::Cell, Sheet}; + +pub trait LuaForEach { + fn lua_foreach<'lua>( + &self, + func: LuaFunction<'lua>, + range: Vec<(usize, usize)>, + ) -> Result<(), LuaError>; +} + +impl LuaForEach for Arc> { + fn lua_foreach<'lua>( + &self, + func: LuaFunction<'lua>, + range: Vec<(usize, usize)>, + ) -> Result<(), LuaError> { + let mut this = self.write().unwrap(); + let mut sheet = this.clone(); + + for (row, column) in range.iter() { + let cellref = this.get_ref(*row, *column); + + match func.call::<_, Cell>(cellref) { + Ok(cell) => sheet.set_cell(*row, *column, cell), + Err(error) => return Err(error), + } + } + + this.apply(sheet); + + Ok(()) + } +} diff --git a/src/sheet/luaref.rs b/src/sheet/luaref.rs new file mode 100644 index 0000000..fa4b8f9 --- /dev/null +++ b/src/sheet/luaref.rs @@ -0,0 +1,82 @@ +use crate::state::GlobalState; + +use super::{ + foreach::LuaForEach, + register::{Register, SheetId}, +}; +use mlua::prelude::*; + +pub struct SheetLuaRef { + id: SheetId, +} + +impl SheetLuaRef { + pub fn new(id: SheetId) -> Self { + Self { id } + } + + pub fn id(&self) -> SheetId { + self.id + } +} + +impl LuaUserData for SheetLuaRef { + fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("width", |_, luaref| { + Ok(Register::get(luaref.id).unwrap().read().unwrap().width()) + }); + + fields.add_field_method_get("height", |_, luaref| { + Ok(Register::get(luaref.id).unwrap().read().unwrap().height()) + }); + } + + fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method_mut("cell", |lua, luaref, (row, column): (usize, usize)| { + if let Some(rw) = Register::get(luaref.id) { + let sheet = rw.read().unwrap(); + Ok(sheet.get_ref(row, column).into_lua(lua).unwrap()) + } else { + Ok(LuaValue::Nil) + } + }); + + methods.add_method_mut("foreach", |_, luaref, func: LuaFunction| { + let range: Vec<_> = { + let state = GlobalState::instance(); + let lock = state.sheetview.active_sheet().unwrap(); + let sheet = lock.read().unwrap(); + + if sheet.id() != luaref.id() { + return Ok(()); + } + + let (width, height) = (sheet.width(), sheet.height()); + + if state.sheetview.selection_anchor.is_some() { + state.sheetview.selection() + } else { + let mut cells = Vec::new(); + for row in 0..height { + for column in 0..width { + cells.push((row, column)); + } + } + cells + } + }; + + Register::get(luaref.id).unwrap().lua_foreach(func, range) + }) + } +} + +impl<'lua> FromLua<'lua> for SheetLuaRef { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + if let Some(ud) = value.as_userdata() { + ud.take() + } else { + Err(LuaError::runtime("failed to parse sheet")) + } + } +} diff --git a/src/sheet/mod.rs b/src/sheet/mod.rs index 501b71e..5f28e07 100644 --- a/src/sheet/mod.rs +++ b/src/sheet/mod.rs @@ -1,18 +1,14 @@ -use std::sync::Mutex; - use cell::{Cell, CellRef}; -use mlua::prelude::*; -use register::{Register, SheetId}; pub mod cell; -pub mod eval; +pub mod foreach; pub mod register; +pub mod luaref; #[derive(Debug, Default)] pub struct Sheet { id: register::SheetId, rows: Vec>, - progress: Mutex, } impl Sheet { @@ -20,7 +16,6 @@ impl Sheet { Self { id, rows: vec![vec![Cell::new_empty(); width]; height], - progress: Mutex::new(0), } } @@ -41,9 +36,8 @@ impl Sheet { } pub fn get_ref(&self, row: usize, column: usize) -> Option { - self.get_cell(row, column).map(|cell| { - unsafe { CellRef::new(self.id, row, column, cell.clone()) } - }) + self.get_cell(row, column) + .map(|cell| unsafe { CellRef::new(self.id, row, column, cell.clone()) }) } pub fn height(&self) -> usize { @@ -74,10 +68,6 @@ impl Sheet { pub fn apply(&mut self, other: Sheet) { self.rows = other.rows; } - - pub fn progress(&self) -> u8 { - *self.progress.lock().unwrap() - } } impl Clone for Sheet { @@ -85,54 +75,7 @@ impl Clone for Sheet { Sheet { id: self.id, rows: self.rows.clone(), - progress: Mutex::new(0), } } } -pub struct SheetLuaRef { - id: SheetId, -} - -impl SheetLuaRef { - pub fn new(id: SheetId) -> Self { - Self { id } - } - - pub fn id(&self) -> SheetId { - self.id - } -} - -impl LuaUserData for SheetLuaRef { - fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) { - fields.add_field_method_get("width", |_, luaref| { - Ok(Register::get(luaref.id).unwrap().read().unwrap().width()) - }); - - fields.add_field_method_get("height", |_, luaref| { - Ok(Register::get(luaref.id).unwrap().read().unwrap().height()) - }); - } - - fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method_mut("cell", |lua, luaref, (row, column): (usize, usize)| { - if let Some(rw) = Register::get(luaref.id) { - let sheet = rw.read().unwrap(); - Ok(sheet.get_ref(row, column).into_lua(lua).unwrap()) - } else { - Ok(LuaValue::Nil) - } - }) - } -} - -impl<'lua> FromLua<'lua> for SheetLuaRef { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - if let Some(ud) = value.as_userdata() { - ud.take() - } else { - Err(LuaError::runtime("failed to parse sheet")) - } - } -} diff --git a/src/sheet/register.rs b/src/sheet/register.rs index 039c6f0..c43720d 100644 --- a/src/sheet/register.rs +++ b/src/sheet/register.rs @@ -2,7 +2,7 @@ use std::sync::{Arc, RwLock}; use mlua::{Table, UserData, Value}; -use super::{cell::Cell, Sheet, SheetLuaRef}; +use super::{cell::Cell, Sheet, luaref::SheetLuaRef}; static REGISTER: RwLock>>> = RwLock::new(Vec::new()); diff --git a/src/state/editor/buffer.rs b/src/state/editor/buffer.rs new file mode 100644 index 0000000..fa26eb7 --- /dev/null +++ b/src/state/editor/buffer.rs @@ -0,0 +1,191 @@ +use std::str::FromStr; + +use crate::cursor::{Cursor, CursorMove}; + +#[derive(Default, Debug, Clone)] +pub struct Buffer { + lines: Vec, + cursor: Cursor, +} + +impl Buffer { + pub const fn new() -> Self { + Self { + lines: Vec::new(), + cursor: Cursor::new().with_position(0, 0).with_max(0, 0), + } + } + + fn refresh_line_max(&mut self) { + self.cursor.set_x_max(self.lines[self.cursor.y()].len()) + } + + fn refresh_buffer_max(&mut self) { + self.cursor.set_y_max(self.lines.len() - 1); + } + + fn refresh_max(&mut self) { + self.refresh_line_max(); + self.refresh_buffer_max(); + } + + fn end_balance(&self, level: usize) -> isize { + let str = "\t".repeat(level); + let start_count = self + .lines + .iter() + .filter(|s| { + s.starts_with(&(str.clone() + "if ")) + || s.starts_with(&(str.clone() + "for ")) + || s.starts_with(&(str.clone() + "while ")) + || (s.starts_with(&str) && s.contains("function(") && s.ends_with(')')) + }) + .count(); + + let end_count = self + .lines + .iter() + .filter(|s| s.starts_with(&(str.clone() + "end ")) || **s == (str.clone() + "end")) + .count(); + + (end_count as isize) - (start_count as isize) + } + + pub fn insert(&mut self, c: char) { + match c { + '\n' => { + let (a, b) = { + let line = self.current_line(); + let (a, b) = line.split_at(self.cursor.x()); + (a.to_string(), b.to_string()) + }; + + let indent_normalized = a.replace(" ", "\t"); + let indent = indent_normalized.len() - indent_normalized.trim_start().len(); + let l = a.trim(); + let insert_end = (l.starts_with("if ") && l.ends_with(" then")) + || ((l.starts_with("for ") || l.starts_with("while ")) && l.ends_with(" do")) + || (l.contains("function(") && l.ends_with(')')); + + let extra_indent = if insert_end { 1 } else { 0 }; + + { + *self.current_line_mut() = a; + } + self.cursor.move_unchecked(CursorMove::Down(1)); + self.cursor.move_unchecked(CursorMove::Begin); + self.lines + .insert(self.cursor().y(), "\t".repeat(indent + extra_indent) + &b); + + if insert_end && self.end_balance(indent) < 0 { + self.lines + .insert(self.cursor().y() + 1, "\t".repeat(indent) + "end"); + } + + self.refresh_max(); + self.cursor + .move_checked(CursorMove::Right(indent + extra_indent)); + } + _ => { + let x = self.cursor().x(); + self.current_line_mut().insert(x, c); + self.refresh_line_max(); + self.cursor.move_checked(CursorMove::Right(1)); + } + } + } + + pub fn delete(&mut self) { + if self.cursor.is_at_start() && !self.cursor.is_at_top() { + let old_line = self.current_line_mut().clone(); + self.lines.remove(self.cursor.y()); + self.cursor.move_checked(CursorMove::Up(1)); + self.refresh_line_max(); + + let new_x = self.current_line().len(); + self.current_line_mut().push_str(&old_line); + self.cursor + .move_checked(CursorMove::Jump((new_x, self.cursor.y()))); + + self.refresh_buffer_max() + } else { + let x = self.cursor.x() - 1; + self.current_line_mut().remove(x); + self.refresh_line_max(); + self.cursor.move_checked(CursorMove::Left(1)); + } + } + + pub fn line(&self, index: usize) -> Option<&String> { + self.lines.get(index) + } + + fn line_mut(&mut self, index: usize) -> Option<&mut String> { + self.lines.get_mut(index) + } + + pub fn current_line(&self) -> &String { + self.line(self.cursor.y()).unwrap() + } + + fn current_line_mut(&mut self) -> &mut String { + self.line_mut(self.cursor.y()).unwrap() + } + + pub fn cursor(&self) -> &Cursor { + &self.cursor + } + + pub fn move_cursor(&mut self, m: CursorMove) { + match m { + CursorMove::Up(_) + | CursorMove::Down(_) + | CursorMove::Top + | CursorMove::Bottom + | CursorMove::Relative(_) + | CursorMove::Jump(_) => { + self.cursor.move_checked(m); + self.cursor.set_x_max(self.current_line().len()); + self.cursor.correct_cursor_position(); + } + CursorMove::Left(_) | CursorMove::Right(_) | CursorMove::Begin | CursorMove::End => { + self.cursor.move_checked(m) + } + } + } + + pub fn lines(&self) -> &Vec { + &self.lines + } + + pub fn set_lines(&mut self, lines: Vec) { + self.lines = lines; + self.cursor.move_checked(CursorMove::Jump((0, 0))); + self.refresh_max(); + } + + pub fn set_lines_from_string(&mut self, content: S) + where + S: AsRef, + { + self.set_lines(content.as_ref().lines().map(|s| s.to_string()).collect()) + } + + pub fn as_string(&self) -> String { + self.lines.join("\n") + } +} + +impl FromStr for Buffer { + type Err = (); + + fn from_str(s: &str) -> Result { + let lines: Vec<_> = s.lines().map(|s| s.to_string()).collect(); + let mut buffer = Self::new(); + buffer.lines = lines; + + buffer.refresh_max(); + + Ok(buffer) + } +} diff --git a/src/state/editor/mod.rs b/src/state/editor/mod.rs new file mode 100644 index 0000000..6fbd2b9 --- /dev/null +++ b/src/state/editor/mod.rs @@ -0,0 +1,57 @@ +use self::buffer::Buffer; + +use super::GlobalState; +use mlua::{Lua, UserData}; + +pub mod buffer; + +#[derive(Default, Debug)] +pub struct EditorState { + pub visible: bool, + pub buffer: Buffer, +} + +macro_rules! cfg { + () => { + GlobalState::instance().editor + }; +} + +macro_rules! cfg_mut { + () => { + GlobalState::instance_mut().editor + }; +} + +impl EditorState { + pub const fn new() -> Self { + Self { + visible: false, + buffer: Buffer::new(), + } + } + + pub fn run(lua: &Lua) { + let script = { cfg!().buffer.as_string() }; + if let Err(_error) = lua.load(script).exec() { + // TODO: add error handling + } + } +} + +impl UserData for EditorState { + fn add_fields<'lua, F: mlua::prelude::LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_function_get("visible", |_, _| Ok(cfg!().visible)); + fields.add_field_function_set("visible", |_, _, visible: bool| { + cfg_mut!().visible = visible; + Ok(()) + }) + } + + fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_function("run", |lua, _: ()| { + EditorState::run(lua); + Ok(()) + }) + } +} diff --git a/src/state/log.rs b/src/state/log.rs new file mode 100644 index 0000000..5afa8ca --- /dev/null +++ b/src/state/log.rs @@ -0,0 +1,35 @@ +use mlua::UserData; +use super::GlobalState; + +#[derive(Debug, Default)] +pub struct LogState { + pub visible: bool, +} + +impl LogState { + pub const fn new() -> Self { + Self { visible: false } + } +} + +macro_rules! cfg { + () => { + GlobalState::instance().log + }; +} + +macro_rules! cfg_mut { + () => { + GlobalState::instance_mut().log + }; +} + +impl UserData for LogState { + fn add_fields<'lua, F: mlua::prelude::LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_function_get("visible", |_, _| Ok(cfg!().visible)); + fields.add_field_function_set("visible", |_, _, visible: bool| { + cfg_mut!().visible = visible; + Ok(()) + }); + } +} diff --git a/src/state/mod.rs b/src/state/mod.rs index 5efd770..5200da9 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -2,12 +2,19 @@ use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use mlua::{UserData, UserDataFields}; +use self::log::LogState; -pub mod sheetview; +pub mod editor; +pub mod log; +pub mod view; +pub mod window; #[derive(Debug, Default)] pub struct GlobalState { - pub sheetview: sheetview::SheetViewState, + pub sheetview: view::SheetViewState, + pub editor: editor::EditorState, + pub active_window: window::Window, + pub log: log::LogState, } static GLOBAL_STATE: RwLock = RwLock::new(GlobalState::new()); @@ -16,7 +23,10 @@ const DUMMY_STATE: GlobalState = GlobalState::new(); impl GlobalState { const fn new() -> Self { Self { - sheetview: sheetview::SheetViewState::new() + sheetview: view::SheetViewState::new(), + editor: editor::EditorState::new(), + log: LogState::new(), + active_window: window::Window::View, } } @@ -27,10 +37,28 @@ impl GlobalState { pub fn instance_mut() -> RwLockWriteGuard<'static, Self> { GLOBAL_STATE.write().unwrap() } + + pub fn set_focus(&mut self, win: window::Window) { + self.active_window = win; + + match win { + window::Window::Editor => self.editor.visible = true, + window::Window::Log => self.log.visible = true, + _ => {} + } + } } impl UserData for GlobalState { fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) { - fields.add_field_function_get("sheetview", |_, _| Ok(DUMMY_STATE.sheetview)) + fields.add_field_function_get("view", |_, _| Ok(DUMMY_STATE.sheetview)); + fields.add_field_function_get("editor", |_, _| Ok(DUMMY_STATE.editor)); + fields.add_field_function_get("active_window", |_, _| { + Ok(GlobalState::instance().active_window) + }); + fields.add_field_function_set("active_window", |_, _, win: window::Window| { + GlobalState::instance_mut().set_focus(win); + Ok(()) + }); } } diff --git a/src/state/sheetview.rs b/src/state/sheetview.rs deleted file mode 100644 index 8108a1d..0000000 --- a/src/state/sheetview.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::sync::{Arc, RwLock}; - -use mlua::{IntoLua, UserData, Value}; - -use super::GlobalState; -use crate::{ - cursor::{Cursor, CursorMove}, - sheet::{ - register::{Register, SheetId}, - Sheet, SheetLuaRef, - }, -}; - -#[derive(Default, Debug)] -pub struct SheetViewState { - pub cursor: Cursor, - active_sheet: Option, -} - -impl SheetViewState { - pub const fn new() -> Self { - Self { - cursor: Cursor::new(), - active_sheet: None, - } - } - - pub fn set_active_sheet(&mut self, sheet: Option) { - if let Some(id) = sheet { - if let Some(lock) = Register::get(id) { - let sheet = lock.read().unwrap(); - self.cursor.set_x_max(sheet.width()); - self.cursor.set_y_max(sheet.height()); - } - } - - self.active_sheet = sheet - } - - pub fn active_sheet(&self) -> Option>> { - if let Some(id) = self.active_sheet { - Register::get(id) - } else { - None - } - } -} - -macro_rules! cfg { - () => { - GlobalState::instance().sheetview - }; -} - -macro_rules! cfg_mut { - () => { - GlobalState::instance_mut().sheetview - }; -} - -impl UserData for SheetViewState { - fn add_fields<'lua, F: mlua::prelude::LuaUserDataFields<'lua, Self>>(fields: &mut F) { - fields.add_field_function_get("active", |lua, _| { - if let Some(id) = cfg!().active_sheet { - SheetLuaRef::new(id).into_lua(lua) - } else { - Ok(Value::Nil) - } - }); - - fields.add_field_function_set("active", |_, _, sheet: Option| { - if let Some(r) = sheet { - cfg_mut!().active_sheet = Some(r.id()) - } else { - cfg_mut!().active_sheet = None - } - - Ok(()) - }); - - fields.add_field_function_get("cursor", |lua, _| { - let table = lua.create_table()?; - table.set("row", cfg!().cursor.y())?; - table.set("column", cfg!().cursor.x())?; - Ok(table) - }) - } - - fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_function("move_cursor", |_, (row, column): (usize, usize)| { - cfg_mut!() - .cursor - .move_checked(CursorMove::Jump((row, column))); - Ok(()) - }) - } -} diff --git a/src/state/view/mod.rs b/src/state/view/mod.rs new file mode 100644 index 0000000..73f02ab --- /dev/null +++ b/src/state/view/mod.rs @@ -0,0 +1,186 @@ +use std::sync::{Arc, RwLock}; + +use mlua::{IntoLua, UserData, Value}; + +use self::mode::Mode; + +use super::GlobalState; +use crate::{ + cursor::{Cursor, CursorMove}, + sheet::{ + luaref::SheetLuaRef, + register::{Register, SheetId}, + Sheet, + }, +}; + +pub mod mode; + +#[derive(Default, Debug)] +pub struct SheetViewState { + pub cursor: Cursor, + active_sheet: Option, + pub mode: Mode, + pub selection_anchor: Option<(usize, usize)>, +} + +impl SheetViewState { + pub const fn new() -> Self { + Self { + cursor: Cursor::new(), + active_sheet: None, + mode: Mode::Normal, + selection_anchor: None, + } + } + + pub fn set_active_sheet(&mut self, sheet: Option) { + if let Some(id) = sheet { + if let Some(lock) = Register::get(id) { + let sheet = lock.read().unwrap(); + self.cursor.set_x_max(sheet.width()); + self.cursor.set_y_max(sheet.height()); + } + } + + self.active_sheet = sheet + } + + pub fn active_sheet(&self) -> Option>> { + if let Some(id) = self.active_sheet { + Register::get(id) + } else { + None + } + } + + pub fn cancel_mode(&mut self) { + if self.selection_anchor.is_some() { + self.mode = Mode::Visual; + } else { + self.mode = Mode::Normal; + } + } + + fn selection_range(&self) -> Option<((usize, usize), (usize, usize))> { + if let Some(selection) = self.selection_anchor { + let row = if selection.0 as usize > self.cursor.y() { + (self.cursor.y(), selection.0) + } else { + (selection.0, self.cursor.y()) + }; + + let column = if selection.1 as usize > self.cursor.x() { + (self.cursor.x(), selection.1) + } else { + (selection.1, self.cursor.x()) + }; + + Some((row, column)) + } else { + None + } + } + + pub fn selection(&self) -> Vec<(usize, usize)> { + 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 + } + + pub fn selection_or_cursor(&self) -> Vec<(usize, usize)> { + if self.selection_anchor.is_some() { + self.selection() + } else { + vec![(self.cursor.y(), self.cursor.x())] + } + } + + pub fn selection_contains(&self, row: usize, column: usize) -> 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 + } + } +} + +macro_rules! cfg { + () => { + GlobalState::instance().sheetview + }; +} + +macro_rules! cfg_mut { + () => { + GlobalState::instance_mut().sheetview + }; +} + +impl UserData for SheetViewState { + fn add_fields<'lua, F: mlua::prelude::LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_function_get("active", |lua, _| { + if let Some(id) = cfg!().active_sheet { + SheetLuaRef::new(id).into_lua(lua) + } else { + Ok(Value::Nil) + } + }); + + fields.add_field_function_set("active", |_, _, sheet: Option| { + if let Some(r) = sheet { + cfg_mut!().active_sheet = Some(r.id()) + } else { + cfg_mut!().active_sheet = None + } + + Ok(()) + }); + + fields.add_field_function_get("cursor", |lua, _| { + let table = lua.create_table()?; + table.set("row", cfg!().cursor.y())?; + table.set("column", cfg!().cursor.x())?; + Ok(table) + }); + + fields.add_field_function_get("mode", |_, _| Ok(cfg!().mode)); + fields.add_field_function_set("mode", |_, _, mode: Mode| { + let this = &mut cfg_mut!(); + match mode { + Mode::Visual => match this.mode { + Mode::Visual => {} + _ => this.selection_anchor = Some((this.cursor.y(), this.cursor.x())), + }, + _ => this.selection_anchor = None, + } + this.mode = mode; + Ok(()) + }) + } + + fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_function("move_cursor", |_, (row, column): (usize, usize)| { + cfg_mut!() + .cursor + .move_checked(CursorMove::Jump((row, column))); + Ok(()) + }); + + methods.add_function("cancel_mode", |_, _: ()| { + cfg_mut!().cancel_mode(); + Ok(()) + }) + } +} diff --git a/src/state/view/mode.rs b/src/state/view/mode.rs new file mode 100644 index 0000000..3fa9c63 --- /dev/null +++ b/src/state/view/mode.rs @@ -0,0 +1,48 @@ +use mlua::{FromLua, IntoLua}; + +#[derive(Default, Debug, Clone, Copy)] +pub enum Mode { + #[default] + Normal, + Visual, + Insert, + Command, +} + +impl<'lua> FromLua<'lua> for Mode { + fn from_lua( + value: mlua::prelude::LuaValue<'lua>, + _lua: &'lua mlua::prelude::Lua, + ) -> mlua::prelude::LuaResult { + if value.is_string() { + match value.as_str().unwrap().to_lowercase().as_ref() { + "normal" => Ok(Mode::Normal), + "visual" => Ok(Mode::Visual), + "insert" => Ok(Mode::Visual), + "command" => Ok(Mode::Visual), + _ => Err(mlua::Error::runtime( + "mode needs to be 'normal', 'visual' or 'insert'", + )), + } + } else { + Err(mlua::Error::runtime( + "mode needs to be 'normal', 'visual' or 'insert'", + )) + } + } +} + +impl<'lua> IntoLua<'lua> for Mode { + fn into_lua( + self, + lua: &'lua mlua::prelude::Lua, + ) -> mlua::prelude::LuaResult> { + match self { + Mode::Normal => "normal", + Mode::Visual => "insert", + Mode::Insert => "visual", + Mode::Command => "command", + } + .into_lua(lua) + } +} diff --git a/src/state/window.rs b/src/state/window.rs new file mode 100644 index 0000000..288b5b4 --- /dev/null +++ b/src/state/window.rs @@ -0,0 +1,47 @@ +use mlua::{FromLua, IntoLua}; + +#[derive(Default, Debug, Clone, Copy)] +pub enum Window { + #[default] + View, + Editor, + Log, + Error, +} + +impl<'lua> FromLua<'lua> for Window { + fn from_lua( + value: mlua::prelude::LuaValue<'lua>, + _lua: &'lua mlua::prelude::Lua, + ) -> mlua::prelude::LuaResult { + if value.is_string() { + match value.as_str().unwrap().to_lowercase().as_ref() { + "view" => Ok(Window::View), + "editor" => Ok(Window::Editor), + "log" => Ok(Window::Log), + "error" => Ok(Window::Error), + _ => Err(mlua::Error::runtime( + "window needs to be 'view', 'editor', 'log' or 'error'", + )), + } + } else { + Err(mlua::Error::runtime( + "window needs to be 'view', 'editor', 'log' or 'error'", + )) + } + } +} + +impl<'lua> IntoLua<'lua> for Window { + fn into_lua( + self, + lua: &'lua mlua::prelude::Lua, + ) -> mlua::prelude::LuaResult> { + match self { + Window::View => "view", + Window::Editor => "editor", + Window::Log => "log", + Window::Error => "error", + }.into_lua(lua) + } +} diff --git a/src/widgets/logview.rs b/src/widgets/logview.rs index 35c7bed..5d30b17 100644 --- a/src/widgets/logview.rs +++ b/src/widgets/logview.rs @@ -1,4 +1,4 @@ -use ratatui::{style::{Style, Stylize}, text::ToSpan, widgets::Widget}; +use ratatui::{crossterm::event::KeyEvent, style::{Style, Stylize}, text::ToSpan, widgets::Widget}; use crate::lua::iobuffer::iobuffer; @@ -15,6 +15,8 @@ impl LogView { bar: StatusBar::new().left(" Log ").left_style(Style::default().on_magenta()), } } + + pub fn handle_key_event(&mut self, _event: KeyEvent) {} } impl Widget for &mut LogView { diff --git a/src/widgets/luaeditor/buffer.rs b/src/widgets/luaeditor/buffer.rs deleted file mode 100644 index 1427bdb..0000000 --- a/src/widgets/luaeditor/buffer.rs +++ /dev/null @@ -1,180 +0,0 @@ -use std::str::FromStr; - -use crate::cursor::{Cursor, CursorMove}; - -#[derive(Default, Debug, Clone)] -pub struct Buffer { - lines: Vec, - cursor: Cursor, -} - -impl Buffer { - pub fn new() -> Self { - Self { - lines: Vec::new(), - cursor: Cursor::new().with_position(0, 0).with_max(0, 0), - } - } - - fn refresh_line_max(&mut self) { - self.cursor.set_x_max(self.lines[self.cursor.y()].len()) - } - - fn refresh_buffer_max(&mut self) { - self.cursor.set_y_max(self.lines.len() - 1); - } - - fn refresh_max(&mut self) { - self.refresh_line_max(); - self.refresh_buffer_max(); - } - - fn end_balance(&self, level: usize) -> isize { - let str = "\t".repeat(level); - let start_count = self - .lines - .iter() - .filter(|s| { - s.starts_with(&(str.clone() + "if ")) - || s.starts_with(&(str.clone() + "for ")) - || s.starts_with(&(str.clone() + "while ")) - || (s.starts_with(&str) && s.contains("function(") && s.ends_with(')')) - }) - .count(); - - let end_count = self - .lines - .iter() - .filter(|s| s.starts_with(&(str.clone() + "end ")) || **s == (str.clone() + "end")) - .count(); - - (end_count as isize) - (start_count as isize) - } - - pub fn insert(&mut self, c: char) { - match c { - '\n' => { - let (a, b) = { - let line = self.current_line(); - let (a, b) = line.split_at(self.cursor.x()); - (a.to_string(), b.to_string()) - }; - - let indent_normalized = a.replace(" ", "\t"); - let indent = indent_normalized.len() - indent_normalized.trim_start().len(); - let l = a.trim(); - let insert_end = (l.starts_with("if ") && l.ends_with(" then")) - || ((l.starts_with("for ") || l.starts_with("while ")) && l.ends_with(" do")) - || (l.contains("function(") && l.ends_with(')')); - - let extra_indent = if insert_end { 1 } else { 0 }; - - { - *self.current_line_mut() = a; - } - self.cursor.move_unchecked(CursorMove::Down(1)); - self.cursor.move_unchecked(CursorMove::Begin); - self.lines - .insert(self.cursor().y(), "\t".repeat(indent + extra_indent) + &b); - - if insert_end && self.end_balance(indent) < 0 { - self.lines - .insert(self.cursor().y() + 1, "\t".repeat(indent) + "end"); - } - - self.refresh_max(); - self.cursor - .move_checked(CursorMove::Right(indent + extra_indent)); - } - _ => { - let x = self.cursor().x(); - self.current_line_mut().insert(x, c); - self.refresh_line_max(); - self.cursor.move_checked(CursorMove::Right(1)); - } - } - } - - pub fn delete(&mut self) { - if self.cursor.is_at_start() && !self.cursor.is_at_top() { - let old_line = self.current_line_mut().clone(); - self.lines.remove(self.cursor.y()); - self.cursor.move_checked(CursorMove::Up(1)); - self.refresh_line_max(); - - let new_x = self.current_line().len(); - self.current_line_mut().push_str(&old_line); - self.cursor - .move_checked(CursorMove::Jump((new_x, self.cursor.y()))); - - self.refresh_buffer_max() - } else { - let x = self.cursor.x() - 1; - self.current_line_mut().remove(x); - self.refresh_line_max(); - self.cursor.move_checked(CursorMove::Left(1)); - } - } - - pub fn line(&self, index: usize) -> Option<&String> { - self.lines.get(index) - } - - fn line_mut(&mut self, index: usize) -> Option<&mut String> { - self.lines.get_mut(index) - } - - pub fn current_line(&self) -> &String { - self.line(self.cursor.y()).unwrap() - } - - fn current_line_mut(&mut self) -> &mut String { - self.line_mut(self.cursor.y()).unwrap() - } - - pub fn cursor(&self) -> &Cursor { - &self.cursor - } - - pub fn move_cursor(&mut self, m: CursorMove) { - match m { - CursorMove::Up(_) - | CursorMove::Down(_) - | CursorMove::Top - | CursorMove::Bottom - | CursorMove::Relative(_) - | CursorMove::Jump(_) => { - self.cursor.move_checked(m); - self.cursor.set_x_max(self.current_line().len()); - self.cursor.correct_cursor_position(); - } - CursorMove::Left(_) | CursorMove::Right(_) | CursorMove::Begin | CursorMove::End => { - self.cursor.move_checked(m) - } - } - } - - pub fn lines(&self) -> &Vec { - &self.lines - } - - pub fn set_lines(&mut self, lines: Vec) { - self.lines = lines; - self.cursor.move_checked(CursorMove::Jump((0, 0))); - self.refresh_max(); - } -} - -impl FromStr for Buffer { - type Err = (); - - fn from_str(s: &str) -> Result { - let lines: Vec<_> = s.lines().map(|s| s.to_string()).collect(); - let mut buffer = Self::new(); - buffer.lines = lines; - - buffer.refresh_max(); - - Ok(buffer) - } -} diff --git a/src/widgets/luaeditor/mod.rs b/src/widgets/luaeditor/mod.rs index 6fc8436..de35c3d 100644 --- a/src/widgets/luaeditor/mod.rs +++ b/src/widgets/luaeditor/mod.rs @@ -1,25 +1,22 @@ use ratatui::{ - crossterm::event::{KeyCode, KeyEvent}, + crossterm::event::{KeyCode, KeyEvent, KeyModifiers}, style::{Style, Stylize}, text::{ToLine, ToSpan}, widgets::Widget, }; - -pub mod buffer; -pub mod theme; -pub mod treesitter; - -use buffer::Buffer; use tree_sitter_highlight::HighlightConfiguration; -use crate::cursor::CursorMove; +use crate::state::{editor::EditorState, window::Window, GlobalState}; +use crate::{cursor::CursorMove, lua}; use super::statusbar::StatusBar; +pub mod theme; +pub mod treesitter; + pub struct LuaEditor { bar: StatusBar, scroll: usize, - buffer: Buffer, highlight_config: HighlightConfiguration, } @@ -30,35 +27,53 @@ impl LuaEditor { .left(" [No Name] ") .left_style(Style::default().on_magenta()), scroll: 0, - buffer: Buffer::new(), highlight_config: treesitter::new_highlight_configuration(), } } pub fn handle_key_event(&mut self, event: KeyEvent) { match event.code { - KeyCode::Char(c) => self.buffer.insert(c), + KeyCode::Char('r') if event.modifiers == KeyModifiers::CONTROL => { + EditorState::run(&lua::get()) + } + KeyCode::Char(c) => GlobalState::instance_mut().editor.buffer.insert(c), KeyCode::Backspace => { - self.buffer.delete(); + GlobalState::instance_mut().editor.buffer.delete(); } KeyCode::Enter => { - self.buffer.insert('\n'); + GlobalState::instance_mut().editor.buffer.insert('\n'); } - KeyCode::Left => self.buffer.move_cursor(CursorMove::Left(1)), - KeyCode::Right => self.buffer.move_cursor(CursorMove::Right(1)), - KeyCode::Up => self.buffer.move_cursor(CursorMove::Up(1)), - KeyCode::Down => self.buffer.move_cursor(CursorMove::Down(1)), + KeyCode::Left => GlobalState::instance_mut() + .editor + .buffer + .move_cursor(CursorMove::Left(1)), + KeyCode::Right => GlobalState::instance_mut() + .editor + .buffer + .move_cursor(CursorMove::Right(1)), + KeyCode::Up => GlobalState::instance_mut() + .editor + .buffer + .move_cursor(CursorMove::Up(1)), + KeyCode::Down => GlobalState::instance_mut() + .editor + .buffer + .move_cursor(CursorMove::Down(1)), KeyCode::Home => {} KeyCode::End => {} KeyCode::PageUp => {} KeyCode::PageDown => {} - KeyCode::Tab => self.buffer.insert('\t'), + KeyCode::Tab => GlobalState::instance_mut().editor.buffer.insert('\t'), KeyCode::BackTab => {} KeyCode::Delete => {} KeyCode::Insert => {} KeyCode::F(_) => {} KeyCode::Null => {} - KeyCode::Esc => {} + KeyCode::Esc => { + let mut state = GlobalState::instance_mut(); + state.editor.visible = false; + state.set_focus(Window::View) + } KeyCode::CapsLock => {} KeyCode::ScrollLock => {} KeyCode::NumLock => {} @@ -70,18 +85,6 @@ impl LuaEditor { KeyCode::Modifier(_) => {} } } - - pub fn set_text(&mut self, content: S) - where - S: AsRef, - { - self.buffer - .set_lines(content.as_ref().lines().map(|s| s.to_string()).collect()) - } - - pub fn text(&self) -> String { - self.buffer.lines().join("\n") - } } impl Widget for &mut LuaEditor { @@ -89,13 +92,16 @@ impl Widget for &mut LuaEditor { where Self: Sized, { + let state = GlobalState::instance(); + let buffer = &state.editor.buffer; + self.bar.render(area, buf); let inner_area = self.bar.area(area); - let text = self.text(); + let text = buffer.as_string(); let highlights = treesitter::highlighter_split(text.as_bytes(), &self.highlight_config); - let nr_width = (self.buffer.lines().len().to_string().len() + 1).max(4) as u16; + let nr_width = (buffer.lines().len().to_string().len() + 1).max(4) as u16; let mut text_area = inner_area; text_area.x += nr_width; @@ -122,7 +128,7 @@ impl Widget for &mut LuaEditor { } } - for i in self.scroll..self.buffer.lines().len() { + for i in self.scroll..buffer.lines().len() { let mut nr_area = span_area; nr_area.x = inner_area.x; nr_area.width = nr_width - 1; @@ -139,13 +145,10 @@ impl Widget for &mut LuaEditor { let mut cursor_area = text_area; cursor_area.width = 1; cursor_area.height = 1; - cursor_area.y += self.buffer.cursor().y() as u16; - cursor_area.x += self.buffer.cursor().x() as u16; + cursor_area.y += buffer.cursor().y() as u16; + cursor_area.x += buffer.cursor().x() as u16; - let (first, _) = self - .buffer - .current_line() - .split_at(self.buffer.cursor().x()); + let (first, _) = buffer.current_line().split_at(buffer.cursor().x()); for c in first.chars() { if c == '\t' { @@ -153,19 +156,19 @@ impl Widget for &mut LuaEditor { } } - if self.scroll > self.buffer.cursor().y() { - self.scroll = self.buffer.cursor().y(); - } else if inner_area.height as usize + self.scroll - 1 < self.buffer.cursor().y() { - self.scroll = self.buffer.cursor().y() - inner_area.height as usize + 1; + if self.scroll > buffer.cursor().y() { + self.scroll = buffer.cursor().y(); + } else if inner_area.height as usize + self.scroll - 1 < buffer.cursor().y() { + self.scroll = buffer.cursor().y() - inner_area.height as usize + 1; } cursor_area.y -= self.scroll as u16; if inner_area.contains(cursor_area.into()) { - self.buffer + buffer .current_line() .chars() - .nth(self.buffer.cursor().x()) + .nth(buffer.cursor().x()) .map(|c| if c == '\t' { ' ' } else { c }) .unwrap_or(' ') .to_span() @@ -181,7 +184,6 @@ impl Default for LuaEditor { highlight_config: treesitter::new_highlight_configuration(), bar: StatusBar::default(), scroll: 0, - buffer: Buffer::default(), } } } 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>>), - 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,315 +34,127 @@ 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) - } } impl Widget for &mut SheetView { @@ -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; - } - 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 state = GlobalState::instance(); + let lock = state.sheetview.active_sheet().unwrap(); + let sheet = lock.read().unwrap(); + let cursor = &state.sheetview.cursor; - 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 sheet_area_inner = self.bar.area(area); - 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() + " "; + 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); - 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.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 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, - ); + 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(); + } - line.render(rect, buf); - } - } - } + 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); + + 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() + " "; + + 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()) + }; - match &self.mode { - SheetViewMode::Command => { - self.bar.set_left(" COMMAND "); - } + 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, + ); - SheetViewMode::Insert => { - self.bar.set_left(" INSERT "); - } - - _ => { - 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::>(); - - 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::>(); - - 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); } } -- cgit v1.2.3-70-g09d2