summaryrefslogtreecommitdiff
path: root/src/state
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2024-08-02 11:38:19 +0200
committerNathan Reiner <nathan@nathanreiner.xyz>2024-08-02 11:38:19 +0200
commit04a5a938994ddb95cfaa9a74b180e457d3a2b5d0 (patch)
tree31ce9525ed3f3423678397323b65c910d63eadb1 /src/state
parentfe0938b1de0c46fc2afcaa3dcd6a0f4ec870d21a (diff)
implement new lua interface
Diffstat (limited to 'src/state')
-rw-r--r--src/state/editor/buffer.rs191
-rw-r--r--src/state/editor/mod.rs57
-rw-r--r--src/state/log.rs35
-rw-r--r--src/state/mod.rs36
-rw-r--r--src/state/sheetview.rs97
-rw-r--r--src/state/view/mod.rs186
-rw-r--r--src/state/view/mode.rs48
-rw-r--r--src/state/window.rs47
8 files changed, 596 insertions, 101 deletions
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<String>,
+ 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<String> {
+ &self.lines
+ }
+
+ pub fn set_lines(&mut self, lines: Vec<String>) {
+ self.lines = lines;
+ self.cursor.move_checked(CursorMove::Jump((0, 0)));
+ self.refresh_max();
+ }
+
+ pub fn set_lines_from_string<S>(&mut self, content: S)
+ where
+ S: AsRef<str>,
+ {
+ 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<Self, Self::Err> {
+ 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<GlobalState> = 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<SheetId>,
-}
-
-impl SheetViewState {
- pub const fn new() -> Self {
- Self {
- cursor: Cursor::new(),
- active_sheet: None,
- }
- }
-
- pub fn set_active_sheet(&mut self, sheet: Option<SheetId>) {
- 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<Arc<RwLock<Sheet>>> {
- 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<SheetLuaRef>| {
- 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<SheetId>,
+ 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<SheetId>) {
+ 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<Arc<RwLock<Sheet>>> {
+ 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<SheetLuaRef>| {
+ 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<Self> {
+ 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<mlua::prelude::LuaValue<'lua>> {
+ 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<Self> {
+ 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<mlua::prelude::LuaValue<'lua>> {
+ match self {
+ Window::View => "view",
+ Window::Editor => "editor",
+ Window::Log => "log",
+ Window::Error => "error",
+ }.into_lua(lua)
+ }
+}