use crate::{theme::Theme, tree::Tree, Document, DocumentId, View, ViewId}; use tui::layout::Rect; use std::path::PathBuf; use slotmap::SlotMap; use anyhow::Error; pub struct Editor { pub tree: Tree, pub documents: SlotMap, pub count: Option, pub theme: Theme, pub language_servers: helix_lsp::Registry, pub executor: &'static smol::Executor<'static>, } pub enum Action { Replace, HorizontalSplit, VerticalSplit, } impl Editor { pub fn new(executor: &'static smol::Executor<'static>, mut area: tui::layout::Rect) -> Self { let theme = Theme::default(); let language_servers = helix_lsp::Registry::new(); // HAXX: offset the render area height by 1 to account for prompt/commandline area.height -= 1; Self { tree: Tree::new(area), documents: SlotMap::with_key(), count: None, theme, language_servers, executor, } } fn _refresh(&mut self) { for (view, _) in self.tree.views_mut() { let doc = &self.documents[view.doc]; view.ensure_cursor_in_view(doc) } } pub fn open(&mut self, path: PathBuf, action: Action) -> Result { let id = self .documents() .find(|doc| doc.path() == Some(&path)) .map(|doc| doc.id); let id = if let Some(id) = id { id } else { let mut doc = Document::load(path, self.theme.scopes())?; // try to find a language server based on the language name let language_server = doc .language .as_ref() .and_then(|language| self.language_servers.get(language, self.executor)); if let Some(language_server) = language_server { doc.set_language_server(Some(language_server.clone())); let language_id = doc .language() .and_then(|s| s.split('.').last()) // source.rust .map(ToOwned::to_owned) .unwrap_or_default(); smol::block_on(language_server.text_document_did_open( doc.url().unwrap(), doc.version(), doc.text(), language_id, )) .unwrap(); } let id = self.documents.insert(doc); self.documents[id].id = id; id }; use crate::tree::Layout; match action { Action::Replace => { self.view_mut().doc = id; // TODO: reset selection? return Ok(id); } Action::HorizontalSplit => { let view = View::new(id)?; self.tree.split(view, Layout::Horizontal); } Action::VerticalSplit => { let view = View::new(id)?; self.tree.split(view, Layout::Vertical); } } self._refresh(); Ok(id) } pub fn close(&mut self, id: ViewId) { let view = self.tree.get(self.tree.focus); // get around borrowck issues let language_servers = &mut self.language_servers; let executor = self.executor; let doc = &self.documents[view.doc]; let language_server = doc .language .as_ref() .and_then(|language| language_servers.get(language, executor)); if let Some(language_server) = language_server { smol::block_on(language_server.text_document_did_close(doc.identifier())).unwrap(); } // self.documents.remove(view.doc); self.tree.remove(id); self._refresh(); } pub fn resize(&mut self, area: Rect) { self.tree.resize(area); self._refresh(); } pub fn focus_next(&mut self) { self.tree.focus_next(); } pub fn should_close(&self) -> bool { self.tree.is_empty() } pub fn view(&self) -> &View { self.tree.get(self.tree.focus) } pub fn view_mut(&mut self) -> &mut View { self.tree.get_mut(self.tree.focus) } pub fn ensure_cursor_in_view(&mut self, id: ViewId) { let view = self.tree.get_mut(id); let doc = &self.documents[view.doc]; view.ensure_cursor_in_view(doc) } pub fn document(&self, id: DocumentId) -> Option<&Document> { self.documents.get(id) } pub fn documents(&self) -> impl Iterator { self.documents.iter().map(|(_id, doc)| doc) } pub fn cursor_position(&self) -> Option { const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter let view = self.view(); let doc = &self.documents[view.doc]; let cursor = doc.selection().cursor(); if let Some(mut pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) { pos.col += view.area.x as usize + OFFSET as usize; pos.row += view.area.y as usize; return Some(pos); } None } }