summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.rs13
-rw-r--r--src/widgets/logview.rs27
-rw-r--r--src/widgets/luaeditor/buffer.rs8
-rw-r--r--src/widgets/luaeditor/cursor.rs2
-rw-r--r--src/widgets/luaeditor/mod.rs71
-rw-r--r--src/widgets/mod.rs1
-rw-r--r--src/widgets/sheetview/mod.rs229
-rw-r--r--src/widgets/statusbar.rs156
8 files changed, 367 insertions, 140 deletions
diff --git a/src/app.rs b/src/app.rs
index b86e6ab..f60c2ca 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -10,16 +10,15 @@ use crate::{
use ratatui::{
crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
prelude::*,
- widgets::Block,
};
-pub struct App<'a> {
+pub struct App {
exit: bool,
- view: SheetView<'a>,
- logview: Option<LogView<'a>>,
+ view: SheetView,
+ logview: Option<LogView>,
}
-impl App<'_> {
+impl App {
pub fn new() -> Self {
let sheet_id = Register::create(200, 1000);
let sheet = Register::get(sheet_id).unwrap();
@@ -81,7 +80,7 @@ impl App<'_> {
match self.logview {
Some(_) => self.logview = None,
None => {
- self.logview = Some(LogView::new().block(Block::bordered().title(" Log ")))
+ self.logview = Some(LogView::new())
}
}
}
@@ -94,7 +93,7 @@ impl App<'_> {
}
}
-impl Widget for &mut App<'_> {
+impl Widget for &mut App {
fn render(self, area: Rect, buf: &mut Buffer)
where
Self: Sized,
diff --git a/src/widgets/logview.rs b/src/widgets/logview.rs
index 4259e35..c5b6432 100644
--- a/src/widgets/logview.rs
+++ b/src/widgets/logview.rs
@@ -1,29 +1,28 @@
-use ratatui::{text::ToSpan, widgets::{block::BlockExt, Block, Widget}};
+use ratatui::{style::{Style, Stylize}, text::ToSpan, widgets::Widget};
use crate::lua::iobuffer::iobuffer;
+use super::statusbar::StatusBar;
-pub struct LogView<'a> {
- block: Option<Block<'a>>
+pub struct LogView {
+ bar: StatusBar,
}
-impl<'a> LogView<'a> {
+impl LogView {
pub fn new() -> Self {
- Self { block: None }
- }
-
- pub fn block(mut self, block: Block<'a>) -> Self {
- self.block = Some(block);
- self
+ Self {
+ bar: StatusBar::new().left(" Log ").left_style(Style::default().on_magenta()),
+ }
}
}
-impl Widget for &mut LogView<'_> {
+impl Widget for &mut LogView {
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);
+ Self: Sized,
+ {
+ self.bar.render(area, buf);
+ let inner_area = self.bar.area(area);
let lines = {
let buffer = iobuffer().read().unwrap();
diff --git a/src/widgets/luaeditor/buffer.rs b/src/widgets/luaeditor/buffer.rs
index 905950e..3f59b6c 100644
--- a/src/widgets/luaeditor/buffer.rs
+++ b/src/widgets/luaeditor/buffer.rs
@@ -2,7 +2,7 @@ use std::str::FromStr;
use super::cursor::{Cursor, CursorMove};
-#[derive(Default, Debug)]
+#[derive(Default, Debug, Clone)]
pub struct Buffer {
lines: Vec<String>,
cursor: Cursor,
@@ -157,6 +157,12 @@ impl Buffer {
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();
+ }
}
impl FromStr for Buffer {
diff --git a/src/widgets/luaeditor/cursor.rs b/src/widgets/luaeditor/cursor.rs
index c4466d0..e051231 100644
--- a/src/widgets/luaeditor/cursor.rs
+++ b/src/widgets/luaeditor/cursor.rs
@@ -11,7 +11,7 @@ pub enum CursorMove {
Jump((usize, usize)),
}
-#[derive(Default, Debug)]
+#[derive(Default, Debug, Clone)]
pub struct Cursor {
x: isize,
y: isize,
diff --git a/src/widgets/luaeditor/mod.rs b/src/widgets/luaeditor/mod.rs
index 63e3d65..98af9e5 100644
--- a/src/widgets/luaeditor/mod.rs
+++ b/src/widgets/luaeditor/mod.rs
@@ -1,11 +1,8 @@
-use std::str::FromStr;
-
use ratatui::{
crossterm::event::{KeyCode, KeyEvent},
- prelude::BlockExt,
- style::Stylize,
- text::ToSpan,
- widgets::{Block, Widget},
+ style::{Style, Stylize},
+ text::{ToLine, ToSpan},
+ widgets::Widget,
};
pub mod buffer;
@@ -18,31 +15,27 @@ use tree_sitter_highlight::HighlightConfiguration;
use self::cursor::CursorMove;
-pub struct LuaEditor<'a> {
- block: Option<Block<'a>>,
+use super::statusbar::StatusBar;
+
+pub struct LuaEditor {
+ bar: StatusBar,
scroll: usize,
buffer: Buffer,
highlight_config: HighlightConfiguration,
}
-impl<'a> LuaEditor<'a> {
- pub fn new<S>(content: S) -> Self
- where
- S: AsRef<str>,
- {
+impl LuaEditor {
+ pub fn new() -> Self {
Self {
- block: None,
+ bar: StatusBar::new()
+ .left(" [No Name] ")
+ .left_style(Style::default().on_magenta()),
scroll: 0,
- buffer: Buffer::from_str(content.as_ref()).unwrap(),
+ buffer: Buffer::new(),
highlight_config: treesitter::new_highlight_configuration(),
}
}
- 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),
@@ -79,30 +72,42 @@ impl<'a> LuaEditor<'a> {
}
}
+ pub fn set_text<S>(&mut self, content: S)
+ where
+ S: AsRef<str>,
+ {
+ self.buffer
+ .set_lines(content.as_ref().lines().map(|s| s.to_string()).collect())
+ }
+
pub fn text(&self) -> String {
self.buffer.lines().join("\n")
}
}
-impl Widget for &mut LuaEditor<'_> {
+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);
+ self.bar.render(area, buf);
+ let inner_area = self.bar.area(area);
let text = self.text();
let highlights = treesitter::highlighter_split(text.as_bytes(), &self.highlight_config);
- let mut span_area = inner_area.clone();
+ let nr_width = (self.buffer.lines().len().to_string().len() + 1).max(4) as u16;
+ let mut text_area = inner_area.clone();
+ text_area.x += nr_width;
+
+ let mut span_area = text_area.clone();
let mut current_line = 0;
for (hl, group) in highlights.iter() {
if *group == "\n" {
if current_line >= self.scroll {
span_area.y += 1;
- span_area.x = inner_area.x;
+ span_area.x = text_area.x;
}
current_line += 1;
@@ -118,7 +123,21 @@ impl Widget for &mut LuaEditor<'_> {
}
}
- let mut cursor_area = inner_area.clone();
+ for i in self.scroll..self.buffer.lines().len() {
+ let mut nr_area = span_area.clone();
+ nr_area.x = inner_area.x;
+ nr_area.width = nr_width - 1;
+ nr_area.y = inner_area.y + (i - self.scroll) as u16;
+ nr_area.height = 1;
+
+ if !inner_area.contains(nr_area.into()) {
+ break;
+ }
+
+ (i + 1).to_line().right_aligned().render(nr_area, buf);
+ }
+
+ let mut cursor_area = text_area.clone();
cursor_area.width = 1;
cursor_area.height = 1;
cursor_area.y += self.buffer.cursor().y() as u16;
diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs
index 48e6854..d746972 100644
--- a/src/widgets/mod.rs
+++ b/src/widgets/mod.rs
@@ -1,3 +1,4 @@
pub mod sheetview;
pub mod luaeditor;
pub mod logview;
+pub mod statusbar;
diff --git a/src/widgets/sheetview/mod.rs b/src/widgets/sheetview/mod.rs
index 37e3d16..40adfc7 100644
--- a/src/widgets/sheetview/mod.rs
+++ b/src/widgets/sheetview/mod.rs
@@ -8,46 +8,55 @@ use ratatui::{
widgets::{Block, Borders, Clear, Gauge, Paragraph, Widget, Wrap},
};
-use crate::{config::GlobalConfig, sheet::{
- eval::EvalFunction,
- register::{Register, SheetId},
- Sheet,
-}};
+use crate::{
+ config::GlobalConfig,
+ sheet::{
+ eval::EvalFunction,
+ register::{Register, SheetId},
+ Sheet,
+ },
+};
-use super::luaeditor::LuaEditor;
+use super::{luaeditor::LuaEditor, statusbar::StatusBar};
const DEFAULT_COLUMN_WIDTH: u16 = 10;
-pub struct SheetView<'a> {
- block: Block<'a>,
- luaeditor: Option<LuaEditor<'a>>,
+enum SheetViewMode {
+ Normal,
+ Insert,
+ Visual,
+ Command,
+ CommandError(String),
+ Script,
+ Processing(Option<JoinHandle<Result<Sheet, String>>>),
+ EditorError(String),
+}
+
+pub struct SheetView {
+ bar: StatusBar,
sheet: SheetId,
- selection: Option<(u16, u16)>,
cursor: (u16, u16),
scroll: (u16, u16),
- error_window: Option<String>,
- process_handle: Option<JoinHandle<Result<Sheet, String>>>,
+ selection_anchor: Option<(u16, u16)>,
+ mode: SheetViewMode,
+ editor: LuaEditor,
}
-impl<'a> SheetView<'a> {
+impl SheetView {
pub fn new(sheet: SheetId) -> Self {
Self {
- block: Block::default()
- .title_bottom(" Sheet ")
- .title_style(Style::default().on_magenta().black())
- .borders(Borders::BOTTOM)
- .border_style(
- Style::default()
- .bg(Color::Rgb(29, 32, 33))
- .fg(Color::Rgb(29, 32, 33)),
- ),
- luaeditor: None,
+ bar: StatusBar::new()
+ .left(" NORMAL ")
+ .left_style(Style::default().on_magenta())
+ .middle("Sheet")
+ .right("")
+ .right_style(Style::default().on_red()),
sheet,
- selection: None,
cursor: (0, 0),
scroll: (0, 0),
- error_window: None,
- process_handle: None,
+ mode: SheetViewMode::Normal,
+ selection_anchor: None,
+ editor: LuaEditor::new(),
}
}
@@ -68,22 +77,6 @@ impl<'a> SheetView<'a> {
.min(sheet.width() as i32 - 1) as u16;
}
- pub fn select_mode(&mut self, flag: bool) {
- if flag {
- self.selection = Some(self.cursor);
- } else {
- self.selection = None;
- }
- }
-
- pub fn is_select_mode(&self) -> bool {
- self.selection.is_some()
- }
-
- pub fn toggle_select_mode(&mut self) {
- self.select_mode(!self.is_select_mode());
- }
-
pub fn selection(&self) -> Vec<(u16, u16)> {
let mut selection = Vec::new();
@@ -110,7 +103,7 @@ impl<'a> SheetView<'a> {
}
fn selection_range(&self) -> Option<((u16, u16), (u16, u16))> {
- if let Some(selection) = self.selection {
+ if let Some(selection) = self.selection_anchor {
let row = if selection.0 > self.cursor.0 {
(self.cursor.0, selection.0)
} else {
@@ -130,32 +123,67 @@ impl<'a> SheetView<'a> {
}
fn join_process_handle_on_finished(&mut self) {
- if let Some(handle) = self.process_handle.take() {
+ if let SheetViewMode::Processing(opt) = &mut self.mode {
+ let handle = opt.take().unwrap();
if handle.is_finished() {
match handle.join().unwrap() {
- Ok(sheet) => Register::get(self.sheet)
- .unwrap()
- .write()
- .unwrap()
- .apply(sheet),
- Err(message) => self.error_window = Some(message),
+ Ok(sheet) => {
+ Register::get(self.sheet)
+ .unwrap()
+ .write()
+ .unwrap()
+ .apply(sheet);
+ self.mode = SheetViewMode::Script;
+ }
+ Err(message) => self.mode = SheetViewMode::EditorError(message),
};
- self.process_handle = None;
} else {
- self.process_handle = Some(handle);
+ self.mode = SheetViewMode::Processing(Some(handle));
}
}
}
pub fn handle_key_event(&mut self, event: KeyEvent) {
- if self.process_handle.is_some() {
- self.join_process_handle_on_finished()
- } else if let Some(_) = &self.error_window {
- self.error_window = None;
- } else if let Some(textarea) = &mut self.luaeditor {
- match event.code {
+ match self.mode {
+ SheetViewMode::Normal => match event.code {
+ KeyCode::Char('j') => self.move_cursor_by((1, 0)),
+ KeyCode::Char('k') => self.move_cursor_by((-1, 0)),
+ KeyCode::Char('h') => self.move_cursor_by((0, -1)),
+ KeyCode::Char('l') => self.move_cursor_by((0, 1)),
+ KeyCode::Char('v') => {
+ self.selection_anchor = Some(self.cursor.clone());
+ self.mode = SheetViewMode::Visual
+ },
+ KeyCode::Char('s') => {
+ self.editor.set_text("function(cell)\n\treturn \"\"\nend");
+ self.mode = SheetViewMode::Script;
+ }
+ _ => {}
+ },
+ SheetViewMode::Insert => {}
+ SheetViewMode::Visual => match event.code {
+ KeyCode::Char('j') => self.move_cursor_by((1, 0)),
+ KeyCode::Char('k') => self.move_cursor_by((-1, 0)),
+ KeyCode::Char('h') => self.move_cursor_by((0, -1)),
+ KeyCode::Char('l') => self.move_cursor_by((0, 1)),
+ KeyCode::Esc | KeyCode::Char('v') => {
+ self.selection_anchor = None;
+ self.mode = SheetViewMode::Normal;
+ },
+ KeyCode::Char('s') => {
+ self.editor.set_text("function(cell)\n\treturn \"\"\nend");
+ self.mode = SheetViewMode::Script;
+ }
+ _ => {}
+ },
+ SheetViewMode::Command => {}
+ SheetViewMode::CommandError(_) => match event.code {
+ KeyCode::Esc => self.mode = SheetViewMode::Normal,
+ _ => {}
+ },
+ SheetViewMode::Script => match event.code {
KeyCode::Char('r') if event.modifiers == KeyModifiers::CONTROL => {
- let script = textarea.text();
+ let script = self.editor.text();
let (width, height) = {
let lock = Register::get(self.sheet).unwrap();
@@ -165,7 +193,7 @@ impl<'a> SheetView<'a> {
let mut cells = Vec::new();
- if let Some(_) = self.selection {
+ if let Some(_) = self.selection_anchor {
cells = self
.selection()
.iter()
@@ -179,32 +207,51 @@ impl<'a> SheetView<'a> {
}
}
- self.process_handle = Some(
+ self.mode = SheetViewMode::Processing(Some(
Register::get(self.sheet)
.unwrap()
.eval_function(script, cells),
- );
+ ));
}
KeyCode::Esc => {
- self.luaeditor = None;
- }
- _ => {
- textarea.handle_key_event(event);
+ if self.selection_anchor.is_some() {
+ self.mode = SheetViewMode::Visual;
+ } else {
+ self.mode = SheetViewMode::Normal;
+ }
}
+ _ => self.editor.handle_key_event(event),
+ },
+ SheetViewMode::Processing(_) => {
+ self.join_process_handle_on_finished();
}
- } else {
- match event.code {
- KeyCode::Char('j') => self.move_cursor_by((1, 0)),
- KeyCode::Char('k') => self.move_cursor_by((-1, 0)),
- KeyCode::Char('h') => self.move_cursor_by((0, -1)),
- KeyCode::Char('l') => self.move_cursor_by((0, 1)),
- KeyCode::Char('v') => self.toggle_select_mode(),
- KeyCode::Char('s') => {
- let editor = LuaEditor::new("function(cell)\n\treturn \"\"\nend");
- self.luaeditor = Some(editor)
- }
+ SheetViewMode::EditorError(_) => match event.code {
+ KeyCode::Esc => self.mode = SheetViewMode::Script,
_ => {}
- }
+ },
+ }
+ }
+
+ fn is_editor_visible(&self) -> bool {
+ match self.mode {
+ SheetViewMode::Script
+ | SheetViewMode::Processing(_)
+ | SheetViewMode::EditorError(_) => true,
+ _ => false,
+ }
+ }
+
+ fn is_error_window_visible(&self) -> bool {
+ match self.mode {
+ SheetViewMode::EditorError(_) => true,
+ _ => false,
+ }
+ }
+
+ fn is_progress_visible(&self) -> bool {
+ match self.mode {
+ SheetViewMode::Processing(_) => true,
+ _ => false,
}
}
@@ -214,20 +261,20 @@ impl<'a> SheetView<'a> {
let mut error = Rect::default();
let mut progress = Rect::default();
- if self.luaeditor.is_some() {
+ if self.is_editor_visible() {
let layout =
Layout::horizontal([Constraint::Min(1), Constraint::Length(50)]).split(sheet);
sheet = layout[0];
editor = layout[1];
- if self.error_window.is_some() {
+ if self.is_error_window_visible() {
let layout =
Layout::vertical([Constraint::Min(1), Constraint::Length(10)]).split(editor);
editor = layout[0];
error = layout[1];
}
- if self.process_handle.is_some() {
+ if self.is_progress_visible() {
let layout =
Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).split(editor);
editor = layout[0];
@@ -239,7 +286,7 @@ impl<'a> SheetView<'a> {
}
}
-impl Widget for &mut SheetView<'_> {
+impl Widget for &mut SheetView {
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
where
Self: Sized,
@@ -257,13 +304,13 @@ impl Widget for &mut SheetView<'_> {
let lock = Register::get(self.sheet).unwrap();
let sheet = lock.read().unwrap();
- let mut sheet_area_inner = self.block.inner(sheet_area);
+ let mut sheet_area_inner = self.bar.area(sheet_area);
- if self.luaeditor.is_some() {
+ if self.is_editor_visible() {
sheet_area_inner = separator.inner(sheet_area_inner);
}
- let viewport_rows = sheet.height().min(sheet_area.height as usize);
+ let viewport_rows = sheet.height().min(sheet_area_inner.height as usize);
let viewport_columns = sheet
.width()
.min((sheet_area_inner.width / DEFAULT_COLUMN_WIDTH) as usize);
@@ -310,14 +357,14 @@ impl Widget for &mut SheetView<'_> {
}
}
- self.block.clone().render(sheet_area, buf);
+ self.bar.render(sheet_area, buf);
- if let Some(textarea) = &mut self.luaeditor {
- separator.render(self.block.inner(sheet_area), buf);
- textarea.render(editor_area, buf)
+ if self.is_editor_visible() {
+ separator.render(sheet_area, buf);
+ self.editor.render(editor_area, buf)
}
- if let Some(error_msg) = &self.error_window {
+ if let SheetViewMode::EditorError(error_msg) = &self.mode {
let lines = error_msg.lines().collect::<Vec<_>>();
let block = Block::default()
@@ -341,7 +388,7 @@ impl Widget for &mut SheetView<'_> {
self.join_process_handle_on_finished();
- if self.process_handle.is_some() {
+ if self.is_progress_visible() {
let gauge = Gauge::default()
.gauge_style(
Style::default()
diff --git a/src/widgets/statusbar.rs b/src/widgets/statusbar.rs
new file mode 100644
index 0000000..97b514d
--- /dev/null
+++ b/src/widgets/statusbar.rs
@@ -0,0 +1,156 @@
+use ratatui::{layout::{Alignment, Rect}, style::Style, text::ToLine, widgets::Widget};
+
+#[derive(Clone)]
+pub struct StatusBar {
+ left: String,
+ left_style: Style,
+ middle: String,
+ middle_style: Style,
+ middle_alignment: Alignment,
+ right: String,
+ right_style: Style,
+}
+
+impl StatusBar {
+ pub fn new() -> Self {
+ Self {
+ left: String::new(),
+ left_style: Style::default(),
+ middle: String::new(),
+ middle_style: Style::default(),
+ middle_alignment: Alignment::Center,
+ right: String::new(),
+ right_style: Style::default(),
+ }
+ }
+
+ pub fn left<S>(mut self, text: S) -> Self
+ where
+ S: AsRef<str>,
+ {
+ self.left = text.as_ref().to_string();
+ self
+ }
+
+ pub fn middle<S>(mut self, text: S) -> Self
+ where
+ S: AsRef<str>,
+ {
+ self.middle = text.as_ref().to_string();
+ self
+ }
+
+ pub fn right<S>(mut self, text: S) -> Self
+ where
+ S: AsRef<str>,
+ {
+ self.right = text.as_ref().to_string();
+ self
+ }
+
+ pub fn left_style(mut self, style: Style) -> Self {
+ self.left_style = style;
+ self
+ }
+
+ pub fn middle_style(mut self, style: Style) -> Self {
+ self.left_style = style;
+ self
+ }
+
+ pub fn middle_alignment(mut self, alignment: Alignment) -> Self {
+ self.middle_alignment = alignment;
+ self
+ }
+
+ pub fn right_style(mut self, style: Style) -> Self {
+ self.right_style = style;
+ self
+ }
+
+ pub fn set_left<S>(&mut self, text: S)
+ where
+ S: AsRef<str>,
+ {
+ self.left = text.as_ref().to_string();
+ }
+
+ pub fn set_middle<S>(&mut self, text: S)
+ where
+ S: AsRef<str>,
+ {
+ self.middle = text.as_ref().to_string();
+ }
+
+ pub fn set_right<S>(&mut self, text: S)
+ where
+ S: AsRef<str>,
+ {
+ self.right = text.as_ref().to_string();
+ }
+
+ pub fn set_left_style(&mut self, style: Style) {
+ self.left_style = style;
+ }
+
+ pub fn set_middle_style(&mut self, style: Style) {
+ self.left_style = style;
+ }
+
+ pub fn set_right_style(&mut self, style: Style) {
+ self.right_style = style;
+ }
+
+ pub fn set_middle_alignment(&mut self, alignment: Alignment) {
+ self.middle_alignment = alignment;
+ }
+
+ pub fn area(&self, rect: Rect) -> Rect {
+ let mut inner = rect.clone();
+ inner.height -= 1;
+ inner
+ }
+
+ fn layout(&self, mut area: Rect) -> (Rect, Rect, Rect) {
+ area.y += area.height - 1;
+ area.height = 1;
+
+ let mut left = area.clone();
+ let mut right = area.clone();
+ let mut middle = area.clone();
+
+ left.width = self.left.len() as u16;
+ right.width = self.right.len() as u16;
+ right.x = area.x + area.width - right.width;
+
+ middle.x = left.x + left.width;
+ middle.width = area.width - left.width - right.width;
+
+ (left, middle, right)
+ }
+}
+
+impl Widget for &mut StatusBar {
+ fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer)
+ where
+ Self: Sized,
+ {
+ let (left, middle, right) = self.layout(area);
+
+ self.left
+ .to_line()
+ .left_aligned()
+ .style(self.left_style)
+ .render(left, buf);
+ self.middle
+ .to_line()
+ .alignment(self.middle_alignment)
+ .style(self.middle_style)
+ .render(middle, buf);
+ self.right
+ .to_line()
+ .right_aligned()
+ .style(self.right_style)
+ .render(right, buf);
+ }
+}