duat_utils/widgets/
line_numbers.rs

1//! Line numbers for a [`File`]
2//!
3//! These are pretty standard like in most text editors. Usually,
4//! they'll be printed on the right of the [`File`], 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//! [`File`]: duat_core::file::File
13use std::{fmt::Alignment, marker::PhantomData};
14
15use duat_core::{prelude::*, text::Builder, ui::Constraint};
16
17/// Shows a column of line numbers beside the [`File`]
18///
19/// This can be configured through [`LineNumbers::cfg`], in order to
20/// get, for example: relative numbering, different alignment,
21/// hidden/shown wrapped lines, etc.
22pub struct LineNumbers<U: Ui> {
23    handle: Handle<File<U>, U>,
24    text: Text,
25    /// The options of these [`LineNumbers`]
26    opts: LineNumbersCfg<U>,
27}
28
29impl<U: Ui> LineNumbers<U> {
30    /// Absolute numbering, first line is 1, second is 2, etc
31    pub fn absolute(&mut self) -> &mut Self {
32        self.opts = self.opts.absolute();
33        self
34    }
35
36    /// Relative numbering, cursor line is 0, surrounding is 1, etc
37    pub fn relative(&mut self) -> &mut Self {
38        self.opts = self.opts.relative();
39        self
40    }
41
42    /// A mix between [`relative`] and [`absolute`] numbering
43    ///
44    /// Will show the line number of the main cursor's line, while
45    /// showing the distance to it on every other line.
46    ///
47    /// [`relative`]: Self::relative
48    /// [`absolute`]: Self::absolute
49    pub fn rel_abs(&mut self) -> &mut Self {
50        self.opts = self.opts.rel_abs();
51        self
52    }
53
54    /// Aligns _all_ numbers left
55    ///
56    /// If you want the main line's number to be aligned differently,
57    /// call one of the [`align_main_*`] functions _after_ this one.
58    ///
59    /// [`align_main_*`]: Self::align_main_right
60    pub fn align_left(&mut self) -> &mut Self {
61        self.opts = self.opts.align_left();
62        self
63    }
64
65    /// Aligns _all_ numbers to the center
66    ///
67    /// If you want the main line's number to be aligned differently,
68    /// call one of the [`align_main_*`] functions _after_ this one.
69    ///
70    /// [`align_main_*`]: Self::align_main_right
71    pub fn align_center(&mut self) -> &mut Self {
72        self.opts = self.opts.align_center();
73        self
74    }
75
76    /// Aligns _all_ numbers right
77    ///
78    /// If you want the main line's number to be aligned differently,
79    /// call one of the [`align_main_*`] functions _after_ this one.
80    ///
81    /// [`align_main_*`]: Self::align_main_left
82    pub fn align_right(&mut self) -> &mut Self {
83        self.opts = self.opts.align_right();
84        self
85    }
86
87    /// Aligns onle the main line's number to the left
88    pub fn align_main_left(&mut self) -> &mut Self {
89        self.opts = self.opts.align_main_left();
90        self
91    }
92
93    /// Aligns onle the main line's number to the center
94    pub fn align_main_center(&mut self) -> &mut Self {
95        self.opts = self.opts.align_main_center();
96        self
97    }
98
99    /// Aligns onle the main line's number to the right
100    pub fn align_main_right(&mut self) -> &mut Self {
101        self.opts = self.opts.align_main_right();
102        self
103    }
104
105    /// Shows wrapping lines, is `false` by default
106    pub fn show_wraps(&mut self) -> &mut Self {
107        self.opts = self.opts.show_wraps();
108        self
109    }
110
111    /// Hides wrapping lines, is `true` by default
112    pub fn hide_wraps(&mut self) -> &mut Self {
113        self.opts = self.opts.hide_wraps();
114        self
115    }
116
117    /// The minimum width that would be needed to show the last line.
118    fn calculate_width(&self, pa: &Pass) -> f32 {
119        let len = self.handle.read(pa).text().len().line();
120        len.ilog10() as f32
121    }
122
123    fn form_text(&self, pa: &Pass) -> Text {
124        let (main_line, printed_lines) = {
125            let file = self.handle.read(pa);
126            let main_line = if file.selections().is_empty() {
127                usize::MAX
128            } else {
129                file.selections().get_main().unwrap().line()
130            };
131
132            (main_line, file.printed_lines().to_vec())
133        };
134
135        let mut builder = Text::builder();
136        align(&mut builder, self.opts.align);
137
138        for (index, (line, is_wrapped)) in printed_lines.iter().enumerate() {
139            if *line == main_line {
140                align(&mut builder, self.opts.main_align);
141            }
142
143            match (*line == main_line, is_wrapped) {
144                (false, false) => {}
145                (true, false) => {
146                    let id = form::id_of!("linenum.main");
147                    builder.push(id)
148                }
149                (false, true) => builder.push(form::id_of!("linenum.wrapped")),
150                (true, true) => builder.push(form::id_of!("linenum.wrapped.main")),
151            }
152
153            let is_wrapped = *is_wrapped && index > 0;
154            push_text(&mut builder, *line, main_line, is_wrapped, &self.opts);
155
156            if *line == main_line {
157                align(&mut builder, self.opts.align);
158            }
159        }
160
161        builder.build()
162    }
163}
164
165impl<U: Ui> Widget<U> for LineNumbers<U> {
166    type Cfg = LineNumbersCfg<U>;
167
168    fn update(pa: &mut Pass, handle: &Handle<Self, U>) {
169        let width = handle.read(pa).calculate_width(pa);
170        handle
171            .area(pa)
172            .constrain_hor([Constraint::Len(width + 1.0)])
173            .unwrap();
174
175        handle.write(pa).text = handle.read(pa).form_text(pa);
176    }
177
178    fn needs_update(&self, _: &Pass) -> bool {
179        self.handle.has_changed()
180    }
181
182    fn cfg() -> Self::Cfg {
183        Self::Cfg {
184            numbering: Numbering::Abs,
185            align: Alignment::Left,
186            main_align: Alignment::Right,
187            show_wraps: false,
188            specs: PushSpecs::left(),
189            _ghost: PhantomData,
190        }
191    }
192
193    fn text(&self) -> &Text {
194        &self.text
195    }
196
197    fn text_mut(&mut self) -> &mut Text {
198        &mut self.text
199    }
200
201    fn once() -> Result<(), Text> {
202        form::set_weak("linenum.main", Form::yellow());
203        form::set_weak("linenum.wrapped", Form::cyan().italic());
204        form::set_weak("linenum.wrapped.main", "linenum.wrapped");
205        Ok(())
206    }
207}
208
209/// [`WidgetCfg`] for the [`LineNumbers`] widget
210///
211/// Contains a [`LineNumbersOptions`], which, unlike
212/// [`LineNumbersCfg`], is modified by the `&mut` version of the
213/// builder pattern.
214#[derive(Debug, Clone)]
215pub struct LineNumbersCfg<_U> {
216    numbering: Numbering = Numbering::Abs,
217    align: Alignment = Alignment::Left,
218    main_align: Alignment = Alignment::Right,
219    show_wraps: bool = false,
220    specs: PushSpecs = PushSpecs::left(),
221    _ghost: PhantomData<_U>
222}
223
224impl<_U> LineNumbersCfg<_U> {
225    /// Absolute numbering, first line is 1, second is 2, etc
226    pub fn absolute(self) -> Self {
227        Self { numbering: Numbering::Abs, ..self }
228    }
229
230    /// Relative numbering, cursor line is 0, surrounding is 1, etc
231    pub fn relative(self) -> Self {
232        Self { numbering: Numbering::Rel, ..self }
233    }
234
235    /// A mix between [`relative`] and [`absolute`] numbering
236    ///
237    /// Will show the line number of the main cursor's line, while
238    /// showing the distance to it on every other line.
239    ///
240    /// [`relative`]: Self::relative
241    /// [`absolute`]: Self::absolute
242    pub fn rel_abs(self) -> Self {
243        Self { numbering: Numbering::RelAbs, ..self }
244    }
245
246    /// Aligns _all_ numbers left
247    ///
248    /// If you want the main line's number to be aligned differently,
249    /// call one of the [`align_main_*`] functions _after_ this one.
250    ///
251    /// [`align_main_*`]: Self::align_main_right
252    pub fn align_left(self) -> Self {
253        Self {
254            align: Alignment::Left,
255            main_align: Alignment::Left,
256            ..self
257        }
258    }
259
260    /// Aligns _all_ numbers to the center
261    ///
262    /// If you want the main line's number to be aligned differently,
263    /// call one of the [`align_main_*`] functions _after_ this one.
264    ///
265    /// [`align_main_*`]: Self::align_main_right
266    pub fn align_center(self) -> Self {
267        Self {
268            align: Alignment::Center,
269            main_align: Alignment::Center,
270            ..self
271        }
272    }
273
274    /// Aligns _all_ numbers right
275    ///
276    /// If you want the main line's number to be aligned differently,
277    /// call one of the [`align_main_*`] functions _after_ this one.
278    ///
279    /// [`align_main_*`]: Self::align_main_left
280    pub fn align_right(self) -> Self {
281        Self {
282            align: Alignment::Right,
283            main_align: Alignment::Right,
284            ..self
285        }
286    }
287
288    /// Aligns onle the main line's number to the left
289    pub fn align_main_left(self) -> Self {
290        Self { main_align: Alignment::Left, ..self }
291    }
292
293    /// Aligns onle the main line's number to the center
294    pub fn align_main_center(self) -> Self {
295        Self { main_align: Alignment::Center, ..self }
296    }
297
298    /// Aligns onle the main line's number to the right
299    pub fn align_main_right(self) -> Self {
300        Self { main_align: Alignment::Right, ..self }
301    }
302
303    /// Shows wrapping lines, is `false` by default
304    pub fn show_wraps(self) -> Self {
305        Self { show_wraps: true, ..self }
306    }
307
308    /// Hides wrapping lines, is `true` by default
309    pub fn hide_wraps(self) -> Self {
310        Self { show_wraps: false, ..self }
311    }
312
313    /// Place the [`LineNumbers`] on the right
314    ///
315    /// Do note that this has no effect if done at runtime.
316    pub fn on_the_right(self) -> Self {
317        Self { specs: self.specs.to_right(), ..self }
318    }
319}
320
321impl<U: Ui> WidgetCfg<U> for LineNumbersCfg<U> {
322    type Widget = LineNumbers<U>;
323
324    fn build(self, pa: &mut Pass, info: BuildInfo<U>) -> (Self::Widget, PushSpecs) {
325        let Some(handle) = info.file() else {
326            panic!("For now, you can't push LineNumbers to something that is not a File");
327        };
328        let specs = self.specs;
329
330        let mut widget = LineNumbers {
331            handle,
332            text: Text::default(),
333            opts: self,
334        };
335        widget.text = widget.form_text(pa);
336
337        (widget, specs)
338    }
339}
340
341impl<U: Ui> Copy for LineNumbersCfg<U> {}
342
343/// Writes the text of the line number to a given [`String`].
344fn push_text<_U>(
345    b: &mut Builder,
346    line: usize,
347    main: usize,
348    is_wrapped: bool,
349    cfg: &LineNumbersCfg<_U>,
350) {
351    if (!is_wrapped || cfg.show_wraps) && main != usize::MAX {
352        let num = match cfg.numbering {
353            Numbering::Abs => line + 1,
354            Numbering::Rel => line.abs_diff(main),
355            Numbering::RelAbs => {
356                if line != main {
357                    line.abs_diff(main)
358                } else {
359                    line + 1
360                }
361            }
362        };
363        b.push(num);
364    }
365
366    b.push("\n");
367    b.push(form::DEFAULT_ID);
368}
369
370fn align(b: &mut Builder, alignment: Alignment) {
371    match alignment {
372        Alignment::Left => b.push(AlignLeft),
373        Alignment::Center => b.push(AlignCenter),
374        Alignment::Right => b.push(AlignRight),
375    }
376}
377
378/// How to show the line numbers on screen.
379#[derive(Default, Debug, Clone, Copy)]
380enum Numbering {
381    #[default]
382    /// Line numbers relative to the beginning of the file.
383    Abs,
384    /// Line numbers relative to the main selection's line, including
385    /// that line.
386    Rel,
387    /// Relative line numbers on every line, except the main
388    /// selection's.
389    RelAbs,
390}