duat_base/widgets/
line_numbers.rs

1//! Line numbers for a [`Buffer`]
2//!
3//! These are pretty standard like in most text editors. Usually,
4//! they'll be printed on the right of the [`Buffer`], but there is an
5//! option to print them on the right, if you need such functionality.
6//!
7//! You can also change other things, like the
8//! relativeness/absoluteness of the numbers, as well as the alignment
9//! of the numbers, with one more option to change that of the main
10//! selection's line number.
11//!
12//! [`Buffer`]: duat_core::buffer::Buffer
13use std::fmt::Alignment;
14
15use duat_core::{
16    context::Handle,
17    data::Pass,
18    form,
19    text::{AlignCenter, AlignLeft, AlignRight, Builder, Text},
20    ui::{PushSpecs, Side, Widget},
21};
22
23/// Shows a column of line numbers beside the [`Buffer`]
24/// 
25/// There are various fields that you can use to configure how the
26/// `LineNumbers` will be displayed. They control things like the
27/// line numbers and the relativeness of the number displayed.
28/// 
29/// This is a default struct of Duat, that is, it is automatically
30/// placed around every `Buffer`, but you can disable that behavior
31/// by [removing] the `"BufferWidgets"` hook.
32///
33/// [`Buffer`]: duat_core::buffer::Buffer
34/// [removing]: duat_core::hook::remove
35pub struct LineNumbers {
36    buffer: Handle,
37    text: Text,
38    /// Wether to show relative numbering
39    ///
40    /// The default is `false`
41    pub relative: bool = false,
42    /// Where to align the numbers
43    ///
44    /// The default is [`Alignment::Left`]
45    pub align: Alignment = Alignment::Left,
46    /// Where to align the main line number
47    ///
48    /// The default is [`Alignment::Right`]
49    pub main_align: Alignment = Alignment::Right,
50    /// Wether to show wrapped line's numbers
51    ///
52    /// The default is `false`
53    pub show_wraps: bool = false,
54}
55
56impl LineNumbers {
57    /// Returns a [`LineNumbersOpts`], used to create a new
58    /// `LineNumbers`
59    pub fn builder() -> LineNumbersOpts {
60        LineNumbersOpts::default()
61    }
62
63    /// The minimum width that would be needed to show the last line.
64    fn calculate_width(&self, pa: &Pass) -> f32 {
65        let len = self.buffer.read(pa).text().len().line();
66        len.ilog10() as f32
67    }
68
69    fn form_text(&self, pa: &Pass) -> Text {
70        let (main_line, printed_lines) = {
71            let buffer = self.buffer.read(pa);
72            let main_line = if buffer.selections().is_empty() {
73                usize::MAX
74            } else {
75                buffer.selections().get_main().unwrap().line()
76            };
77
78            (main_line, buffer.printed_lines().to_vec())
79        };
80
81        let mut builder = Text::builder();
82        align(&mut builder, self.align);
83
84        for (index, (line, is_wrapped)) in printed_lines.iter().enumerate() {
85            if *line == main_line {
86                align(&mut builder, self.main_align);
87            }
88
89            match (*line == main_line, is_wrapped) {
90                (false, false) => {}
91                (true, false) => builder.push(form::id_of!("linenum.main")),
92                (false, true) => builder.push(form::id_of!("linenum.wrapped")),
93                (true, true) => builder.push(form::id_of!("linenum.wrapped.main")),
94            }
95
96            let is_wrapped = *is_wrapped && index > 0;
97            push_text(&mut builder, *line, main_line, is_wrapped, self);
98
99            if *line == main_line {
100                align(&mut builder, self.align);
101            }
102        }
103
104        builder.build()
105    }
106}
107
108impl Widget for LineNumbers {
109    fn update(pa: &mut Pass, handle: &Handle<Self>) {
110        let width = handle.read(pa).calculate_width(pa);
111        handle.area().set_width(pa, width + 1.0).unwrap();
112
113        handle.write(pa).text = handle.read(pa).form_text(pa);
114    }
115
116    fn needs_update(&self, pa: &Pass) -> bool {
117        self.buffer.has_changed(pa)
118    }
119
120    fn text(&self) -> &Text {
121        &self.text
122    }
123
124    fn text_mut(&mut self) -> &mut Text {
125        &mut self.text
126    }
127}
128
129/// Options for cosntructing a [`LineNumbers`] [`Widget`]
130///
131/// For most options, you can just set them in the `Widget`
132/// directly (through a [hook] or something). Right now, the
133/// only option exclusive to this struct is the [`on_the_right`]
134/// option, which places the `LineNumbers` on the right, as
135/// opposed to on the left.
136///
137/// [`on_the_right`]: Self::on_the_right
138/// [hook]: duat_core::hook
139#[derive(Default, Clone, Copy, Debug)]
140pub struct LineNumbersOpts {
141    /// Wether to show relative numbering
142    ///
143    /// The default is `false`
144    pub relative: bool = false,
145    /// Where to align the numbers
146    ///
147    /// The default is [`Alignment::Left`]
148    pub align: Alignment = Alignment::Left,
149    /// Where to align the main line number
150    ///
151    /// The default is [`Alignment::Right`]
152    pub main_align: Alignment = Alignment::Right,
153    /// Wether to show wrapped line's numbers
154    ///
155    /// The default is `false`
156    pub show_wraps: bool = false,
157    /// Place this [`Widget`] on the right, as opposed to on the left
158    ///
159    /// The default is `false`
160    pub on_the_right: bool = false,
161}
162
163impl LineNumbersOpts {
164    /// Push the [`LineNumbers`] to a [`Handle`]
165    pub fn push_on(self, pa: &mut Pass, handle: &Handle) -> Handle<LineNumbers> {
166        let mut line_numbers = LineNumbers {
167            buffer: handle.clone(),
168            text: Text::default(),
169            relative: self.relative,
170            align: self.align,
171            main_align: self.main_align,
172            show_wraps: self.show_wraps,
173        };
174        line_numbers.text = line_numbers.form_text(pa);
175
176        let specs = PushSpecs {
177            side: if self.on_the_right {
178                Side::Right
179            } else {
180                Side::Left
181            },
182            ..
183        };
184
185        handle.push_outer_widget(pa, line_numbers, specs)
186    }
187}
188
189/// Writes the text of the line number to a given [`String`].
190fn push_text(b: &mut Builder, line: usize, main: usize, is_wrapped: bool, opts: &LineNumbers) {
191    if (!is_wrapped || opts.show_wraps) && main != usize::MAX {
192        let num = if opts.relative {
193            if line != main {
194                line.abs_diff(main)
195            } else {
196                line + 1
197            }
198        } else {
199            line + 1
200        };
201        b.push(num);
202    }
203
204    b.push("\n");
205    b.push(form::DEFAULT_ID);
206}
207
208fn align(b: &mut Builder, alignment: Alignment) {
209    match alignment {
210        Alignment::Left => b.push(AlignLeft),
211        Alignment::Center => b.push(AlignCenter),
212        Alignment::Right => b.push(AlignRight),
213    }
214}