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 | 146 |
1 files changed, 87 insertions, 59 deletions
diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index 266a8aec..ce29eb49 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -1,13 +1,21 @@ -// 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>; + +// --> 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 { @@ -15,11 +23,9 @@ pub enum EventResult { 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,19 +33,9 @@ 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 { + fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult { EventResult::Ignored(None) } // , args: () @@ -75,39 +71,56 @@ pub trait Component: Any + AnyComponent { } } +use termwiz::{ + caps::Capabilities, surface::CursorVisibility, terminal::buffered::BufferedTerminal, + terminal::SystemTerminal, +}; +type Terminal = BufferedTerminal<SystemTerminal>; + pub struct Compositor { layers: Vec<Box<dyn Component>>, - area: Rect, + terminal: Terminal, + surface: Surface, 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, termwiz::Error> { + let terminal = BufferedTerminal::new(SystemTerminal::new(Capabilities::new_from_env()?)?)?; + let (width, height) = terminal.dimensions(); + let surface = Surface::new(width, height); + Ok(Self { layers: Vec::new(), - area, + terminal, + surface, last_picker: None, - full_redraw: false, - } + }) } pub fn size(&self) -> Rect { - self.area + let (width, height) = self.terminal.dimensions(); + Rect::new(0, 0, width as u16, height as u16) } - pub fn resize(&mut self, area: Rect) { - self.area = area; + // TODO: pass in usize + pub fn resize(&mut self, width: u16, height: u16) { + self.terminal.resize(width as usize, height as usize) } - /// 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 restore_cursor(&mut self) { + if self.terminal.cursor_visibility() == CursorVisibility::Hidden { + // TODO: set cursor to block } + } + + pub fn load_cursor(&mut self) { + if self.terminal.cursor_visibility() == CursorVisibility::Hidden { + // TODO: hide cursor again + } + } + + 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)); @@ -128,34 +141,17 @@ impl Compositor { 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. + 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) { - if cx.editor.macro_replaying.is_empty() { - keys.push(*key); - } + keys.push(key.into()); } 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. + // run out of layers (event bubbling) for layer in self.layers.iter_mut().rev() { match layer.handle_event(event, cx) { EventResult::Consumed(Some(callback)) => { @@ -181,10 +177,46 @@ impl Compositor { consumed } - pub fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { + pub fn render(&mut self, cx: &mut Context) { + // self.terminal + // .autoresize() + // .expect("Unable to determine terminal size"); + + // TODO: need to recalculate view tree if necessary + + let area = self.size(); + + if (area.width as usize, area.height as usize) != self.surface.dimensions() { + self.surface + .resize(area.width as usize, area.height as usize); + } + for layer in &mut self.layers { - layer.render(area, surface, cx); + layer.render(area, &mut self.surface, cx) + } + + // TODO use kind + let (pos, kind) = self.cursor(area, cx.editor); + let pos = pos.map(|pos| (pos.col, pos.row)); + + use termwiz::surface::{Change, Position}; + if let Some(pos) = pos { + self.terminal + .add_change(Change::CursorVisibility(CursorVisibility::Visible)); + self.terminal.add_change(Change::CursorPosition { + x: Position::Absolute(pos.0), + y: Position::Absolute(pos.1), + }); + } else { + self.terminal + .add_change(Change::CursorVisibility(CursorVisibility::Hidden)); } + + self.terminal.draw_from_screen(&self.surface, 0, 0); + self.terminal.flush().expect("failed to flush"); + + self.surface + .flush_changes_older_than(self.surface.current_seqno()); } pub fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) { @@ -216,10 +248,6 @@ impl Compositor { .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 |