use ratatui::{ crossterm::event::{KeyCode, KeyEvent}, layout::Rect, text::{ToLine, ToSpan}, widgets::{Paragraph, Widget}, }; use tree_sitter_highlight::HighlightConfiguration; use crate::{config::keymap::EditorKeyMap, lua}; use crate::{ config::{theme::editor::bar::EditorBarTheme, GlobalConfig}, state::{editor::bar::EditorBarState, GlobalState}, tuicursor::TuiCursor, }; use super::statusbar::StatusBar; pub mod theme; pub mod treesitter; pub struct LuaEditor { bar: StatusBar, scroll: usize, highlight_config: HighlightConfiguration, } impl LuaEditor { pub fn new() -> Self { Self { bar: StatusBar::new(), scroll: 0, highlight_config: treesitter::new_highlight_configuration(), } } pub fn handle_key_event(&mut self, event: KeyEvent) { let r = EditorKeyMap::handle(event); if r.unwrap_or(true) { match event.code { KeyCode::Char(c) => GlobalState::instance_mut().editor.buffer.insert(c), KeyCode::Backspace => { GlobalState::instance_mut().editor.buffer.delete(); } KeyCode::Enter => { GlobalState::instance_mut().editor.buffer.insert('\n'); } _ => {} } } } pub fn render_cursor(&self) -> Option { let state = GlobalState::instance(); let buffer = &state.editor.buffer; let nr_width = (buffer.lines().len().to_string().len() + 1).max(4) as u16; Some(TuiCursor { position: ( buffer.cursor().y() as u16, buffer.cursor().x() as u16 + nr_width, ), style: ratatui::crossterm::cursor::SetCursorStyle::SteadyBar, }) } } impl Widget for &mut LuaEditor { fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) where Self: Sized, { { let state = GlobalState::instance(); let buffer = &state.editor.buffer; let config = GlobalConfig::instance(); let theme = &config.theme.editor; let inner_area = self.bar.area(area); let text = buffer.as_string(); let highlights = treesitter::highlighter_split(text.as_bytes(), &self.highlight_config); let nr_width = (buffer.lines().len().to_string().len() + 1).max(4) as u16; let mut text_area = inner_area; text_area.x += nr_width; let mut span_area = text_area; let mut current_line = 0; theme .background .get((), &lua::get()) .unwrap_or_default() .apply(Paragraph::default()) .render(area, buf); theme .cursor_line .get((), &lua::get()) .unwrap_or_default() .apply(Paragraph::default()) .render( Rect::new( text_area.x, text_area.y + buffer.cursor().y() as u16, text_area.width, 1, ), buf, ); for (hl, group) in highlights.iter() { if *group == "\n" { if current_line >= self.scroll { span_area.y += 1; span_area.x = text_area.x; } current_line += 1; } else if current_line >= self.scroll { let group = group.replace('\t', " "); let span = group.to_span(); if inner_area.contains(span_area.into()) { theme.highlight.highlight(*hl, span).render(span_area, buf); } span_area.x += group.len() as u16; } } for i in self.scroll..buffer.lines().len() { let mut nr_area = span_area; nr_area.x = inner_area.x; nr_area.width = nr_width; nr_area.y = inner_area.y + (i - self.scroll) as u16; nr_area.height = 1; if !inner_area.contains(nr_area.into()) { break; } if buffer.cursor().y() == i { theme .active_line_number .get((), &lua::get()) .unwrap_or_default() } else { theme.line_number.get((), &lua::get()).unwrap_or_default() } .apply(((i + 1).to_string() + " ").to_line()) .right_aligned() .render(nr_area, buf); } } EditorBarState::apply(&mut self.bar, (), &lua::get()); EditorBarTheme::apply(&mut self.bar, (), &lua::get()); self.bar.render(area, buf); } } impl Default for LuaEditor { fn default() -> Self { Self { highlight_config: treesitter::new_highlight_configuration(), bar: StatusBar::default(), scroll: 0, } } }