// Each component declares its own size constraints and gets fitted based on its parent. // Q: how does this work with popups? // cursive does compositor.screen_mut().add_layer_at(pos::absolute(x, y), ) use helix_core::Position; use helix_view::graphics::{CursorKind, Rect}; use tui::buffer::Buffer as Surface; pub type Callback = Box; pub type SyncCallback = Box; // Cursive-inspired pub enum EventResult { Ignored(Option), Consumed(Option), } use crate::job::Jobs; use crate::ui::picker; use helix_view::Editor; pub use helix_view::input::Event; pub struct Context<'a> { pub editor: &'a mut Editor, pub scroll: Option, pub jobs: &'a mut Jobs, } impl<'a> Context<'a> { /// Waits on all pending jobs, and then tries to flush all pending write /// operations for all documents. pub fn block_try_flush_writes(&mut self) -> anyhow::Result<()> { tokio::task::block_in_place(|| helix_lsp::block_on(self.jobs.finish(self.editor, None)))?; tokio::task::block_in_place(|| helix_lsp::block_on(self.editor.flush_writes()))?; Ok(()) } /// Purpose: to test `handle_event` without escalating the test case to integration test /// Usage: /// ``` /// let mut editor = Context::dummy_editor(); /// let mut jobs = Context::dummy_jobs(); /// let mut cx = Context::dummy(&mut jobs, &mut editor); /// ``` #[cfg(test)] pub fn dummy(jobs: &'a mut Jobs, editor: &'a mut helix_view::Editor) -> Context<'a> { Context { jobs, scroll: None, editor, } } #[cfg(test)] pub fn dummy_jobs() -> Jobs { Jobs::new() } #[cfg(test)] pub fn dummy_editor() -> Editor { use crate::config::Config; use arc_swap::{access::Map, ArcSwap}; use helix_core::syntax::{self, Configuration}; use helix_view::theme; use std::{collections::HashMap, sync::Arc}; let config = Arc::new(ArcSwap::from_pointee(Config::default())); Editor::new( Rect::new(0, 0, 60, 120), Arc::new(theme::Loader::new(&[])), Arc::new(syntax::Loader::new(Configuration { language: vec![], language_server: HashMap::new(), })), Arc::new(Arc::new(Map::new( Arc::clone(&config), |config: &Config| &config.editor, ))), ) } } pub trait Component: Any + AnyComponent { /// Process input events, return true if handled. fn handle_event(&mut self, _event: &Event, _ctx: &mut Context) -> EventResult { EventResult::Ignored(None) } // , args: () /// Should redraw? Useful for saving redraw cycles if we know component didn't change. fn should_update(&self) -> bool { true } /// Render the component onto the provided surface. fn render(&mut self, area: Rect, frame: &mut Surface, ctx: &mut Context); /// Get cursor position and cursor kind. fn cursor(&self, _area: Rect, _ctx: &Editor) -> (Option, CursorKind) { (None, CursorKind::Hidden) } /// May be used by the parent component to compute the child area. /// viewport is the maximum allowed area, and the child should stay within those bounds. /// /// The returned size might be larger than the viewport if the child is too big to fit. /// In this case the parent can use the values to calculate scroll. fn required_size(&mut self, _viewport: (u16, u16)) -> Option<(u16, u16)> { None } fn type_name(&self) -> &'static str { std::any::type_name::() } fn id(&self) -> Option<&'static str> { None } #[cfg(test)] /// Utility method for testing `handle_event` without using integration test. /// Especially useful for testing helper components such as `Prompt`, `TreeView` etc fn handle_events(&mut self, events: &str) -> anyhow::Result<()> { use helix_view::input::parse_macro; let mut editor = Context::dummy_editor(); let mut jobs = Context::dummy_jobs(); let mut cx = Context::dummy(&mut jobs, &mut editor); for event in parse_macro(events)? { self.handle_event(&Event::Key(event), &mut cx); } Ok(()) } } pub struct Compositor { layers: Vec>, area: Rect, pub(crate) last_picker: Option>, pub(crate) full_redraw: bool, } impl Compositor { pub fn new(area: Rect) -> Self { Self { layers: Vec::new(), area, last_picker: None, full_redraw: false, } } pub fn size(&self) -> Rect { self.area } pub fn resize(&mut self, area: Rect) { self.area = area; } /// Add a layer to be rendered in front of all existing layers. pub fn push(&mut self, mut layer: Box) { // immediately clear last_picker field to avoid excessive memory // consumption for picker with many items if layer.id() == Some(picker::ID) { self.last_picker = None; } let size = self.size(); // trigger required_size on init layer.required_size((size.width, size.height)); self.layers.push(layer); } /// Replace a component that has the given `id` with the new layer and if /// no component is found, push the layer normally. pub fn replace_or_push(&mut self, id: &'static str, layer: T) { if let Some(component) = self.find_id(id) { *component = layer; } else { self.push(Box::new(layer)) } } pub fn pop(&mut self) -> Option> { self.layers.pop() } pub fn remove(&mut self, id: &'static str) -> Option> { let idx = self .layers .iter() .position(|layer| layer.id() == Some(id))?; Some(self.layers.remove(idx)) } pub fn handle_event(&mut self, event: &Event, cx: &mut Context) -> bool { // If it is a key event and a macro is being recorded, push the key event to the recording. if let (Event::Key(key), Some((_, keys))) = (event, &mut cx.editor.macro_recording) { keys.push(*key); } let mut callbacks = Vec::new(); let mut consumed = false; // propagate events through the layers until we either find a layer that consumes it or we // run out of layers (event bubbling), starting at the front layer and then moving to the // background. for layer in self.layers.iter_mut().rev() { match layer.handle_event(event, cx) { EventResult::Consumed(Some(callback)) => { callbacks.push(callback); consumed = true; break; } EventResult::Consumed(None) => { consumed = true; break; } EventResult::Ignored(Some(callback)) => { callbacks.push(callback); } EventResult::Ignored(None) => {} }; } for callback in callbacks { callback(self, cx) } consumed } pub fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { for layer in &mut self.layers { layer.render(area, surface, cx); } } pub fn cursor(&self, area: Rect, editor: &Editor) -> (Option, CursorKind) { for layer in self.layers.iter().rev() { if let (Some(pos), kind) = layer.cursor(area, editor) { return (Some(pos), kind); } } (None, CursorKind::Hidden) } pub fn has_component(&self, type_name: &str) -> bool { self.layers .iter() .any(|component| component.type_name() == type_name) } pub fn find(&mut self) -> Option<&mut T> { let type_name = std::any::type_name::(); self.layers .iter_mut() .find(|component| component.type_name() == type_name) .and_then(|component| component.as_any_mut().downcast_mut()) } pub fn find_id(&mut self, id: &'static str) -> Option<&mut T> { self.layers .iter_mut() .find(|component| component.id() == Some(id)) .and_then(|component| component.as_any_mut().downcast_mut()) } pub fn need_full_redraw(&mut self) { self.full_redraw = true; } } // View casting, taken straight from Cursive use std::any::Any; /// A view that can be downcasted to its concrete type. /// /// This trait is automatically implemented for any `T: Component`. pub trait AnyComponent { /// Downcast self to a `Any`. fn as_any(&self) -> &dyn Any; /// Downcast self to a mutable `Any`. fn as_any_mut(&mut self) -> &mut dyn Any; /// Returns a boxed any from a boxed self. /// /// Can be used before `Box::downcast()`. /// /// # Examples /// /// ```rust /// use helix_term::{ui::Text, compositor::Component}; /// let boxed: Box = Box::new(Text::new("text".to_string())); /// let text: Box = boxed.as_boxed_any().downcast().unwrap(); /// ``` fn as_boxed_any(self: Box) -> Box; } impl AnyComponent for T { /// Downcast self to a `Any`. fn as_any(&self) -> &dyn Any { self } /// Downcast self to a mutable `Any`. fn as_any_mut(&mut self) -> &mut dyn Any { self } fn as_boxed_any(self: Box) -> Box { self } } impl dyn AnyComponent { /// Attempts to downcast `self` to a concrete type. pub fn downcast_ref(&self) -> Option<&T> { self.as_any().downcast_ref() } /// Attempts to downcast `self` to a concrete type. pub fn downcast_mut(&mut self) -> Option<&mut T> { self.as_any_mut().downcast_mut() } /// Attempts to downcast `Box` to a concrete type. pub fn downcast(self: Box) -> Result, Box> { // Do the check here + unwrap, so the error // value is `Self` and not `dyn Any`. if self.as_any().is::() { Ok(self.as_boxed_any().downcast().unwrap()) } else { Err(self) } } /// Checks if this view is of type `T`. pub fn is(&mut self) -> bool { self.as_any().is::() } }