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/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 +++++++++++ 8 files changed, 596 insertions(+), 101 deletions(-) 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 (limited to 'src/state') 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) + } +} -- cgit v1.2.3-70-g09d2