duat_core/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//! cursor's line number.
11//!
12//! [`File`]: super::File
13use std::{fmt::Alignment, marker::PhantomData};
14
15use crate::{
16    context::{self, FixedFile},
17    form::{self, Form},
18    text::{AlignCenter, AlignLeft, AlignRight, Builder, Text, text},
19    ui::{Area, Constraint, PushSpecs, Ui},
20    widgets::{Widget, WidgetCfg},
21};
22
23pub struct LineNumbers<U: Ui> {
24    ff: FixedFile<U>,
25    text: Text,
26    cfg: LineNumbersOptions<U>,
27}
28
29impl<U: Ui> LineNumbers<U> {
30    /// The minimum width that would be needed to show the last line.
31    fn calculate_width(&mut self) -> f32 {
32        let len = self.ff.read().0.text().len().line();
33        len.ilog10() as f32
34    }
35
36    fn update_text(&mut self) {
37        let (main_line, printed_lines) = {
38            let (file, _) = self.ff.read();
39            let main_line = match file.cursors().is_empty() {
40                true => usize::MAX,
41                false => file.cursors().get_main().unwrap().line(),
42            };
43            (main_line, file.printed_lines().to_vec())
44        };
45
46        let mut builder = Text::builder();
47        align(&mut builder, self.cfg.align);
48
49        for (index, (line, is_wrapped)) in printed_lines.iter().enumerate() {
50            if main_line == *line {
51                align(&mut builder, self.cfg.main_align);
52            }
53
54            match (main_line == *line, is_wrapped) {
55                (false, false) => text!(builder, [LineNum]),
56                (true, false) => text!(builder, [MainLineNum]),
57                (false, true) => text!(builder, [WrappedLineNum]),
58                (true, true) => text!(builder, [WrappedMainLineNum]),
59            }
60
61            let is_wrapped = *is_wrapped && index > 0;
62            push_text(&mut builder, *line, main_line, is_wrapped, &self.cfg);
63
64            if main_line == *line {
65                align(&mut builder, self.cfg.align);
66            }
67        }
68
69        self.text = builder.finish();
70    }
71
72	/// The options for these [`LineNumbers`]
73    pub fn options(&self) -> &LineNumbersOptions<U> {
74        &self.cfg
75    }
76
77	/// The mutable options for these [`LineNumbers`]
78    pub fn options_mut(&mut self) -> &mut LineNumbersOptions<U> {
79        &mut self.cfg
80    }
81}
82
83impl<U: Ui> Widget<U> for LineNumbers<U> {
84    type Cfg = LineNumbersOptions<U>;
85
86    fn cfg() -> Self::Cfg {
87        Self::Cfg {
88            num_rel: LineNum::Abs,
89            align: Alignment::Left,
90            main_align: Alignment::Right,
91            show_wraps: false,
92            specs: PushSpecs::left(),
93            _ghost: PhantomData,
94        }
95    }
96
97    fn update(&mut self, area: &U::Area) {
98        let width = self.calculate_width();
99        area.constrain_hor([Constraint::Len(width + 1.0)]).unwrap();
100
101        self.update_text();
102    }
103
104    fn text(&self) -> &Text {
105        &self.text
106    }
107
108    fn text_mut(&mut self) -> &mut Text {
109        &mut self.text
110    }
111
112    fn once() -> Result<(), Text> {
113        form::set_weak("LineNum", Form::grey());
114        form::set_weak("MainLineNum", Form::yellow());
115        form::set_weak("WrappedLineNum", Form::cyan().italic());
116        form::set_weak("WrappedMainLineNum", "WrappedLineNum");
117        Ok(())
118    }
119}
120
121/// How to show the line numbers on screen.
122#[derive(Default, Debug, Clone, Copy)]
123pub enum LineNum {
124    #[default]
125    /// Line numbers relative to the beginning of the file.
126    Abs,
127    /// Line numbers relative to the main cursor's line, including
128    /// that line.
129    Rel,
130    /// Relative line numbers on every line, except the main cursor's.
131    RelAbs,
132}
133
134/// Configuration options for the [`LineNumbers<U>`] widget.
135#[derive(Debug, Clone, Copy)]
136#[doc(hidden)]
137pub struct LineNumbersOptions<U> {
138    pub num_rel: LineNum,
139    pub align: Alignment,
140    pub main_align: Alignment,
141    pub show_wraps: bool,
142    specs: PushSpecs,
143    _ghost: PhantomData<U>,
144}
145
146impl<U> LineNumbersOptions<U> {
147    pub fn absolute(self) -> Self {
148        Self { num_rel: LineNum::Abs, ..self }
149    }
150
151    pub fn relative(self) -> Self {
152        Self { num_rel: LineNum::Rel, ..self }
153    }
154
155    pub fn rel_abs(self) -> Self {
156        Self { num_rel: LineNum::RelAbs, ..self }
157    }
158
159    pub fn align_left(self) -> Self {
160        Self {
161            main_align: Alignment::Left,
162            align: Alignment::Left,
163            ..self
164        }
165    }
166
167    pub fn align_center(self) -> Self {
168        Self {
169            main_align: Alignment::Center,
170            align: Alignment::Center,
171            ..self
172        }
173    }
174
175    pub fn align_right(self) -> Self {
176        Self {
177            main_align: Alignment::Right,
178            align: Alignment::Right,
179            ..self
180        }
181    }
182
183    pub fn align_main_left(self) -> Self {
184        Self { main_align: Alignment::Left, ..self }
185    }
186
187    pub fn align_main_center(self) -> Self {
188        Self { main_align: Alignment::Center, ..self }
189    }
190
191    pub fn align_main_right(self) -> Self {
192        Self { main_align: Alignment::Right, ..self }
193    }
194
195    pub fn show_wraps(self) -> Self {
196        Self { show_wraps: true, ..self }
197    }
198
199    pub fn hide_wraps(self) -> Self {
200        Self { show_wraps: false, ..self }
201    }
202
203    pub fn on_the_right(self) -> Self {
204        Self { specs: self.specs.to_right(), ..self }
205    }
206}
207
208impl<U: Ui> WidgetCfg<U> for LineNumbersOptions<U> {
209    type Widget = LineNumbers<U>;
210
211    fn build(self, _: bool) -> (Self::Widget, impl Fn() -> bool, PushSpecs) {
212        let ff = context::fixed_file().unwrap();
213        let specs = self.specs;
214
215        let checker = ff.checker();
216        let mut widget = LineNumbers { ff, text: Text::default(), cfg: self };
217        widget.update_text();
218
219        (widget, checker, specs)
220    }
221}
222
223/// Writes the text of the line number to a given [`String`].
224fn push_text<U>(
225    b: &mut Builder,
226    line: usize,
227    main: usize,
228    is_wrapped: bool,
229    cfg: &LineNumbersOptions<U>,
230) {
231    if is_wrapped && !cfg.show_wraps {
232        text!(*b, "\n");
233    } else if main != usize::MAX {
234        let num = match cfg.num_rel {
235            LineNum::Abs => line + 1,
236            LineNum::Rel => line.abs_diff(main),
237            LineNum::RelAbs => {
238                if line != main {
239                    line.abs_diff(main)
240                } else {
241                    line + 1
242                }
243            }
244        };
245
246        text!(*b, num "\n");
247    } else {
248        text!(*b, { line + 1 } "\n");
249    }
250}
251
252fn align(b: &mut Builder, alignment: Alignment) {
253    match alignment {
254        Alignment::Left => text!(*b, AlignLeft),
255        Alignment::Center => text!(*b, AlignCenter),
256        Alignment::Right => text!(*b, AlignRight),
257    }
258}