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 {
Self(ArcCow::Borrowed(str))
}
/// Explicitly dereferences this [`SharedString`] to a `&`[`str`].
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
impl Default for SharedString {

View file

@ -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<PathBuf>,
pub marked: Option<Range<usize>>,
pub selected: Option<Range<usize>>,
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("<scratch>"),
modified: false,
mode: Mode::default()
}
}

View file

@ -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<crate::buffer::Buffer>,
settings: Model<EditorSettings>,
pub settings: Model<EditorSettings>,
cursor: Point<usize>,
scroll: Point<Pixels>,
// in percent
ruler: u8,
logger: Logger,
}
impl Editor {
pub fn make<V: 'static>(
cx: &mut ViewContext<V>,
pub fn make(
cx: &mut WindowContext,
settings: Model<EditorSettings>,
logger: Logger,
) -> View<Self> {
@ -188,16 +190,59 @@ impl Editor {
logger,
cursor: 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 {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
cx.focus(&self.focus_handle);
let styled = {
// -1 read...
let style = &self.settings.read(cx).style;
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| {
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))
}
}

View file

@ -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
);

View file

@ -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<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> {
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))
}
}
struct Nite {
editor: View<editor::Editor>,
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<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() {
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")))
},
);
})

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
}