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    buffer::Buffer,
17    context::Handle,
18    data::Pass,
19    form,
20    mode::{MouseButton, MouseEvent, MouseEventKind},
21    text::{AlignCenter, AlignLeft, AlignRight, Builder, Text},
22    ui::{PushSpecs, Side, Widget},
23};
24
25/// Shows a column of line numbers beside the [`Buffer`]
26///
27/// There are various fields that you can use to configure how the
28/// `LineNumbers` will be displayed. They control things like the
29/// line numbers and the relativeness of the number displayed.
30///
31/// This is a default struct of Duat, that is, it is automatically
32/// placed around every `Buffer`, but you can disable that behavior
33/// by [removing] the `"BufferWidgets"` hook.
34///
35/// [`Buffer`]: duat_core::buffer::Buffer
36/// [removing]: duat_core::hook::remove
37pub struct LineNumbers {
38    buffer: Handle,
39    text: Text,
40    /// Wether to show relative numbering
41    ///
42    /// The default is `false`
43    pub relative: bool,
44    /// Where to align the numbers
45    ///
46    /// The default is [`Alignment::Left`]
47    pub align: Alignment,
48    /// Where to align the main line number
49    ///
50    /// The default is [`Alignment::Right`]
51    pub main_align: Alignment,
52    /// Wether to show wrapped line's numbers
53    ///
54    /// The default is `false`
55    pub show_wraps: bool,
56}
57
58impl LineNumbers {
59    /// Returns a [`LineNumbersOpts`], used to create a new
60    /// `LineNumbers`
61    pub fn builder() -> LineNumbersOpts {
62        LineNumbersOpts::default()
63    }
64
65    /// The minimum width that would be needed to show the last line.
66    fn calculate_width(&self, pa: &Pass) -> f32 {
67        let len = self.buffer.read(pa).text().len().line();
68        len.ilog10() as f32
69    }
70
71    fn form_text(&self, pa: &Pass) -> Text {
72        let (main_line, printed_lines) = {
73            let buffer = self.buffer.read(pa);
74            let main_line = if buffer.selections().is_empty() {
75                usize::MAX
76            } else {
77                buffer.selections().get_main().unwrap().line()
78            };
79
80            (main_line, buffer.printed_lines().to_vec())
81        };
82
83        let mut builder = Text::builder();
84        align(&mut builder, self.align);
85
86        for (index, (line, is_wrapped)) in printed_lines.iter().enumerate() {
87            if *line == main_line {
88                align(&mut builder, self.main_align);
89            }
90
91            match (*line == main_line, is_wrapped) {
92                (false, false) => {}
93                (true, false) => builder.push(form::id_of!("linenum.main")),
94                (false, true) => builder.push(form::id_of!("linenum.wrapped")),
95                (true, true) => builder.push(form::id_of!("linenum.wrapped.main")),
96            }
97
98            let is_wrapped = *is_wrapped && index > 0;
99            push_text(&mut builder, *line, main_line, is_wrapped, self);
100
101            if *line == main_line {
102                align(&mut builder, self.align);
103            }
104        }
105
106        builder.build()
107    }
108}
109
110impl Widget for LineNumbers {
111    fn update(pa: &mut Pass, handle: &Handle<Self>) {
112        let width = handle.read(pa).calculate_width(pa);
113        handle.area().set_width(pa, width + 1.0).unwrap();
114
115        handle.write(pa).text = handle.read(pa).form_text(pa);
116    }
117
118    fn needs_update(&self, pa: &Pass) -> bool {
119        self.buffer.has_changed(pa)
120    }
121
122    fn text(&self) -> &Text {
123        &self.text
124    }
125
126    fn text_mut(&mut self) -> &mut Text {
127        &mut self.text
128    }
129
130    fn on_mouse_event(pa: &mut Pass, handle: &Handle<Self>, event: MouseEvent) {
131        let line = |buffer: &Buffer| {
132            let lines = buffer.printed_lines();
133            event
134                .points
135                .and_then(|tpp| lines.get(tpp.points().real.line()))
136                .map(|(line, _)| *line)
137                .unwrap_or(buffer.text().len().line())
138        };
139        match event.kind {
140            MouseEventKind::Down(MouseButton::Left) => {
141                let handle = handle.read(pa).buffer.clone();
142                let line = line(handle.read(pa));
143
144                handle.selections_mut(pa).remove_extras();
145                handle.edit_main(pa, |mut c| {
146                    c.unset_anchor();
147                    c.move_to_coords(line, 0)
148                })
149            }
150            MouseEventKind::Drag(MouseButton::Left) => {
151                let handle = handle.read(pa).buffer.clone();
152                let line = line(handle.read(pa));
153
154                handle.selections_mut(pa).remove_extras();
155                handle.edit_main(pa, |mut c| {
156                    c.set_anchor_if_needed();
157                    c.move_to_coords(line, 0)
158                })
159            }
160            MouseEventKind::ScrollDown => {
161                let handle = handle.read(pa).buffer.clone();
162                let opts = handle.opts(pa);
163                let (buffer, area) = handle.write_with_area(pa);
164
165                area.scroll_ver(buffer.text(), 3, opts);
166            }
167            MouseEventKind::ScrollUp => {
168                let handle = handle.read(pa).buffer.clone();
169                let opts = handle.opts(pa);
170                let (buffer, area) = handle.write_with_area(pa);
171
172                area.scroll_ver(buffer.text(), -3, opts);
173            }
174            _ => {}
175        }
176    }
177}
178
179/// Options for cosntructing a [`LineNumbers`] [`Widget`]
180///
181/// For most options, you can just set them in the `Widget`
182/// directly (through a [hook] or something). Right now, the
183/// only option exclusive to this struct is the [`on_the_right`]
184/// option, which places the `LineNumbers` on the right, as
185/// opposed to on the left.
186///
187/// [`on_the_right`]: Self::on_the_right
188/// [hook]: duat_core::hook
189#[derive(Clone, Copy, Debug)]
190pub struct LineNumbersOpts {
191    /// Wether to show relative numbering
192    ///
193    /// The default is `false`
194    pub relative: bool,
195    /// Where to align the numbers
196    ///
197    /// The default is [`Alignment::Left`]
198    pub align: Alignment,
199    /// Where to align the main line number
200    ///
201    /// The default is [`Alignment::Right`]
202    pub main_align: Alignment,
203    /// Wether to show wrapped line's numbers
204    ///
205    /// The default is `false`
206    pub show_wraps: bool,
207    /// Place this [`Widget`] on the right, as opposed to on the left
208    ///
209    /// The default is `false`
210    pub on_the_right: bool,
211}
212
213impl LineNumbersOpts {
214    /// Retunrs a new `LineNumbersOpts`
215    pub const fn new() -> Self {
216        Self {
217            relative: false,
218            align: Alignment::Left,
219            main_align: Alignment::Right,
220            show_wraps: false,
221            on_the_right: false,
222        }
223    }
224
225    /// Push the [`LineNumbers`] to a [`Handle`]
226    pub fn push_on(self, pa: &mut Pass, handle: &Handle) -> Handle<LineNumbers> {
227        let mut line_numbers = LineNumbers {
228            buffer: handle.clone(),
229            text: Text::default(),
230            relative: self.relative,
231            align: self.align,
232            main_align: self.main_align,
233            show_wraps: self.show_wraps,
234        };
235        line_numbers.text = line_numbers.form_text(pa);
236
237        let specs = PushSpecs {
238            side: if self.on_the_right {
239                Side::Right
240            } else {
241                Side::Left
242            },
243            ..Default::default()
244        };
245
246        handle.push_outer_widget(pa, line_numbers, specs)
247    }
248}
249
250impl Default for LineNumbersOpts {
251    fn default() -> Self {
252        Self::new()
253    }
254}
255
256/// Writes the text of the line number to a given [`String`].
257fn push_text(b: &mut Builder, line: usize, main: usize, is_wrapped: bool, opts: &LineNumbers) {
258    if (!is_wrapped || opts.show_wraps) && main != usize::MAX {
259        let num = if opts.relative {
260            if line != main {
261                line.abs_diff(main)
262            } else {
263                line + 1
264            }
265        } else {
266            line + 1
267        };
268        b.push(num);
269    }
270
271    b.push("\n");
272    b.push(form::DEFAULT_ID);
273}
274
275fn align(b: &mut Builder, alignment: Alignment) {
276    match alignment {
277        Alignment::Left => b.push(AlignLeft),
278        Alignment::Center => b.push(AlignCenter),
279        Alignment::Right => b.push(AlignRight),
280    }
281}