use crate::tui::component::{Action, Component}; use anyhow::Result; use edtui::{ EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, Lines, SyntaxHighlighter, }; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use ratatui::layout::{Alignment, Rect}; use ratatui::prelude::{Color, Style}; use ratatui::widgets::{Block, Borders, Padding, Widget}; // TODO: Consider using editor-command https://docs.rs/editor-command/latest/editor_command/ // TODO: Title should be detected programming language name // TODO: Content should be file contents // TODO: Vimrc should be used pub struct Editor { pub state: EditorState, pub event_handler: EditorEventHandler, pub file_path: Option, } impl Editor { pub fn new() -> Self { Editor { state: EditorState::default(), event_handler: EditorEventHandler::default(), file_path: None, } } pub fn set_contents(&mut self, path: &std::path::PathBuf) -> Result<()> { if let Ok(contents) = std::fs::read_to_string(path) { let lines: Vec<_> = contents .lines() .map(|line| line.chars().collect::>()) .collect(); self.file_path = Some(path.clone()); self.state.lines = Lines::new(lines); return Ok(()); } Err(anyhow::Error::msg("Failed to set editor file contents")) } pub fn save(&self) -> Result<()> { if let Some(path) = &self.file_path { return std::fs::write(path, self.state.lines.to_string()).map_err(|e| e.into()); }; Err(anyhow::anyhow!("File not saved. No file path set.")) } } impl Widget for &mut Editor { fn render(self, area: Rect, buf: &mut Buffer) { // TODO: Use current file extension for syntax highlighting here. EditorView::new(&mut self.state) .wrap(true) .theme( EditorTheme::default().block( Block::default() .title("Rust") .title_style(Style::default().fg(Color::Yellow)) .title_alignment(Alignment::Right) .borders(Borders::ALL) .padding(Padding::new(0, 0, 0, 1)), ), ) .syntax_highlighter(SyntaxHighlighter::new("dracula", "rs").ok()) .tab_width(2) .line_numbers(LineNumbers::Absolute) .render(area, buf); } } impl Component for Editor { fn id(&self) -> &str { "editor" } fn handle_event(&mut self, event: Event) -> Action { if let Some(key_event) = event.as_key_event() { // Handle events here that should not be passed on to the vim emulation handler. match self.handle_key_events(key_event) { Action::Handled => return Action::Handled, _ => {} } } self.event_handler.on_event(event, &mut self.state); Action::Pass } /// The events for the vim emulation should be handled by EditorEventHandler::on_event. /// These events are custom to the clide application. fn handle_key_events(&mut self, key: KeyEvent) -> Action { match key { KeyEvent { code: KeyCode::Char('s'), modifiers: KeyModifiers::CONTROL, .. } => { self.save().expect("Failed to save file."); Action::Handled } // For other events not handled here, pass to the vim emulation handler. _ => Action::Noop, } } }