statusline + ruler

This commit is contained in:
Borodinov Ilya 2024-05-19 16:42:33 +03:00
parent ebe77bd035
commit 1346d8ddbf
Signed by: noth
GPG key ID: 75503B2EF596D1BD
7 changed files with 165 additions and 38 deletions

View file

@ -626,3 +626,65 @@ impl Element for Empty {
) { ) {
} }
} }
impl<T: IntoElement> IntoElement for Option<T> {
type Element = Option<T::Element>;
fn into_element(self) -> Self::Element {
self.map(IntoElement::into_element)
}
}
impl<T: Element> Element for Option<T> {
type PrepaintState = Option<T::PrepaintState>;
type RequestLayoutState = Option<T::RequestLayoutState>;
fn id(&self) -> Option<ElementId> {
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<Pixels>,
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<Pixels>,
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)
}
}
}

View file

@ -19,6 +19,11 @@ impl SharedString {
pub const fn from_static(str: &'static str) -> Self { pub const fn from_static(str: &'static str) -> Self {
Self(ArcCow::Borrowed(str)) Self(ArcCow::Borrowed(str))
} }
/// Explicitly dereferences this [`SharedString`] to a `&`[`str`].
pub fn as_str(&self) -> &str {
self.as_ref()
}
} }
impl Default for SharedString { impl Default for SharedString {

View file

@ -4,6 +4,7 @@ use std::{ops::Range, path::PathBuf};
use widestring::Utf16String; use widestring::Utf16String;
use crate::editor::Theme; use crate::editor::Theme;
use crate::vim::Mode;
use super::*; use super::*;
@ -12,6 +13,9 @@ pub struct Buffer {
pub path: Option<PathBuf>, pub path: Option<PathBuf>,
pub marked: Option<Range<usize>>, pub marked: Option<Range<usize>>,
pub selected: Option<Range<usize>>, pub selected: Option<Range<usize>>,
pub name: SharedString,
pub modified: bool,
pub mode: Mode
} }
impl Buffer { impl Buffer {
@ -19,9 +23,12 @@ impl Buffer {
let text = std::fs::read_to_string(&path)?; let text = std::fs::read_to_string(&path)?;
Ok(Self { Ok(Self {
text: text.into(), text: text.into(),
name: SharedString::from(path.display().to_string()),
path: Some(path), path: Some(path),
marked: None, marked: None,
selected: None, selected: None,
modified: false,
mode: Mode::default()
}) })
} }
@ -31,6 +38,9 @@ impl Buffer {
path: None, path: None,
marked: None, marked: None,
selected: None, selected: None,
name: SharedString::from_static("<scratch>"),
modified: false,
mode: Mode::default()
} }
} }

View file

@ -10,7 +10,7 @@ mod element;
mod input; mod input;
pub struct EditorSettings { pub struct EditorSettings {
style: EditorStyle, pub style: EditorStyle,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -163,15 +163,17 @@ impl EditorSettings {
pub struct Editor { pub struct Editor {
focus_handle: FocusHandle, focus_handle: FocusHandle,
buf: Model<crate::buffer::Buffer>, buf: Model<crate::buffer::Buffer>,
settings: Model<EditorSettings>, pub settings: Model<EditorSettings>,
cursor: Point<usize>, cursor: Point<usize>,
scroll: Point<Pixels>, scroll: Point<Pixels>,
// in percent
ruler: u8,
logger: Logger, logger: Logger,
} }
impl Editor { impl Editor {
pub fn make<V: 'static>( pub fn make(
cx: &mut ViewContext<V>, cx: &mut WindowContext,
settings: Model<EditorSettings>, settings: Model<EditorSettings>,
logger: Logger, logger: Logger,
) -> View<Self> { ) -> View<Self> {
@ -188,16 +190,59 @@ impl Editor {
logger, logger,
cursor: Point::default(), cursor: Point::default(),
scroll: Point::default(), scroll: Point::default(),
ruler: 0,
}) })
} }
fn statusline(&self, cx: &mut ViewContext<Self>) -> 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 { impl Render for Editor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
cx.focus(&self.focus_handle); cx.focus(&self.focus_handle);
let styled = {
// -1 read...
let style = &self.settings.read(cx).style;
div() div()
.key_context("Editor") .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| { .on_action(cx.listener(|me, event: &action::CursorDown, cx| {
me.cursor.y = me.cursor.y.saturating_add(1).min( me.cursor.y = me.cursor.y.saturating_add(1).min(
me.buf me.buf
@ -213,11 +258,17 @@ impl Render for Editor {
me.cursor.y = me.cursor.y.saturating_sub(1); me.cursor.y = me.cursor.y.saturating_sub(1);
funnylog::trace!(me.logger, ?me.cursor, "up"); funnylog::trace!(me.logger, ?me.cursor, "up");
})) }))
.overflow_hidden() .flex()
.child(element::EditorElement::new( .flex_col()
.justify_start()
.min_h_full()
.min_w_full()
.size(relative(1.))
.child(div().overflow_hidden().child(element::EditorElement::new(
cx.view(), cx.view(),
self.settings.read(cx).style.clone(), self.settings.read(cx).style.clone(),
)) )))
.child(self.statusline(cx))
} }
} }

