use std::{fmt::Alignment, marker::PhantomData};
use crate::{
context::{self, FileReader},
forms::{self, Form},
text::{Builder, Tag, Text, text},
ui::{Area, Constraint, PushSpecs, Ui},
widgets::{Widget, WidgetCfg},
};
pub struct LineNumbers<U: Ui> {
reader: FileReader<U>,
text: Text,
cfg: LineNumbersCfg<U>,
}
impl<U: Ui> LineNumbers<U> {
fn calculate_width(&mut self) -> f32 {
let len = self.reader.inspect(|file, _, _| file.text().len().line()) + 1;
len.ilog10() as f32
}
fn update_text(&mut self) {
self.text = self.reader.inspect(|file, _, cursors| {
let printed_lines = file.printed_lines();
let main_line = if cursors.is_empty() {
u32::MAX
} else {
cursors.main().line()
};
let mut builder = Text::builder();
text!(builder, { tag_from_align(self.cfg.align) });
for (index, (line, is_wrapped)) in printed_lines.iter().enumerate() {
if main_line == *line {
text!(builder, { tag_from_align(self.cfg.main_align) });
}
match (main_line == *line, is_wrapped) {
(false, false) => text!(builder, [LineNum]),
(true, false) => text!(builder, [MainLineNum]),
(false, true) => text!(builder, [WrappedLineNum]),
(true, true) => text!(builder, [WrappedMainLineNum]),
}
let is_wrapped = *is_wrapped && index > 0;
push_text(&mut builder, *line, main_line, is_wrapped, &self.cfg);
if main_line == *line {
text!(builder, { tag_from_align(self.cfg.align) });
}
}
builder.finish()
});
}
}
impl<U: Ui> Widget<U> for LineNumbers<U> {
type Cfg = LineNumbersCfg<U>;
fn cfg() -> Self::Cfg {
LineNumbersCfg::new()
}
fn update(&mut self, area: &U::Area) {
let width = self.calculate_width();
area.constrain_hor(Constraint::Length(width + 1.0)).unwrap();
self.update_text();
}
fn text(&self) -> &Text {
&self.text
}
fn text_mut(&mut self) -> &mut Text {
&mut self.text
}
fn once() {
forms::set_weak("LineNum", Form::grey());
forms::set_weak("MainLineNum", Form::yellow());
forms::set_weak("WrappedLineNum", Form::cyan().italic());
forms::set_weak("WrappedMainLineNum", "WrappedLineNum");
}
}
#[derive(Default, Debug, Copy, Clone)]
enum Numbers {
#[default]
Absolute,
Relative,
RelAbs,
}
#[derive(Debug, Clone, Copy)]
pub struct LineNumbersCfg<U> {
numbers: Numbers,
align: Alignment,
main_align: Alignment,
show_wraps: bool,
specs: PushSpecs,
ghost: PhantomData<U>,
}
impl<U> Default for LineNumbersCfg<U> {
fn default() -> Self {
Self::new()
}
}
impl<U> LineNumbersCfg<U> {
pub fn new() -> Self {
Self {
numbers: Numbers::Absolute,
align: Alignment::Left,
main_align: Alignment::Right,
show_wraps: false,
specs: PushSpecs::left(),
ghost: PhantomData,
}
}
pub fn absolute(self) -> Self {
Self { numbers: Numbers::Absolute, ..self }
}
pub fn relative(self) -> Self {
Self { numbers: Numbers::Relative, ..self }
}
pub fn rel_abs(self) -> Self {
Self { numbers: Numbers::RelAbs, ..self }
}
pub fn align_left(self) -> Self {
Self {
main_align: Alignment::Left,
align: Alignment::Left,
..self
}
}
pub fn align_center(self) -> Self {
Self {
main_align: Alignment::Center,
align: Alignment::Center,
..self
}
}
pub fn align_right(self) -> Self {
Self {
main_align: Alignment::Right,
align: Alignment::Right,
..self
}
}
pub fn align_main_left(self) -> Self {
Self { main_align: Alignment::Left, ..self }
}
pub fn align_main_center(self) -> Self {
Self { main_align: Alignment::Center, ..self }
}
pub fn align_main_right(self) -> Self {
Self { main_align: Alignment::Right, ..self }
}
pub fn show_wraps(self) -> Self {
Self { show_wraps: true, ..self }
}
pub fn hide_wraps(self) -> Self {
Self { show_wraps: false, ..self }
}
pub fn on_the_right(self) -> Self {
Self { specs: self.specs.to_right(), ..self }
}
}
impl<U: Ui> WidgetCfg<U> for LineNumbersCfg<U> {
type Widget = LineNumbers<U>;
fn build(self, _: bool) -> (Self::Widget, impl Fn() -> bool, PushSpecs) {
let reader = context::cur_file().unwrap().fixed_reader();
let specs = self.specs;
let mut widget = LineNumbers {
reader: reader.clone(),
text: Text::default(),
cfg: self,
};
widget.update_text();
(widget, move || reader.has_changed(), specs)
}
}
fn push_text<U>(
builder: &mut Builder,
line: u32,
main: u32,
is_wrapped: bool,
cfg: &LineNumbersCfg<U>,
) {
if is_wrapped && !cfg.show_wraps {
text!(*builder, "\n");
} else if main != u32::MAX {
let num = match cfg.numbers {
Numbers::Absolute => line + 1,
Numbers::Relative => line.abs_diff(main),
Numbers::RelAbs => {
if line != main {
line.abs_diff(main)
} else {
line + 1
}
}
};
text!(*builder, num "\n");
} else {
text!(*builder, { line + 1 } "\n");
}
}
fn tag_from_align(alignment: Alignment) -> Tag {
match alignment {
Alignment::Left => Tag::StartAlignLeft,
Alignment::Right => Tag::StartAlignRight,
Alignment::Center => Tag::StartAlignCenter,
}
}