use mlua::FromLuaMulti; pub enum CursorMove { Up(usize), Down(usize), Left(usize), Right(usize), Top, Bottom, Begin, End, Jump((usize, usize)), Relative((isize, isize)), } impl<'lua> FromLuaMulti<'lua> for CursorMove { fn from_lua_multi( values: mlua::prelude::LuaMultiValue<'lua>, _lua: &'lua mlua::prelude::Lua, ) -> mlua::prelude::LuaResult { let first = values .get(1) .filter(|v| v.is_integer() || v.is_number()) .map(|v| { if v.is_number() { v.as_number().unwrap_or(0.0) } else { v.as_number().unwrap_or(0.0) } }) .map(|n| n as usize); let second = values .get(2) .filter(|v| v.is_integer() || v.is_number()) .map(|v| { if v.is_number() { v.as_number().unwrap() } else { v.as_number().unwrap() } }) .map(|n| n as usize); if let Some(value) = values.get(0) { if value.is_string() { match value.as_str().unwrap().to_lowercase().as_str() { "up" => return Ok(CursorMove::Up(first.unwrap_or(1))), "down" => return Ok(CursorMove::Down(first.unwrap_or(1))), "left" => return Ok(CursorMove::Left(first.unwrap_or(1))), "right" => return Ok(CursorMove::Right(first.unwrap_or(1))), "top" => return Ok(CursorMove::Top), "bottom" => return Ok(CursorMove::Bottom), "begin" => return Ok(CursorMove::Begin), "end" => return Ok(CursorMove::End), "jump" => { if first.is_some() && second.is_some() { return Ok(CursorMove::Jump((first.unwrap(), second.unwrap()))); } return Err(mlua::Error::runtime("invalid row, column values")); } "relative" => { if first.is_some() && second.is_some() { return Ok(CursorMove::Jump((first.unwrap(), second.unwrap()))); } return Err(mlua::Error::runtime("invalid row, column values")); } _ => {} } } } Err(mlua::Error::runtime("invalid first value")) } } #[derive(Default, Debug, Clone)] pub struct Cursor { x: isize, y: isize, x_max: isize, y_max: isize, } impl Cursor { pub const fn new() -> Self { Self { x: 0, y: 0, x_max: 0, y_max: 0, } } pub const fn with_position(mut self, x: isize, y: isize) -> Self { self.x = x; self.y = y; self } pub const fn with_max(mut self, x: isize, y: isize) -> Self { self.x_max = x; self.y_max = y; self } pub fn x(&self) -> usize { self.x as usize } pub fn y(&self) -> usize { self.y as usize } pub fn set_x_max(&mut self, max: usize) { self.x_max = max as isize; } pub fn set_y_max(&mut self, max: usize) { self.y_max = max as isize; } pub fn move_checked(&mut self, m: CursorMove) { self.move_unchecked(m); self.correct_cursor_position(); } pub fn move_unchecked(&mut self, m: CursorMove) { match m { CursorMove::Up(n) => self.y -= n as isize, CursorMove::Down(n) => self.y += n as isize, CursorMove::Left(n) => self.x -= n as isize, CursorMove::Right(n) => self.x += n as isize, CursorMove::Top => self.y = 0, CursorMove::Bottom => self.y = self.y_max, CursorMove::Begin => self.x = 0, CursorMove::End => self.x = self.x_max, CursorMove::Jump((x, y)) => { self.x = x as isize; self.y = y as isize; } CursorMove::Relative((x, y)) => { self.x += x; self.y += y; } }; } pub fn correct_cursor_position(&mut self) { self.y = self.y.max(0).min(self.y_max); self.x = self.x.max(0).min(self.x_max); } pub fn is_at_start(&self) -> bool { self.x == 0 } pub fn is_at_end(&self) -> bool { self.x == self.x_max } pub fn is_at_top(&self) -> bool { self.y == 0 } pub fn is_at_bottom(&self) -> bool { self.y == self.y_max } }