diff --git a/crates/ming/src/element.rs b/crates/ming/src/element.rs index 18861d2..3721caf 100644 --- a/crates/ming/src/element.rs +++ b/crates/ming/src/element.rs @@ -626,3 +626,65 @@ impl Element for Empty { ) { } } + +impl IntoElement for Option { + type Element = Option; + + fn into_element(self) -> Self::Element { + self.map(IntoElement::into_element) + } +} + +impl Element for Option { + type PrepaintState = Option; + type RequestLayoutState = Option; + + fn id(&self) -> Option { + self.as_ref().and_then(Element::id) + } + + fn request_layout( + &mut self, + id: Option<&GlobalElementId>, + cx: &mut WindowContext, + ) -> (LayoutId, Self::RequestLayoutState) { + if let Some(me) = self.as_mut() { + let (id, state) = me.request_layout(id, cx); + (id, Some(state)) + } else { + (cx.request_layout(Style::default(), None), None) + } + } + + fn prepaint( + &mut self, + id: Option<&GlobalElementId>, + bounds: Bounds, + request_layout: &mut Self::RequestLayoutState, + cx: &mut WindowContext, + ) -> Self::PrepaintState { + if let Some(me) = self.as_mut() { + let request_layout = request_layout.as_mut().unwrap(); + + Some(me.prepaint(id, bounds, request_layout, cx)) + } else { + None + } + } + + fn paint( + &mut self, + id: Option<&GlobalElementId>, + bounds: Bounds, + request_layout: &mut Self::RequestLayoutState, + prepaint: &mut Self::PrepaintState, + cx: &mut WindowContext, + ) { + if let Some(me) = self.as_mut() { + let request_layout = request_layout.as_mut().unwrap(); + let prepaint = prepaint.as_mut().unwrap(); + + me.paint(id, bounds, request_layout, prepaint, cx) + } + } +} diff --git a/crates/ming/src/shared_string.rs b/crates/ming/src/shared_string.rs index 52a036f..28793b2 100644 --- a/crates/ming/src/shared_string.rs +++ b/crates/ming/src/shared_string.rs @@ -19,6 +19,11 @@ impl SharedString { pub const fn from_static(str: &'static str) -> Self { Self(ArcCow::Borrowed(str)) } + + /// Explicitly dereferences this [`SharedString`] to a `&`[`str`]. + pub fn as_str(&self) -> &str { + self.as_ref() + } } impl Default for SharedString { diff --git a/src/buffer.rs b/src/buffer.rs index 01c1128..c85e48f 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -4,6 +4,7 @@ use std::{ops::Range, path::PathBuf}; use widestring::Utf16String; use crate::editor::Theme; +use crate::vim::Mode; use super::*; @@ -12,6 +13,9 @@ pub struct Buffer { pub path: Option, pub marked: Option>, pub selected: Option>, + pub name: SharedString, + pub modified: bool, + pub mode: Mode } impl Buffer { @@ -19,9 +23,12 @@ impl Buffer { let text = std::fs::read_to_string(&path)?; Ok(Self { text: text.into(), + name: SharedString::from(path.display().to_string()), path: Some(path), marked: None, selected: None, + modified: false, + mode: Mode::default() }) } @@ -31,6 +38,9 @@ impl Buffer { path: None, marked: None, selected: None, + name: SharedString::from_static(""), + modified: false, + mode: Mode::default() } } diff --git a/src/editor.rs b/src/editor.rs index d5d14fe..7ad0efb 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -10,7 +10,7 @@ mod element; mod input; pub struct EditorSettings { - style: EditorStyle, + pub style: EditorStyle, } #[derive(Clone, Copy, Debug)] @@ -163,15 +163,17 @@ impl EditorSettings { pub struct Editor { focus_handle: FocusHandle, buf: Model, - settings: Model, + pub settings: Model, cursor: Point, scroll: Point, + // in percent + ruler: u8, logger: Logger, } impl Editor { - pub fn make( - cx: &mut ViewContext, + pub fn make( + cx: &mut WindowContext, settings: Model, logger: Logger, ) -> View { @@ -188,16 +190,59 @@ impl Editor { logger, cursor: Point::default(), scroll: Point::default(), + ruler: 0, }) } + + fn statusline(&self, cx: &mut ViewContext) -> impl IntoElement { + let buf = self.buf.read(cx); + + div() + .debug() + .flex() + .flex_row() + .justify_between() + .child( + div() + .flex() + .flex_row() + .gap_4() + .child(match buf.mode { + vim::Mode::Normal => "", + vim::Mode::Insert => "INSERT", + vim::Mode::Visual => "VISUAL", + }) + .child(buf.name.clone()) + .child(if buf.modified { "[+]" } else { "" }), + ) + .child( + div() + .flex() + .flex_row() + .gap_4() + .child(format!("{},{}", self.cursor.x, self.cursor.y)) + .child(match self.ruler { + ..10 => format!("Top"), + 90.. => format!("Bot"), + other => format!("{other}%"), + }), + ) + } } impl Render for Editor { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { cx.focus(&self.focus_handle); + let styled = { + // -1 read... + let style = &self.settings.read(cx).style; - div() - .key_context("Editor") + div() + .font_family(style.font_family.clone()) + .text_color(style.theme.text_color) + }; + + styled.key_context("Editor") .on_action(cx.listener(|me, event: &action::CursorDown, cx| { me.cursor.y = me.cursor.y.saturating_add(1).min( me.buf @@ -213,11 +258,17 @@ impl Render for Editor { me.cursor.y = me.cursor.y.saturating_sub(1); funnylog::trace!(me.logger, ?me.cursor, "up"); })) - .overflow_hidden() - .child(element::EditorElement::new( + .flex() + .flex_col() + .justify_start() + .min_h_full() + .min_w_full() + .size(relative(1.)) + .child(div().overflow_hidden().child(element::EditorElement::new( cx.view(), self.settings.read(cx).style.clone(), - )) + ))) + .child(self.statusline(cx)) } } diff --git a/src/editor/element.rs b/src/editor/element.rs index 8fa40d8..8fbdcd9 100644 --- a/src/editor/element.rs +++ b/src/editor/element.rs @@ -172,8 +172,16 @@ impl Element for EditorElement { &Point::default(), ); + let prev_ruler = editor.ruler; + let next_ruler = (editor.scroll.y.0 / bounds.size.height.0 * 100.).abs().floor() as u8; + + if prev_ruler != next_ruler { + editor.ruler = next_ruler; + cx.notify(); + } + log::trace!( - "[{prev_editor_scroll:#?} -> {:#?}] {scroll:#?}", + "[{prev_editor_scroll:#?} -> {:#?}] [{prev_ruler}% -> {next_ruler}%] {scroll:#?}", editor.scroll ); diff --git a/src/main.rs b/src/main.rs index d4a9d87..b76b7c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,26 +4,28 @@ use std::sync::Arc; use funnylog::{filter::LevelFilter, span, Drain, Level}; use miette::{Diagnostic, Report, WrapErr}; -use ming::{prelude::*, *, Context}; +use ming::{prelude::*, Context, *}; mod buffer; mod editor; +mod vim; trait MietteContext { - fn context(self, msg: impl std::fmt::Display + std::fmt::Debug + Send + Sync + 'static) -> Result; + fn context( + self, + msg: impl std::fmt::Display + std::fmt::Debug + Send + Sync + 'static, + ) -> Result; } impl MietteContext for Option { - fn context(self, msg: impl std::fmt::Display + std::fmt::Debug + Send + Sync + 'static) -> Result { + fn context( + self, + msg: impl std::fmt::Display + std::fmt::Debug + Send + Sync + 'static, + ) -> Result { self.ok_or_else(|| Report::msg(msg)) } } -struct Nite { - editor: View, - logger: Logger, -} - type Logger = funnylog::Logger< Arc< funnylog::IgnoreError< @@ -32,21 +34,6 @@ type Logger = funnylog::Logger< >, >; -impl Render for Nite { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - div() - .flex() - .size(Length::Definite(DefiniteLength::Fraction(1f32))) - .bg(transparent_black()) - .justify_start() - .items_start() - .text_xl() - .text_color(white()) - .font_family("ComicShannsMono Nerd Font Mono") - .child(self.editor.clone()) - } -} - fn main() { miette::set_panic_hook(); let drain = Arc::new( @@ -58,7 +45,7 @@ fn main() { funnylog::stdlog::setup_with_level(drain.clone(), LevelFilter(Some(Level::Trace))).unwrap(); let logger = Logger::new(drain); - App::new().run(|cx| { + App::new().run(move |cx| { cx.bind_keys([ KeyBinding::new("up", editor::action::CursorUp, Some("Editor")), KeyBinding::new("down", editor::action::CursorDown, Some("Editor")), @@ -71,10 +58,7 @@ fn main() { funnylog::info!(logger, "Hello from nite"); let settings = cx.new_model(|_cx| editor::EditorSettings::load_defaults()); - cx.new_view(|cx| Nite { - editor: editor::Editor::make(cx, settings, logger.branch(span!("editor"))), - logger, - }) + editor::Editor::make(cx, settings, logger.branch(span!("editor"))) }, ); }) diff --git a/src/vim.rs b/src/vim.rs new file mode 100644 index 0000000..52066d9 --- /dev/null +++ b/src/vim.rs @@ -0,0 +1,7 @@ +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum Mode { + #[default] + Normal, + Insert, + Visual +}