Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/compositor.rs')
| -rw-r--r-- | helix-term/src/compositor.rs | 183 |
1 files changed, 73 insertions, 110 deletions
diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index 266a8aec..dc8b91d7 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -1,25 +1,31 @@ -// Each component declares its own size constraints and gets fitted based on its parent. +// Each component declares it's own size constraints and gets fitted based on it's parent. // Q: how does this work with popups? // cursive does compositor.screen_mut().add_layer_at(pos::absolute(x, y), <component>) use helix_core::Position; use helix_view::graphics::{CursorKind, Rect}; +use crossterm::event::Event; use tui::buffer::Buffer as Surface; -pub type Callback = Box<dyn FnOnce(&mut Compositor, &mut Context)>; -pub type SyncCallback = Box<dyn FnOnce(&mut Compositor, &mut Context) + Sync>; +pub type Callback = Box<dyn FnOnce(&mut Compositor)>; + +// --> EventResult should have a callback that takes a context with methods like .popup(), +// .prompt() etc. That way we can abstract it from the renderer. +// Q: How does this interact with popups where we need to be able to specify the rendering of the +// popup? +// A: It could just take a textarea. +// +// If Compositor was specified in the callback that's then problematic because of // Cursive-inspired pub enum EventResult { - Ignored(Option<Callback>), + Ignored, Consumed(Option<Callback>), } -use crate::job::Jobs; -use crate::ui::picker; use helix_view::Editor; -pub use helix_view::input::Event; +use crate::job::Jobs; pub struct Context<'a> { pub editor: &'a mut Editor, @@ -27,20 +33,10 @@ pub struct Context<'a> { pub jobs: &'a mut Jobs, } -impl Context<'_> { - /// 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(()) - } -} - 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) + fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult { + EventResult::Ignored } // , args: () @@ -59,132 +55,111 @@ pub trait Component: Any + AnyComponent { /// 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)> { + // TODO: for scrolling, the scroll wrapper should place a size + offset on the Context + // that way render can use it None } fn type_name(&self) -> &'static str { std::any::type_name::<Self>() } - - fn id(&self) -> Option<&'static str> { - None - } } +use anyhow::Error; +use std::io::stdout; +use tui::backend::{Backend, CrosstermBackend}; +type Terminal = tui::terminal::Terminal<CrosstermBackend<std::io::Stdout>>; + pub struct Compositor { layers: Vec<Box<dyn Component>>, - area: Rect, + terminal: Terminal, pub(crate) last_picker: Option<Box<dyn Component>>, - pub(crate) full_redraw: bool, } impl Compositor { - pub fn new(area: Rect) -> Self { - Self { + pub fn new() -> Result<Self, Error> { + let backend = CrosstermBackend::new(stdout()); + let terminal = Terminal::new(backend)?; + Ok(Self { layers: Vec::new(), - area, + terminal, last_picker: None, - full_redraw: false, - } + }) } pub fn size(&self) -> Rect { - self.area + self.terminal.size().expect("couldn't get terminal size") } - pub fn resize(&mut self, area: Rect) { - self.area = area; + pub fn resize(&mut self, width: u16, height: u16) { + self.terminal + .resize(Rect::new(0, 0, width, height)) + .expect("Unable to resize terminal") } - /// Add a layer to be rendered in front of all existing layers. - pub fn push(&mut self, mut layer: Box<dyn Component>) { - // 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; + pub fn save_cursor(&mut self) { + if self.terminal.cursor_kind() == CursorKind::Hidden { + self.terminal + .backend_mut() + .show_cursor(CursorKind::Block) + .ok(); + } + } + + pub fn load_cursor(&mut self) { + if self.terminal.cursor_kind() == CursorKind::Hidden { + self.terminal.backend_mut().hide_cursor().ok(); } + } + + pub fn push(&mut self, mut layer: Box<dyn Component>) { 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<T: Component>(&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<Box<dyn Component>> { self.layers.pop() } - pub fn remove(&mut self, id: &'static str) -> Option<Box<dyn Component>> { - let idx = self - .layers - .iter() - .position(|layer| layer.id() == Some(id))?; - Some(self.layers.remove(idx)) - } - - pub fn remove_type<T: 'static>(&mut self) { - let type_name = std::any::type_name::<T>(); - self.layers - .retain(|component| component.type_name() != type_name); - } - pub fn handle_event(&mut self, event: &Event, cx: &mut Context) -> bool { - // If it is a key event, a macro is being recorded, and a macro isn't being replayed, - // push the key event to the recording. - if let (Event::Key(key), Some((_, keys))) = (event, &mut cx.editor.macro_recording) { - if cx.editor.macro_replaying.is_empty() { - keys.push(*key); - } - } - - let mut callbacks = Vec::new(); - let mut consumed = false; - + pub fn handle_event(&mut self, event: Event, cx: &mut Context) -> bool { // 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. + // run out of layers (event bubbling) 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; + callback(self); + return true; } - EventResult::Ignored(Some(callback)) => { - callbacks.push(callback); - } - EventResult::Ignored(None) => {} + EventResult::Consumed(None) => return true, + EventResult::Ignored => false, }; } + false + } - for callback in callbacks { - callback(self, cx) - } + pub fn render(&mut self, cx: &mut Context) { + self.terminal + .autoresize() + .expect("Unable to determine terminal size"); - consumed - } + // TODO: need to recalculate view tree if necessary + + let surface = self.terminal.current_buffer_mut(); + + let area = *surface.area(); - pub fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { for layer in &mut self.layers { layer.render(area, surface, cx); } + + let (pos, kind) = self.cursor(area, cx.editor); + let pos = pos.map(|pos| (pos.col as u16, pos.row as u16)); + + self.terminal.draw(pos, kind).unwrap(); } pub fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) { @@ -202,23 +177,11 @@ impl Compositor { .any(|component| component.type_name() == type_name) } - pub fn find<T: 'static>(&mut self) -> Option<&mut T> { - let type_name = std::any::type_name::<T>(); + pub fn find(&mut self, type_name: &str) -> Option<&mut dyn Component> { self.layers .iter_mut() .find(|component| component.type_name() == type_name) - .and_then(|component| component.as_any_mut().downcast_mut()) - } - - pub fn find_id<T: 'static>(&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; + .map(|component| component.as_mut()) } } |