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,
42    /// Where to align the numbers
43    ///
44    /// The default is [`Alignment::Left`]
45    pub align: Alignment,
46    /// Where to align the main line number
47    ///
48    /// The default is [`Alignment::Right`]
49    pub main_align: Alignment,
50    /// Wether to show wrapped line's numbers
51    ///
52    /// The default is `false`
53    pub show_wraps: bool,
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(Clone, Copy, Debug)]
140pub struct LineNumbersOpts {
141    /// Wether to show relative numbering
142    ///
143    /// The default is `false`
144    pub relative: bool,
145    /// Where to align the numbers
146    ///
147    /// The default is [`Alignment::Left`]
148    pub align: Alignment,
149    /// Where to align the main line number
150    ///
151    /// The default is [`Alignment::Right`]
152    pub main_align: Alignment,
153    /// Wether to show wrapped line's numbers
154    ///
155    /// The default is `false`
156    pub show_wraps: bool,
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,
161}
162
163impl LineNumbersOpts {
164    /// Retunrs a new `LineNumbersOpts`
165    pub const fn new() -> Self {
166        Self {
167            relative: false,
168            align: Alignment::Left,
169            main_align: Alignment::Right,
170            show_wraps: false,
171            on_the_right: false,
172        }
173    }
174
175    /// Push the [`LineNumbers`] to a [`Handle`]
176    pub fn push_on(self, pa: &mut Pass, handle: &Handle) -> Handle<LineNumbers> {
177        let mut line_numbers = LineNumbers {
178            buffer: handle.clone(),
179            text: Text::default(),
180            relative: self.relative,
181            align: self.align,
182            main_align: self.main_align,
183            show_wraps: self.show_wraps,
184        };
185        line_numbers.text = line_numbers.form_text(pa);
186
187        let specs = PushSpecs {
188            side: if self.on_the_right {
189                Side::Right
190            } else {
191                Side::Left
192            },
193            ..Default::default()
194        };
195
196        handle.push_outer_widget(pa, line_numbers, specs)
197    }
198}
199
200impl Default for LineNumbersOpts {
201    fn default() -> Self {
202        Self::new()
203    }
204}
205
206/// Writes the text of the line number to a given [`String`].
207fn push_text(b: &mut Builder, line: usize, main: usize, is_wrapped: bool, opts: &LineNumbers) {
208    if (!is_wrapped || opts.show_wraps) && main != usize::MAX {
209        let num = if opts.relative {
210            if line != main {
211                line.abs_diff(main)
212            } else {
213                line + 1
214            }
215        } else {
216            line + 1
217        };
218        b.push(num);
219    }
220
221    b.push("\n");
222    b.push(form::DEFAULT_ID);
223}
224
225fn align(b: &mut Builder, alignment: Alignment) {
226    match alignment {
227        Alignment::Left => b.push(AlignLeft),
228        Alignment::Center => b.push(AlignCenter),
229        Alignment::Right => b.push(AlignRight),
230    }
231}