use crate::tui::component::{Action, Component}; use anyhow::{Context, 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}; use syntect::parsing::SyntaxSet; // 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, syntax_set: SyntaxSet, } impl Editor { pub fn new() -> Self { Editor { state: EditorState::default(), event_handler: EditorEventHandler::default(), file_path: None, syntax_set: SyntaxSet::load_defaults_nonewlines(), } } 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); } Ok(()) } 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) { let lang = self .file_path .as_ref() .and_then(|p| p.extension()) .map(|e| e.to_str().unwrap_or("md")) .unwrap_or("md"); let lang_name = self .syntax_set .find_syntax_by_extension(lang) .map(|s| s.name.to_string()) .unwrap_or("Unknown".to_string()); EditorView::new(&mut self.state) .wrap(true) .theme( EditorTheme::default().block( Block::default() .title(lang_name.to_owned()) .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", lang).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, } } }