diff --git a/helix-view/src/compositor.rs b/helix-view/src/compositor.rs index e66a8d89d..55a158441 100644 --- a/helix-view/src/compositor.rs +++ b/helix-view/src/compositor.rs @@ -23,7 +23,7 @@ pub struct Context<'a> { } #[cfg(feature = "term")] -mod term { +pub mod term { use super::*; pub use tui::buffer::Buffer as Surface; @@ -32,12 +32,24 @@ pub struct RenderContext<'a> { pub surface: &'a mut Surface, pub scroll: Option, } + + pub trait Render { + /// Render the component onto the provided surface. + fn render(&mut self, area: Rect, ctx: &mut RenderContext); + + // TODO: make required_size be layout() instead and part of this trait? + + /// Get cursor position and cursor kind. + fn cursor(&self, _area: Rect, _ctx: &Editor) -> (Option, CursorKind) { + (None, CursorKind::Hidden) + } + } } #[cfg(feature = "term")] pub use term::*; -pub trait Component: Any + AnyComponent { +pub trait Component: Any + AnyComponent + Render { /// Process input events, return true if handled. fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult { EventResult::Ignored(None) @@ -48,14 +60,6 @@ fn should_update(&self) -> bool { true } - /// Render the component onto the provided surface. - fn render(&mut self, area: Rect, ctx: &mut RenderContext); - - /// 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. /// diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index 545011f90..9cc5c4288 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -77,12 +77,13 @@ pub fn from_registers(registers: &Registers) -> Self { // term use crate::{ - compositor::{Component, RenderContext}, + compositor::{self, Component, RenderContext}, graphics::{Margin, Rect}, }; use tui::widgets::{Block, Borders, Paragraph, Widget}; -impl Component for Info { +#[cfg(feature = "term")] +impl compositor::term::Render for Info { fn render(&mut self, viewport: Rect, cx: &mut RenderContext<'_>) { let text_style = cx.editor.theme.get("ui.text.info"); let popup_style = cx.editor.theme.get("ui.popup.info"); @@ -117,3 +118,5 @@ fn render(&mut self, viewport: Rect, cx: &mut RenderContext<'_>) { .render(inner, cx.surface); } } + +impl Component for Info {} diff --git a/helix-view/src/ui/completion.rs b/helix-view/src/ui/completion.rs index 64ef686e8..c6ffe4624 100644 --- a/helix-view/src/ui/completion.rs +++ b/helix-view/src/ui/completion.rs @@ -1,14 +1,14 @@ -use crate::compositor::{Component, Context, Event, EventResult, RenderContext}; +use crate::compositor::{self, Component, Context, Event, EventResult}; use crate::editor::CompleteAction; use std::borrow::Cow; -use helix_core::{Change, Transaction}; use crate::{ graphics::Rect, input::{KeyCode, KeyEvent}, Document, Editor, }; +use helix_core::{Change, Transaction}; use crate::commands; use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent}; @@ -302,8 +302,11 @@ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { self.popup.required_size(viewport) } +} - fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { +#[cfg(feature = "term")] +impl compositor::term::Render for Completion { + fn render(&mut self, area: Rect, cx: &mut compositor::term::RenderContext<'_>) { self.popup.render(area, cx); // if we have a selection, render a markdown popup on top/below with info diff --git a/helix-view/src/ui/editor.rs b/helix-view/src/ui/editor.rs index 8d479337f..7caf56edf 100644 --- a/helix-view/src/ui/editor.rs +++ b/helix-view/src/ui/editor.rs @@ -1,11 +1,19 @@ use crate::{ - commands, key, + commands, compositor, key, keymap::{KeymapResult, Keymaps}, ui::{Completion, ProgressSpinners}, }; -use crate::compositor::{Component, Context, Event, EventResult, RenderContext}; +use crate::compositor::{Component, Context, Event, EventResult}; +use crate::{ + document::{Mode, SCRATCH_BUFFER_NAME}, + editor::{CompleteAction, CursorShapeConfig}, + graphics::{CursorKind, Modifier, Rect, Style}, + input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind}, + keyboard::{KeyCode, KeyModifiers}, + Document, Editor, Theme, View, +}; use helix_core::{ coords_at_pos, encoding, graphemes::{ @@ -17,18 +25,8 @@ unicode::width::UnicodeWidthStr, LineEnding, Position, Range, Selection, Transaction, }; -use crate::{ - document::{Mode, SCRATCH_BUFFER_NAME}, - editor::{CompleteAction, CursorShapeConfig}, - graphics::{CursorKind, Modifier, Rect, Style}, - input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind}, - keyboard::{KeyCode, KeyModifiers}, - Document, Editor, Theme, View, -}; use std::borrow::Cow; -use tui::buffer::Buffer as Surface; - pub struct EditorView { pub keymaps: Keymaps, on_next_key: Option>, @@ -64,7 +62,13 @@ pub fn new(keymaps: Keymaps) -> Self { pub fn spinners_mut(&mut self) -> &mut ProgressSpinners { &mut self.spinners } +} +#[cfg(feature = "term")] +use tui::buffer::Buffer as Surface; + +#[cfg(feature = "term")] +impl EditorView { pub fn render_view( &self, editor: &Editor, @@ -777,7 +781,9 @@ pub fn render_statusline( true, ); } +} +impl EditorView { /// Handle events by looking them up in `self.keymaps`. Returns None /// if event was handled (a command was executed or a subkeymap was /// activated). Only KeymapResult::{NotFound, Cancelled} is returned @@ -948,9 +954,7 @@ pub fn handle_idle_timeout(&mut self, cx: &mut crate::compositor::Context) -> Ev EventResult::Consumed(None) } -} -impl EditorView { fn handle_mouse_event( &mut self, event: MouseEvent, @@ -1288,8 +1292,11 @@ fn handle_event( Event::Mouse(event) => self.handle_mouse_event(event, &mut cx), } } +} - fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { +#[cfg(feature = "term")] +impl compositor::term::Render for EditorView { + fn render(&mut self, area: Rect, cx: &mut compositor::term::RenderContext<'_>) { // clear with background color cx.surface .set_style(area, cx.editor.theme.get("ui.background")); diff --git a/helix-view/src/ui/markdown.rs b/helix-view/src/ui/markdown.rs index def84477c..eb5e0a430 100644 --- a/helix-view/src/ui/markdown.rs +++ b/helix-view/src/ui/markdown.rs @@ -1,18 +1,18 @@ -use crate::compositor::{Component, RenderContext}; +use crate::compositor::{self, Component, RenderContext}; use tui::text::{Span, Spans, Text}; use std::sync::Arc; use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag}; -use helix_core::{ - syntax::{self, HighlightEvent, Syntax}, - Rope, -}; use crate::{ graphics::{Margin, Rect, Style}, Theme, }; +use helix_core::{ + syntax::{self, HighlightEvent, Syntax}, + Rope, +}; fn styled_multiline_text<'a>(text: String, style: Style) -> Text<'a> { let spans: Vec<_> = text @@ -255,7 +255,8 @@ fn parse(&self, theme: Option<&Theme>) -> tui::text::Text<'_> { } } -impl Component for Markdown { +#[cfg(feature = "term")] +impl compositor::term::Render for Markdown { fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { use tui::widgets::{Paragraph, Widget, Wrap}; @@ -271,7 +272,9 @@ fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { }; par.render(area.inner(&margin), cx.surface); } +} +impl Component for Markdown { fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { let padding = 2; if padding >= viewport.1 || padding >= viewport.0 { diff --git a/helix-view/src/ui/menu.rs b/helix-view/src/ui/menu.rs index de370e0d8..d85eb100a 100644 --- a/helix-view/src/ui/menu.rs +++ b/helix-view/src/ui/menu.rs @@ -1,16 +1,15 @@ -use crate::{ctrl, key, shift}; use crate::compositor::{ - Callback, Component, Compositor, Context, Event, EventResult, RenderContext, + self, Callback, Component, Compositor, Context, Event, EventResult, RenderContext, }; -use tui::widgets::Table; - -pub use tui::widgets::{Cell, Row}; +use crate::{ctrl, key, shift}; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::FuzzyMatcher; use crate::{graphics::Rect, Editor}; + use tui::layout::Constraint; +pub use tui::widgets::{Cell, Row}; pub trait Item { fn label(&self) -> &str; @@ -263,8 +262,13 @@ fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { Some(self.size) } +} +#[cfg(feature = "term")] +impl compositor::term::Render for Menu { fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { + use tui::widgets::Table; + let theme = &cx.editor.theme; let style = theme .try_get("ui.menu") diff --git a/helix-view/src/ui/overlay.rs b/helix-view/src/ui/overlay.rs index c7e153d17..f44374eb7 100644 --- a/helix-view/src/ui/overlay.rs +++ b/helix-view/src/ui/overlay.rs @@ -1,8 +1,9 @@ -use helix_core::Position; use crate::{ + compositor, graphics::{CursorKind, Rect}, Editor, }; +use helix_core::Position; use crate::compositor::{Component, Context, Event, EventResult, RenderContext}; @@ -41,12 +42,15 @@ fn mul_and_cast(size: u16, factor: u8) -> u16 { } } -impl Component for Overlay { +#[cfg(feature = "term")] +impl compositor::term::Render for Overlay { fn render(&mut self, area: Rect, ctx: &mut RenderContext<'_>) { let dimensions = (self.calc_child_size)(area); self.content.render(dimensions, ctx) } +} +impl Component for Overlay { fn required_size(&mut self, (width, height): (u16, u16)) -> Option<(u16, u16)> { let area = Rect { x: 0, diff --git a/helix-view/src/ui/picker.rs b/helix-view/src/ui/picker.rs index 2fbacab3e..91022e442 100644 --- a/helix-view/src/ui/picker.rs +++ b/helix-view/src/ui/picker.rs @@ -1,13 +1,11 @@ +use crate::compositor::{self, Component, Compositor, Context, Event, EventResult, RenderContext}; use crate::{ ctrl, key, shift, ui::{self, EditorView}, }; -use crate::compositor::{Component, Compositor, Context, Event, EventResult, RenderContext}; -use tui::widgets::{Block, BorderType, Borders}; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::FuzzyMatcher; -use tui::widgets::Widget; use std::time::Instant; use std::{ @@ -19,12 +17,12 @@ }; use crate::ui::{Prompt, PromptEvent}; -use helix_core::{movement::Direction, Position}; use crate::{ editor::Action, graphics::{Color, CursorKind, Margin, Modifier, Rect, Style}, Document, Editor, }; +use helix_core::{movement::Direction, Position}; pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72; /// Biggest file size to preview in bytes @@ -159,8 +157,11 @@ fn get_preview<'picker, 'editor>( } } -impl Component for FilePicker { +#[cfg(feature = "term")] +impl compositor::term::Render for FilePicker { fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { + use tui::widgets::Widget; + use tui::widgets::{Block, Borders}; // +---------+ +---------+ // |prompt | |preview | // +---------+ | | @@ -260,15 +261,17 @@ fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { } } + fn cursor(&self, area: Rect, ctx: &Editor) -> (Option, CursorKind) { + self.picker.cursor(area, ctx) + } +} + +impl Component for FilePicker { fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult { // TODO: keybinds for scrolling preview self.picker.handle_event(event, ctx) } - fn cursor(&self, area: Rect, ctx: &Editor) -> (Option, CursorKind) { - self.picker.cursor(area, ctx) - } - fn required_size(&mut self, (width, height): (u16, u16)) -> Option<(u16, u16)> { let picker_width = if width > MIN_AREA_WIDTH_FOR_PREVIEW { width / 2 @@ -548,8 +551,13 @@ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { EventResult::Consumed(None) } +} +#[cfg(feature = "term")] +impl compositor::term::Render for Picker { fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { + use tui::widgets::Widget; + use tui::widgets::{Block, BorderType, Borders}; let text_style = cx.editor.theme.get("ui.text"); let selected = cx.editor.theme.get("ui.text.focus"); let highlighted = cx.editor.theme.get("special").add_modifier(Modifier::BOLD); @@ -639,6 +647,7 @@ fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { } fn cursor(&self, area: Rect, editor: &Editor) -> (Option, CursorKind) { + use tui::widgets::{Block, Borders}; let block = Block::default().borders(Borders::ALL); // calculate the inner area inside the box let inner = block.inner(area); diff --git a/helix-view/src/ui/popup.rs b/helix-view/src/ui/popup.rs index c8de7e74c..eb6823a7e 100644 --- a/helix-view/src/ui/popup.rs +++ b/helix-view/src/ui/popup.rs @@ -1,11 +1,11 @@ +use crate::compositor::{self, Callback, Component, Context, Event, EventResult, RenderContext}; use crate::{ctrl, key}; -use crate::compositor::{Callback, Component, Context, Event, EventResult, RenderContext}; -use helix_core::Position; use crate::{ graphics::{Margin, Rect}, Editor, }; +use helix_core::Position; // TODO: share logic with Menu, it's essentially Popup(render_fn), but render fn needs to return // a width/height hint. maybe Popup(Box) @@ -175,6 +175,13 @@ fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { Some(self.size) } + fn id(&self) -> Option<&'static str> { + Some(self.id) + } +} + +#[cfg(feature = "term")] +impl compositor::term::Render for Popup { fn render(&mut self, viewport: Rect, cx: &mut RenderContext<'_>) { // trigger required_size so we recalculate if the child changed self.required_size((viewport.width, viewport.height)); @@ -193,8 +200,4 @@ fn render(&mut self, viewport: Rect, cx: &mut RenderContext<'_>) { let inner = area.inner(&self.margin); self.contents.render(inner, cx); } - - fn id(&self) -> Option<&'static str> { - Some(self.id) - } } diff --git a/helix-view/src/ui/prompt.rs b/helix-view/src/ui/prompt.rs index 183d5de46..2d73fb1ea 100644 --- a/helix-view/src/ui/prompt.rs +++ b/helix-view/src/ui/prompt.rs @@ -1,9 +1,8 @@ -use crate::compositor::{Component, Compositor, Context, Event, EventResult, RenderContext}; +use crate::compositor::{self, Component, Compositor, Context, Event, EventResult, RenderContext}; use crate::input::KeyEvent; use crate::keyboard::KeyCode; use crate::{alt, ctrl, key, shift, ui}; use std::{borrow::Cow, ops::RangeFrom}; -use tui::widgets::{Block, Borders, Widget}; use crate::{ graphics::{CursorKind, Margin, Rect}, @@ -324,8 +323,11 @@ pub fn exit_selection(&mut self) { const BASE_WIDTH: u16 = 30; -impl Prompt { - pub fn render_prompt(&self, area: Rect, cx: &mut RenderContext<'_>) { +#[cfg(feature = "term")] +impl compositor::term::Render for Prompt { + fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { + use tui::widgets::{Block, Borders, Widget}; + let theme = &cx.editor.theme; let prompt_color = theme.get("ui.text"); let completion_color = theme.get("ui.statusline"); @@ -438,6 +440,19 @@ pub fn render_prompt(&self, area: Rect, cx: &mut RenderContext<'_>) { prompt_color, ); } + + fn cursor(&self, area: Rect, _editor: &Editor) -> (Option, CursorKind) { + let line = area.height as usize - 1; + ( + Some(Position::new( + area.y as usize + line, + area.x as usize + + self.prompt.len() + + UnicodeWidthStr::width(&self.line[..self.cursor]), + )), + CursorKind::Block, + ) + } } impl Component for Prompt { @@ -545,21 +560,4 @@ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { EventResult::Consumed(None) } - - fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { - self.render_prompt(area, cx) - } - - fn cursor(&self, area: Rect, _editor: &Editor) -> (Option, CursorKind) { - let line = area.height as usize - 1; - ( - Some(Position::new( - area.y as usize + line, - area.x as usize - + self.prompt.len() - + UnicodeWidthStr::width(&self.line[..self.cursor]), - )), - CursorKind::Block, - ) - } } diff --git a/helix-view/src/ui/text.rs b/helix-view/src/ui/text.rs index 224aa2306..520820801 100644 --- a/helix-view/src/ui/text.rs +++ b/helix-view/src/ui/text.rs @@ -1,4 +1,4 @@ -use crate::compositor::{Component, RenderContext}; +use crate::compositor::{self, Component, RenderContext}; use crate::graphics::Rect; pub struct Text { @@ -27,7 +27,8 @@ fn from(contents: tui::text::Text<'static>) -> Self { } } -impl Component for Text { +#[cfg(feature = "term")] +impl compositor::term::Render for Text { fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { use tui::widgets::{Paragraph, Widget, Wrap}; @@ -36,7 +37,9 @@ fn render(&mut self, area: Rect, cx: &mut RenderContext<'_>) { par.render(area, cx.surface); } +} +impl Component for Text { fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { if viewport != self.viewport { let width = std::cmp::min(self.contents.width() as u16, viewport.0);