duat-term 0.10.0

A frontend for Duat for the terminal
Documentation
//! A vertical ruler, usually to separate the [`Buffer`] from other
//! widgets
//!
//! This is just a simple widget, which should only really make sense
//! in a terminal context, since, in GUIs, you'd use styling of the
//! widget areas in order to accomplish the same thing.
//!
//! [`Buffer`]: duat_core::buffer::Buffer
use duat_core::{
    context::Handle,
    data::Pass,
    hook::{self, BufferUpdated},
    text::{Text, TextMut, txt},
    ui::{PushSpecs, PushTarget, Side, Widget},
};

/// Add a [`VertRule`] hook.
pub fn add_vertrule_hook() {
    hook::add::<BufferUpdated>(|pa, buffer| {
        for (vertrule, _) in buffer.get_related::<VertRule>(pa) {
            let (buf, vr, area) = (buffer.read(pa), &vertrule.read(pa), vertrule.area());

            let text = if let SepChar::ThreeWay(..) | SepChar::TwoWay(..) = vr.sep_char {
                let lines = buffer.printed_line_numbers(pa);
                let (upper, middle, lower) = {
                    if let Some(main) = buf.selections().get_main() {
                        let main = main.caret().line();
                        let upper = lines.iter().filter(|&line| line.number < main).count();
                        let middle = lines.iter().filter(|&line| line.number == main).count();
                        let lower = lines.iter().filter(|&line| line.number > main).count();
                        (upper, middle, lower)
                    } else {
                        (0, lines.len(), 0)
                    }
                };

                let chars = vr.sep_char.chars();
                txt!(
                    "[rule.upper]{}[]{}[rule.lower]{}",
                    form_string(chars[0], upper),
                    form_string(chars[1], middle),
                    form_string(chars[2], lower)
                )
            } else {
                let full_line =
                    format!("{}\n", vr.sep_char.chars()[1]).repeat(area.height(pa) as usize);

                txt!("{full_line}")
            };

            vertrule.write(pa).text = text;
        }
    });
}

/// A vertical line on screen, useful, for example, for the separation
/// of a [`Buffer`] and [`LineNumbers`].
///
/// By default, this [`VertRule`] will show the `'│'` character on the
/// whole line, using the `"default.VertRule"` form. However, you can
/// change that with the [`VertRule::sep_char`] field.
///
/// This field, of type [`SepChar`], can have up to 3 distinct
/// characters, in order to differentiate between the main line's
/// separator and even the separators above and below the main line.
///
/// If the main character is not the same as the other two characters,
/// then the line will be printed with the `"rule.upper"` and
/// `"rule.lower"` forms for the characters above and below.
///
/// [`Buffer`]: duat_core::buffer::Buffer
/// [`LineNumbers`]: https://docs.rs/duat-utils/latest/duat_utils/widgets/struct.LineNumbers.html
pub struct VertRule {
    text: Text,
    pub sep_char: SepChar,
}

impl VertRule {
    /// Returns a [`VertRuleBuilder`], letting you push your own
    /// [`VertRule`]s around
    pub fn builder() -> VertRuleBuilder {
        VertRuleBuilder::default()
    }
}

impl Widget for VertRule {
    fn text(&self) -> &Text {
        &self.text
    }

    fn text_mut(&mut self) -> TextMut<'_> {
        self.text.as_mut()
    }
}

/// The configurations for the [`VertRule`] widget.
#[derive(Default, Clone)]
pub struct VertRuleBuilder {
    pub sep_char: SepChar,
    pub on_the_right: bool,
}

impl VertRuleBuilder {
    /// Pushes a [`VertRule`] to a [`PushTarget`]
    pub fn push_on(self, pa: &mut Pass, push_target: &impl PushTarget) -> Handle<VertRule> {
        let vert_rule = VertRule {
            text: Text::default(),
            sep_char: self.sep_char,
        };

        let specs = PushSpecs {
            side: if self.on_the_right {
                Side::Right
            } else {
                Side::Left
            },
            width: Some(1.0),
            ..Default::default()
        };

        push_target.push_outer(pa, vert_rule, specs)
    }
}

/// The [`char`]s that should be printed above, equal to, and below
/// the main line.
#[derive(Clone)]
pub enum SepChar {
    Uniform(char),
    /// Order: main line, other lines.
    TwoWay(char, char),
    /// Order: main line, above main line, below main line.
    ThreeWay(char, char, char),
}

impl SepChar {
    /// The [`char`]s above, equal to, and below the main line,
    /// respectively.
    fn chars(&self) -> [char; 3] {
        match self {
            SepChar::Uniform(uniform) => [*uniform, *uniform, *uniform],
            SepChar::TwoWay(main, other) => [*other, *main, *other],
            SepChar::ThreeWay(main, upper, lower) => [*upper, *main, *lower],
        }
    }
}

impl Default for SepChar {
    fn default() -> Self {
        Self::Uniform(' ')
    }
}

fn form_string(char: char, count: usize) -> String {
    [char, '\n'].repeat(count).iter().collect()
}