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