rat_text/
line_number.rs

1//!
2//! Line numbers widget.
3//!
4
5use crate::_private::NonExhaustive;
6use crate::upos_type;
7use format_num_pattern::NumberFormat;
8use rat_event::util::MouseFlags;
9use ratatui::buffer::Buffer;
10use ratatui::layout::Rect;
11use ratatui::prelude::BlockExt;
12use ratatui::style::Style;
13use ratatui::text::Line;
14use ratatui::widgets::StatefulWidget;
15use ratatui::widgets::{Block, Widget};
16
17/// Renders line-numbers.
18///
19/// # Stateful
20/// This widget implements [`StatefulWidget`], you can use it with
21/// [`LineNumberState`] to handle common actions.
22#[derive(Debug, Default, Clone)]
23pub struct LineNumbers<'a> {
24    start: upos_type,
25    end: Option<upos_type>,
26    cursor: upos_type,
27    relative: bool,
28    flags: Vec<Line<'a>>,
29
30    flag_width: Option<u16>,
31    margin: (u16, u16),
32
33    format: Option<NumberFormat>,
34    style: Style,
35    cursor_style: Option<Style>,
36
37    block: Option<Block<'a>>,
38}
39
40/// Styles as a package.
41#[derive(Debug, Clone)]
42pub struct LineNumberStyle {
43    pub flag_width: Option<u16>,
44    pub margin: Option<(u16, u16)>,
45    pub format: Option<NumberFormat>,
46    pub style: Style,
47    pub cursor: Option<Style>,
48    pub block: Option<Block<'static>>,
49
50    pub non_exhaustive: NonExhaustive,
51}
52
53/// State
54#[derive(Debug, Clone)]
55pub struct LineNumberState {
56    pub area: Rect,
57    pub inner: Rect,
58
59    pub start: upos_type,
60
61    /// Helper for mouse.
62    pub mouse: MouseFlags,
63
64    pub non_exhaustive: NonExhaustive,
65}
66
67impl<'a> LineNumbers<'a> {
68    pub fn new() -> Self {
69        Self::default()
70    }
71
72    /// Start position.
73    pub fn start(mut self, start: upos_type) -> Self {
74        self.start = start;
75        self
76    }
77
78    /// End position.
79    pub fn end(mut self, end: upos_type) -> Self {
80        self.end = Some(end);
81        self
82    }
83
84    /// Current line for highlighting.
85    pub fn cursor(mut self, cursor: upos_type) -> Self {
86        self.cursor = cursor;
87        self
88    }
89
90    /// Numbering relative to cursor
91    pub fn relative(mut self, relative: bool) -> Self {
92        self.relative = relative;
93        self
94    }
95
96    /// Extra info.
97    pub fn flags(mut self, flags: Vec<Line<'a>>) -> Self {
98        self.flags = flags;
99        self
100    }
101
102    /// Required width for the flags.
103    pub fn flag_width(mut self, width: u16) -> Self {
104        self.flag_width = Some(width);
105        self
106    }
107
108    /// Extra margin.
109    pub fn margin(mut self, margin: (u16, u16)) -> Self {
110        self.margin = margin;
111        self
112    }
113
114    /// Line number format.
115    pub fn format(mut self, format: NumberFormat) -> Self {
116        self.format = Some(format);
117        self
118    }
119
120    /// Complete set of styles.
121    pub fn styles(mut self, styles: LineNumberStyle) -> Self {
122        self.style = styles.style;
123        if let Some(flag_width) = styles.flag_width {
124            self.flag_width = Some(flag_width);
125        }
126        if let Some(margin) = styles.margin {
127            self.margin = margin;
128        }
129        if let Some(format) = styles.format {
130            self.format = Some(format);
131        }
132        if let Some(cursor_style) = styles.cursor {
133            self.cursor_style = Some(cursor_style);
134        }
135        if let Some(block) = styles.block {
136            self.block = Some(block);
137        }
138        self.block = self.block.map(|v| v.style(self.style));
139        self
140    }
141
142    /// Base style.
143    pub fn style(mut self, style: Style) -> Self {
144        self.style = style;
145        self.block = self.block.map(|v| v.style(style));
146        self
147    }
148
149    /// Style for current line.
150    pub fn cursor_style(mut self, style: Style) -> Self {
151        self.cursor_style = Some(style);
152        self
153    }
154
155    /// Block.
156    pub fn block(mut self, block: Block<'a>) -> Self {
157        self.block = Some(block.style(self.style));
158        self
159    }
160
161    /// Calculates the necessary width for the configuration.
162    pub fn width(&self) -> u16 {
163        let nr_width = if let Some(end) = self.end {
164            end.ilog10() as u16 + 1
165        } else {
166            (self.start + 100).ilog10() as u16 + 1
167        };
168        let flag_width = if let Some(flag_width) = self.flag_width {
169            flag_width
170        } else {
171            self.flags
172                .iter()
173                .map(|v| v.width() as u16)
174                .max()
175                .unwrap_or_default()
176        };
177        let block_width = {
178            let area = self.block.inner_if_some(Rect::new(0, 0, 2, 2));
179            2 - area.width
180        };
181
182        nr_width + flag_width + self.margin.0 + self.margin.1 + block_width + 1
183    }
184}
185
186impl Default for LineNumberStyle {
187    fn default() -> Self {
188        Self {
189            flag_width: None,
190            margin: None,
191            format: None,
192            style: Default::default(),
193            cursor: None,
194            block: None,
195            non_exhaustive: NonExhaustive,
196        }
197    }
198}
199
200impl StatefulWidget for LineNumbers<'_> {
201    type State = LineNumberState;
202
203    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
204        state.area = area;
205        state.inner = self.block.inner_if_some(area);
206        state.start = self.start;
207        let end = self.end.unwrap_or(upos_type::MAX);
208
209        let nr_width = if let Some(end) = self.end {
210            end.ilog10() as u16 + 1
211        } else {
212            (self.start + 100).ilog10() as u16 + 1
213        };
214
215        let flag_width = if let Some(flag_width) = self.flag_width {
216            flag_width
217        } else {
218            self.flags
219                .iter()
220                .map(|v| v.width() as u16)
221                .max()
222                .unwrap_or_default()
223        };
224
225        let format = if let Some(format) = self.format {
226            format
227        } else {
228            let mut f = "#".repeat(nr_width.saturating_sub(1) as usize);
229            f.push('0');
230            NumberFormat::new(f).expect("valid")
231        };
232
233        let cursor_style = if let Some(cursor_style) = self.cursor_style {
234            cursor_style
235        } else {
236            self.style
237        };
238
239        if let Some(block) = self.block {
240            block.render(area, buf);
241        } else {
242            buf.set_style(area, self.style);
243        }
244
245        let mut tmp = String::new();
246        for y in state.inner.top()..state.inner.bottom() {
247            let (nr, is_cursor) = if self.relative {
248                let pos = self.start + (y - state.inner.y) as upos_type;
249                (pos.abs_diff(self.cursor), pos == self.cursor)
250            } else {
251                let pos = self.start + (y - state.inner.y) as upos_type;
252                (pos, pos == self.cursor)
253            };
254
255            tmp.clear();
256            if nr < end {
257                _ = format.fmt_to(nr, &mut tmp);
258            }
259
260            let style = if is_cursor { cursor_style } else { self.style };
261
262            let nr_area = Rect::new(
263                state.inner.x + self.margin.0, //
264                y,
265                nr_width,
266                1,
267            )
268            .intersection(area);
269            buf.set_stringn(nr_area.x, nr_area.y, &tmp, nr_area.width as usize, style);
270
271            if let Some(flags) = self.flags.get((y - state.inner.y) as usize) {
272                flags.render(
273                    Rect::new(
274                        state.inner.x + self.margin.0 + nr_width + 1,
275                        y,
276                        flag_width,
277                        1,
278                    ),
279                    buf,
280                );
281            }
282        }
283    }
284}
285
286impl Default for LineNumberState {
287    fn default() -> Self {
288        Self {
289            area: Default::default(),
290            inner: Default::default(),
291            start: 0,
292            mouse: Default::default(),
293            non_exhaustive: NonExhaustive,
294        }
295    }
296}
297
298impl LineNumberState {
299    pub fn new() -> Self {
300        Self::default()
301    }
302}