From 58b305b9f6d13007d2ea62775054c95177f81092 Mon Sep 17 00:00:00 2001 From: Nathan Reiner Date: Thu, 25 Jul 2024 00:40:37 +0200 Subject: first sketch --- src/widgets/luaeditor/buffer.rs | 134 ++++++++++++++++++++++++++++++++++++++++ src/widgets/luaeditor/cursor.rs | 102 ++++++++++++++++++++++++++++++ src/widgets/luaeditor/mod.rs | 132 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 368 insertions(+) create mode 100644 src/widgets/luaeditor/buffer.rs create mode 100644 src/widgets/luaeditor/cursor.rs create mode 100644 src/widgets/luaeditor/mod.rs (limited to 'src/widgets/luaeditor') 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, + 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 { + &self.lines + } +} + +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/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>, + scroll: usize, + buffer: Buffer, +} + +impl<'a> LuaEditor<'a> { + pub fn new(content: S) -> Self + where + S: AsRef, + { + Self { + block: None, + scroll: 0, + buffer: Buffer::from_str(content.as_ref()).unwrap(), + } + } + + pub fn block(mut self, block: Option>) -> 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); + } +} -- cgit v1.2.3-70-g09d2