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