clide/src/tui/editor.rs

157 lines
4.0 KiB
Rust
Raw Normal View History

2026-01-17 19:21:14 -05:00
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<NvimUI>,
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<Line> = grid
.iter()
.map(|row| Line::from(row.iter().collect::<String>()))
.collect();
Paragraph::new(lines).render(area, buf);
}
}
impl ClideComponent for Editor {}
#[derive(Default, Clone)]
pub struct NvimUI {
pub grid: Arc<Mutex<Vec<Vec<char>>>>,
pub cursor: Arc<Mutex<(usize, usize)>>,
}
impl Handler for NvimUI {
type Writer = Compat<tokio::process::ChildStdin>;
async fn handle_notify(
&self,
_name: String,
_args: Vec<Value>,
_neovim: Neovim<Compat<tokio::process::ChildStdin>>,
) -> 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<H: Handler + Send + 'static>(
handler: H,
) -> Result<Neovim<Compat<tokio::process::ChildStdin>>> {
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<nvim_rs::compat::tokio::Compat<tokio::process::ChildStdin>>,
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(())
}
}