1use crate::_private::NonExhaustive;
6use crate::text_area::TextAreaState;
7use crate::{TextPosition, upos_type};
8use format_num_pattern::NumberFormat;
9use rat_event::util::MouseFlags;
10use ratatui::buffer::Buffer;
11use ratatui::layout::Rect;
12use ratatui::prelude::BlockExt;
13use ratatui::style::Style;
14use ratatui::text::Line;
15use ratatui::widgets::StatefulWidget;
16use ratatui::widgets::{Block, Widget};
17
18#[derive(Debug, Default, Clone)]
24pub struct LineNumbers<'a> {
25 start: Option<upos_type>,
26 end: Option<upos_type>,
27 cursor: Option<upos_type>,
28 text_area: Option<&'a TextAreaState>,
29
30 relative: bool,
31 flags: Vec<Line<'a>>,
32 flag_width: Option<u16>,
33 margin: (u16, u16),
34
35 format: Option<NumberFormat>,
36 style: Style,
37 cursor_style: Option<Style>,
38
39 block: Option<Block<'a>>,
40}
41
42#[derive(Debug, Clone)]
44pub struct LineNumberStyle {
45 pub flag_width: Option<u16>,
46 pub margin: Option<(u16, u16)>,
47 pub format: Option<NumberFormat>,
48 pub style: Style,
49 pub cursor: Option<Style>,
50 pub block: Option<Block<'static>>,
51
52 pub non_exhaustive: NonExhaustive,
53}
54
55#[derive(Debug, Clone)]
57pub struct LineNumberState {
58 pub area: Rect,
59 pub inner: Rect,
60
61 pub start: upos_type,
63
64 pub mouse: MouseFlags,
66
67 pub non_exhaustive: NonExhaustive,
68}
69
70impl<'a> LineNumbers<'a> {
71 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn with_textarea(mut self, text_area: &'a TextAreaState) -> Self {
81 self.text_area = Some(text_area);
82 self
83 }
84
85 pub fn start(mut self, start: upos_type) -> Self {
87 self.start = Some(start);
88 self
89 }
90
91 pub fn end(mut self, end: upos_type) -> Self {
93 self.end = Some(end);
94 self
95 }
96
97 pub fn cursor(mut self, cursor: upos_type) -> Self {
99 self.cursor = Some(cursor);
100 self
101 }
102
103 pub fn relative(mut self, relative: bool) -> Self {
105 self.relative = relative;
106 self
107 }
108
109 pub fn flags(mut self, flags: Vec<Line<'a>>) -> Self {
111 self.flags = flags;
112 self
113 }
114
115 pub fn flag_width(mut self, width: u16) -> Self {
117 self.flag_width = Some(width);
118 self
119 }
120
121 pub fn margin(mut self, margin: (u16, u16)) -> Self {
123 self.margin = margin;
124 self
125 }
126
127 pub fn format(mut self, format: NumberFormat) -> Self {
129 self.format = Some(format);
130 self
131 }
132
133 pub fn styles(mut self, styles: LineNumberStyle) -> Self {
135 self.style = styles.style;
136 if let Some(flag_width) = styles.flag_width {
137 self.flag_width = Some(flag_width);
138 }
139 if let Some(margin) = styles.margin {
140 self.margin = margin;
141 }
142 if let Some(format) = styles.format {
143 self.format = Some(format);
144 }
145 if let Some(cursor_style) = styles.cursor {
146 self.cursor_style = Some(cursor_style);
147 }
148 if let Some(block) = styles.block {
149 self.block = Some(block);
150 }
151 self.block = self.block.map(|v| v.style(self.style));
152 self
153 }
154
155 pub fn style(mut self, style: Style) -> Self {
157 self.style = style;
158 self.block = self.block.map(|v| v.style(style));
159 self
160 }
161
162 pub fn cursor_style(mut self, style: Style) -> Self {
164 self.cursor_style = Some(style);
165 self
166 }
167
168 pub fn block(mut self, block: Block<'a>) -> Self {
170 self.block = Some(block.style(self.style));
171 self
172 }
173
174 #[deprecated(since = "1.1.0", note = "use width_for()")]
176 pub fn width(&self) -> u16 {
177 let nr_width = if let Some(text_area) = self.text_area {
178 (text_area.vscroll.offset() + 50).ilog10() as u16 + 1
179 } else if let Some(end) = self.end {
180 end.ilog10() as u16 + 1
181 } else if let Some(start) = self.start {
182 (start + 50).ilog10() as u16 + 1
183 } else {
184 3
185 };
186
187 let flag_width = if let Some(flag_width) = self.flag_width {
188 flag_width
189 } else {
190 self.flags
191 .iter()
192 .map(|v| v.width() as u16)
193 .max()
194 .unwrap_or_default()
195 };
196
197 let block_width = {
198 let area = self.block.inner_if_some(Rect::new(0, 0, 2, 2));
199 2 - area.width
200 };
201
202 nr_width + flag_width + self.margin.0 + self.margin.1 + block_width + 1
203 }
204
205 pub fn width_for(start_nr: usize, flag_width: u16, margin: (u16, u16), block: u16) -> u16 {
207 let nr_width = (start_nr + 50).ilog10() as u16 + 1;
208 nr_width + flag_width + margin.0 + margin.1 + block + 1
209 }
210}
211
212impl Default for LineNumberStyle {
213 fn default() -> Self {
214 Self {
215 flag_width: None,
216 margin: None,
217 format: None,
218 style: Default::default(),
219 cursor: None,
220 block: None,
221 non_exhaustive: NonExhaustive,
222 }
223 }
224}
225
226impl StatefulWidget for LineNumbers<'_> {
227 type State = LineNumberState;
228
229 #[allow(clippy::manual_unwrap_or_default)]
230 #[allow(clippy::manual_unwrap_or)]
231 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
232 state.area = area;
233 state.inner = self.block.inner_if_some(area);
234
235 state.start = if let Some(text_area) = self.text_area {
236 text_area.offset().1 as upos_type
237 } else if let Some(start) = self.start {
238 start
239 } else {
240 0
241 };
242 let end = if let Some(text_area) = self.text_area {
243 text_area.len_lines()
244 } else if let Some(end) = self.end {
245 end
246 } else {
247 state.start + state.inner.height as upos_type
248 };
249
250 let nr_width = if let Some(text_area) = self.text_area {
251 (text_area.vscroll.offset() + 50).ilog10() as u16 + 1
252 } else if let Some(end) = self.end {
253 end.ilog10() as u16 + 1
254 } else if let Some(start) = self.start {
255 (start + 50).ilog10() as u16 + 1
256 } else {
257 3
258 };
259
260 let flag_width = if let Some(flag_width) = self.flag_width {
261 flag_width
262 } else {
263 self.flags
264 .iter()
265 .map(|v| v.width() as u16)
266 .max()
267 .unwrap_or_default()
268 };
269
270 let format = if let Some(format) = self.format {
271 format
272 } else {
273 let mut f = "#".repeat(nr_width.saturating_sub(1) as usize);
274 f.push('0');
275 NumberFormat::new(f).expect("valid")
276 };
277
278 let cursor_style = if let Some(cursor_style) = self.cursor_style {
279 cursor_style
280 } else {
281 self.style
282 };
283
284 if let Some(block) = self.block {
285 block.render(area, buf);
286 } else {
287 buf.set_style(area, self.style);
288 }
289
290 let cursor = if let Some(text_area) = self.text_area {
291 text_area.cursor()
292 } else if let Some(cursor) = self.cursor {
293 TextPosition::new(0, cursor)
294 } else {
295 TextPosition::new(0, upos_type::MAX)
296 };
297
298 let mut tmp = String::new();
299 let mut prev_nr = upos_type::MAX;
300
301 for y in state.inner.top()..state.inner.bottom() {
302 let nr;
303 let rel_nr;
304 let render_nr;
305 let render_cursor;
306
307 if let Some(text_area) = self.text_area {
308 let rel_y = y - state.inner.y;
309 if let Some(pos) = text_area.relative_screen_to_pos((0, rel_y as i16)) {
310 nr = pos.y;
311 if self.relative {
312 rel_nr = nr.abs_diff(cursor.y);
313 } else {
314 rel_nr = nr;
315 }
316 render_nr = pos.y != prev_nr;
317 render_cursor = pos.y == cursor.y;
318 } else {
319 nr = 0;
320 rel_nr = 0;
321 render_nr = false;
322 render_cursor = false;
323 }
324 } else {
325 nr = state.start + (y - state.inner.y) as upos_type;
326 render_nr = nr < end;
327 render_cursor = Some(nr) == self.cursor;
328 if self.relative {
329 rel_nr = nr.abs_diff(self.cursor.unwrap_or_default());
330 } else {
331 rel_nr = nr;
332 }
333 }
334
335 tmp.clear();
336 if render_nr {
337 _ = format.fmt_to(rel_nr, &mut tmp);
338 }
339
340 let style = if render_cursor {
341 cursor_style
342 } else {
343 self.style
344 };
345
346 let nr_area = Rect::new(
347 state.inner.x + self.margin.0, y,
349 nr_width,
350 1,
351 )
352 .intersection(area);
353 buf.set_stringn(nr_area.x, nr_area.y, &tmp, nr_area.width as usize, style);
354
355 if let Some(flags) = self.flags.get((y - state.inner.y) as usize) {
356 flags.render(
357 Rect::new(
358 state.inner.x + self.margin.0 + nr_width + 1,
359 y,
360 flag_width,
361 1,
362 ),
363 buf,
364 );
365 }
366
367 prev_nr = nr;
368 }
369 }
370}
371
372impl Default for LineNumberState {
373 fn default() -> Self {
374 Self {
375 area: Default::default(),
376 inner: Default::default(),
377 start: 0,
378 mouse: Default::default(),
379 non_exhaustive: NonExhaustive,
380 }
381 }
382}
383
384impl LineNumberState {
385 pub fn new() -> Self {
386 Self::default()
387 }
388}