View file

@ -172,8 +172,16 @@ impl Element for EditorElement {
&Point::default(), &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!( log::trace!(
"[{prev_editor_scroll:#?} -> {:#?}] {scroll:#?}", "[{prev_editor_scroll:#?} -> {:#?}] [{prev_ruler}% -> {next_ruler}%] {scroll:#?}",
editor.scroll editor.scroll
); );

View file

@ -4,26 +4,28 @@ use std::sync::Arc;
use funnylog::{filter::LevelFilter, span, Drain, Level}; use funnylog::{filter::LevelFilter, span, Drain, Level};
use miette::{Diagnostic, Report, WrapErr}; use miette::{Diagnostic, Report, WrapErr};
use ming::{prelude::*, *, Context}; use ming::{prelude::*, Context, *};
mod buffer; mod buffer;
mod editor; mod editor;
mod vim;
trait MietteContext<T> { trait MietteContext<T> {
fn context(self, msg: impl std::fmt::Display + std::fmt::Debug + Send + Sync + 'static) -> Result<T, Report>; fn context(
self,
msg: impl std::fmt::Display + std::fmt::Debug + Send + Sync + 'static,
) -> Result<T, Report>;
} }
impl<T> MietteContext<T> for Option<T> { impl<T> MietteContext<T> for Option<T> {
fn context(self, msg: impl std::fmt::Display + std::fmt::Debug + Send + Sync + 'static) -> Result<T, Report> { fn context(
self,
msg: impl std::fmt::Display + std::fmt::Debug + Send + Sync + 'static,
) -> Result<T, Report> {
self.ok_or_else(|| Report::msg(msg)) self.ok_or_else(|| Report::msg(msg))
} }
} }
struct Nite {
editor: View<editor::Editor>,
logger: Logger,
}
type Logger = funnylog::Logger< type Logger = funnylog::Logger<
Arc< Arc<
funnylog::IgnoreError< funnylog::IgnoreError<
@ -32,21 +34,6 @@ type Logger = funnylog::Logger<
>, >,
>; >;
impl Render for Nite {
fn render(&mut self, cx: &mut ViewContext<Self>) -> 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() { fn main() {
miette::set_panic_hook(); miette::set_panic_hook();
let drain = Arc::new( let drain = Arc::new(
@ -58,7 +45,7 @@ fn main() {
funnylog::stdlog::setup_with_level(drain.clone(), LevelFilter(Some(Level::Trace))).unwrap(); funnylog::stdlog::setup_with_level(drain.clone(), LevelFilter(Some(Level::Trace))).unwrap();
let logger = Logger::new(drain); let logger = Logger::new(drain);
App::new().run(|cx| { App::new().run(move |cx| {
cx.bind_keys([ cx.bind_keys([
KeyBinding::new("up", editor::action::CursorUp, Some("Editor")), KeyBinding::new("up", editor::action::CursorUp, Some("Editor")),
KeyBinding::new("down", editor::action::CursorDown, Some("Editor")), KeyBinding::new("down", editor::action::CursorDown, Some("Editor")),
@ -71,10 +58,7 @@ fn main() {
funnylog::info!(logger, "Hello from nite"); funnylog::info!(logger, "Hello from nite");
let settings = cx.new_model(|_cx| editor::EditorSettings::load_defaults()); let settings = cx.new_model(|_cx| editor::EditorSettings::load_defaults());
cx.new_view(|cx| Nite { editor::Editor::make(cx, settings, logger.branch(span!("editor")))
editor: editor::Editor::make(cx, settings, logger.branch(span!("editor"))),
logger,
})
}, },
); );
}) })

7
src/vim.rs Normal file
View file

@ -0,0 +1,7 @@
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Mode {
#[default]
Normal,
Insert,
Visual
}