summaryrefslogtreecommitdiff
path: root/src/widgets/luaeditor
diff options
context:
space:
mode:
Diffstat (limited to 'src/widgets/luaeditor')
-rw-r--r--src/widgets/luaeditor/buffer.rs134
-rw-r--r--src/widgets/luaeditor/cursor.rs102
-rw-r--r--src/widgets/luaeditor/mod.rs132
3 files changed, 368 insertions, 0 deletions
diff --git a/src/widgets/luaeditor/buffer.rs b/src/widgets/luaeditor/buffer.rs
new file mode 100644
index 0000000..bdbee8d
--- /dev/null
+++ b/src/widgets/luaeditor/buffer.rs
@@ -0,0 +1,134 @@
+use std::str::FromStr;
+
+use super::cursor::{Cursor, CursorMove};
+
+#[derive(Default, Debug)]
+pub struct Buffer {
+ lines: Vec<String>,
+ 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() as usize].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();
+ }
+
+ 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())
+ };
+
+ {
+ *self.current_line_mut() = a;
+ }
+ self.cursor.move_unchecked(CursorMove::Down(1));
+ self.cursor.move_unchecked(CursorMove::Begin);
+ self.lines.insert(self.cursor().y(), b);
+ self.refresh_max()
+ }
+ _ => {
+ 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() as usize).unwrap()
+ }
+
+ fn current_line_mut(&mut self) -> &mut String {
+ self.line_mut(self.cursor.y() as usize).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::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
+ }
+}
+
+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/widgets/luaeditor/cursor.rs b/src/widgets/luaeditor/cursor.rs
new file mode 100644
index 0000000..c4466d0
--- /dev/null
+++ b/src/widgets/luaeditor/cursor.rs
@@ -0,0 +1,102 @@
+
+pub enum CursorMove {
+ Up(usize),
+ Down(usize),
+ Left(usize),
+ Right(usize),
+ Top,
+ Bottom,
+ Begin,
+ End,
+ Jump((usize, usize)),
+}
+
+#[derive(Default, Debug)]
+pub struct Cursor {
+ x: isize,
+ y: isize,
+ x_max: isize,
+ y_max: isize,
+}
+
+impl Cursor {
+ pub fn new() -> Self {
+ Self {
+ x: 0,
+ y: 0,
+ x_max: 0,
+ y_max: 0,
+ }
+ }
+
+ pub 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 {
+ 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;
+ }
+ };
+ }
+
+ 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
+ }
+}
diff --git a/src/widgets/luaeditor/mod.rs b/src/widgets/luaeditor/mod.rs
new file mode 100644
index 0000000..723d8b5
--- /dev/null
+++ b/src/widgets/luaeditor/mod.rs
@@ -0,0 +1,132 @@
+use std::str::FromStr;
+
+use ratatui::{
+ crossterm::event::{KeyCode, KeyEvent},
+ prelude::BlockExt,
+ style::Stylize,
+ text::ToSpan,
+ widgets::{Block, Widget},
+};
+
+pub mod buffer;
+pub mod cursor;
+
+use buffer::Buffer;
+
+use self::cursor::CursorMove;
+
+#[derive(Debug)]
+pub struct LuaEditor<'a> {
+ block: Option<Block<'a>>,
+ scroll: usize,
+ buffer: Buffer,
+}
+
+impl<'a> LuaEditor<'a> {
+ pub fn new<S>(content: S) -> Self
+ where
+ S: AsRef<str>,
+ {
+ Self {
+ block: None,
+ scroll: 0,
+ buffer: Buffer::from_str(content.as_ref()).unwrap(),
+ }
+ }
+
+ pub fn block(mut self, block: Option<Block<'a>>) -> Self {
+ self.block = block;
+ self
+ }
+
+ pub fn handle_key_event(&mut self, event: KeyEvent) {
+ match event.code {
+ KeyCode::Char(c) => self.buffer.insert(c),
+ KeyCode::Backspace => {
+ self.buffer.delete();
+ }
+ KeyCode::Enter => {
+ self.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::Home => {}
+ KeyCode::End => {}
+ KeyCode::PageUp => {}
+ KeyCode::PageDown => {}
+ KeyCode::Tab => self.buffer.insert('\t'),
+ KeyCode::BackTab => {}
+ KeyCode::Delete => {}
+ KeyCode::Insert => {}
+ KeyCode::F(_) => {}
+ KeyCode::Null => {}
+ KeyCode::Esc => {}
+ KeyCode::CapsLock => {}
+ KeyCode::ScrollLock => {}
+ KeyCode::NumLock => {}
+ KeyCode::PrintScreen => {}
+ KeyCode::Pause => {}
+ KeyCode::Menu => {}
+ KeyCode::KeypadBegin => {}
+ KeyCode::Media(_) => {}
+ KeyCode::Modifier(_) => {}
+ }
+ }
+
+ pub fn text(&self) -> String {
+ self.buffer.lines().join("\n")
+ }
+}
+
+impl Widget for &mut LuaEditor<'_> {
+ fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
+ where
+ Self: Sized,
+ {
+ self.block.render(area, buf);
+ let inner_area = self.block.inner_if_some(area);
+
+ for (i, line) in self.buffer.lines().iter().enumerate().skip(self.scroll) {
+ let replace = &line.replace('\t', " ");
+ let span = replace.to_span();
+ let mut span_area = inner_area.clone();
+ span_area.height = 1;
+ span_area.y += i as u16;
+
+ if !inner_area.contains(span_area.into()) {
+ break;
+ }
+
+ span.render(span_area, buf)
+ }
+
+ let mut cursor_area = inner_area.clone();
+ 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;
+
+ let (first, _) = self
+ .buffer
+ .current_line()
+ .split_at(self.buffer.cursor().x() as usize);
+
+ for c in first.chars() {
+ if c == '\t' {
+ cursor_area.x += 1;
+ }
+ }
+
+ self.buffer
+ .current_line()
+ .chars()
+ .nth(self.buffer.cursor().x() as usize)
+ .map(|c| if c == '\t' { ' ' } else { c })
+ .unwrap_or(' ')
+ .to_span()
+ .reversed()
+ .render(cursor_area, buf);
+ }
+}