use crate::tui::component::ClideComponent; use anyhow::Result; use nvim_rs::compat::tokio::Compat; use nvim_rs::{Handler, Neovim, UiAttachOptions, Value}; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::text::Line; use ratatui::widgets::{Paragraph, Widget}; use std::process::Stdio; use std::sync::{Arc, Mutex}; use tokio::process::Command; struct Editor { ui: Arc, height: usize, width: usize, } impl Editor { fn new(height: usize, width: usize) -> Self { let editor = Editor { ui: Arc::new(NvimUI::default()), height, width, }; editor .ui .grid .lock() .unwrap() .resize(height, vec![' '; width]); editor } } impl<'a> Widget for &Editor { fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized, { let grid = self.ui.grid.lock().unwrap(); let lines: Vec = grid .iter() .map(|row| Line::from(row.iter().collect::())) .collect(); Paragraph::new(lines).render(area, buf); } } impl ClideComponent for Editor {} #[derive(Default, Clone)] pub struct NvimUI { pub grid: Arc>>>, pub cursor: Arc>, } impl Handler for NvimUI { type Writer = Compat; async fn handle_notify( &self, _name: String, _args: Vec, _neovim: Neovim>, ) -> Result<()> { if _name != "redraw" { return Ok(()); } for event in _args { if let Value::Array(items) = event { if items.is_empty() { continue; } let event_name = items[0].as_str().unwrap_or(""); match event_name { "grid_line" => self.handle_grid_line(&items), "cursor_goto" => self.handle_cursor(&items), _ => {} } } } Ok(()) } } impl NvimUI { fn handle_grid_line(&self, items: &[Value]) { // ["grid_line", grid, row, col, cells] let row = items[2].as_u64().unwrap() as usize; let col = items[3].as_u64().unwrap() as usize; let cells = items[4].as_array().unwrap(); let mut grid = self.grid.lock().unwrap(); let mut c = col; for cell in cells { let cell_arr = cell.as_array().unwrap(); let text = cell_arr[0].as_str().unwrap(); let repeat = cell_arr.get(2).and_then(|v| v.as_u64()).unwrap_or(1); for _ in 0..repeat { if let Some(ch) = text.chars().next() { grid[row][c] = ch; } c += 1; } } } fn handle_cursor(&self, items: &[Value]) { let row = items[2].as_u64().unwrap() as usize; let col = items[3].as_u64().unwrap() as usize; *self.cursor.lock().unwrap() = (row, col); } pub async fn spawn_nvim( handler: H, ) -> Result>> { let mut child = Command::new("nvim") .arg("--embed") .arg("--headless") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; let stdin = child.stdin.take().unwrap(); let mut stdout = child.stdout.take().unwrap(); let (nvim, io_handler) = Neovim::new(stdout, stdin, handler); tokio::spawn(async move { nvim_rs::compat::tokio::spawn(stdout.compat(), io_handler).await; }); Ok(nvim) } pub async fn init_ui( nvim: &Neovim>, w: i64, h: i64, ) -> anyhow::Result<()> { let mut opts = UiAttachOptions::default(); opts.set_rgb(true).set_linegrid_external(true); nvim.ui_attach(w, h, &opts).await?; Ok(()) } }