1use crate::_private::NonExhaustive;
32use crate::text_area::TextAreaState;
33use crate::{TextPosition, upos_type};
34use format_num_pattern::NumberFormat;
35use rat_event::util::MouseFlags;
36use ratatui::buffer::Buffer;
37use ratatui::layout::Rect;
38use ratatui::prelude::BlockExt;
39use ratatui::style::Style;
40use ratatui::text::Line;
41use ratatui::widgets::StatefulWidget;
42use ratatui::widgets::{Block, Widget};
43
44#[derive(Debug, Default, Clone)]
50pub struct LineNumbers<'a> {
51 start: Option<upos_type>,
52 end: Option<upos_type>,
53 cursor: Option<upos_type>,
54 text_area: Option<&'a TextAreaState>,
55
56 relative: bool,
57 flags: Vec<Line<'a>>,
58 flag_width: Option<u16>,
59 margin: (u16, u16),
60
61 format: Option<NumberFormat>,
62 style: Style,
63 cursor_style: Option<Style>,
64
65 block: Option<Block<'a>>,
66}
67
68#[derive(Debug, Clone)]
70pub struct LineNumberStyle {
71 pub flag_width: Option<u16>,
72 pub margin: Option<(u16, u16)>,
73 pub format: Option<NumberFormat>,
74 pub style: Style,
75 pub cursor: Option<Style>,
76 pub block: Option<Block<'static>>,
77
78 pub non_exhaustive: NonExhaustive,
79}
80
81#[derive(Debug, Clone)]
83pub struct LineNumberState {
84 pub area: Rect,
85 pub inner: Rect,
86
87 pub start: upos_type,
89
90 pub mouse: MouseFlags,
92
93 pub non_exhaustive: NonExhaustive,
94}
95
96impl<'a> LineNumbers<'a> {
97 pub fn new() -> Self {
98 Self::default()
99 }
100
101 pub fn with_textarea(mut self, text_area: &'a TextAreaState) -> Self {
107 self.text_area = Some(text_area);
108 self
109 }
110
111 pub fn start(mut self, start: upos_type) -> Self {
113 self.start = Some(start);
114 self
115 }
116
117 pub fn end(mut self, end: upos_type) -> Self {
119 self.end = Some(end);
120 self
121 }
122
123 pub fn cursor(mut self, cursor: upos_type) -> Self {
125 self.cursor = Some(cursor);
126 self
127 }
128
129 pub fn relative(mut self, relative: bool) -> Self {
131 self.relative = relative;
132 self
133 }
134
135 pub fn flags(mut self, flags: Vec<Line<'a>>) -> Self {
139 self.flags = flags;
140 self
141 }
142
143 pub fn flag_width(mut self, width: u16) -> Self {
145 self.flag_width = Some(width);
146 self
147 }
148
149 pub fn margin(mut self, margin: (u16, u16)) -> Self {
151 self.margin = margin;
152 self
153 }
154
155 pub fn format(mut self, format: NumberFormat) -> Self {
157 self.format = Some(format);
158 self
159 }
160
161 pub fn styles(mut self, styles: LineNumberStyle) -> Self {
163 self.style = styles.style;
164 if let Some(flag_width) = styles.flag_width {
165 self.flag_width = Some(flag_width);
166 }
167 if let Some(margin) = styles.margin {
168 self.margin = margin;
169 }
170 if let Some(format) = styles.format {
171 self.format = Some(format);
172 }
173 if let Some(cursor_style) = styles.cursor {
174 self.cursor_style = Some(cursor_style);
175 }
176 if let Some(block) = styles.block {
177 self.block = Some(block);
178 }
179 self.block = self.block.map(|v| v.style(self.style));
180 self
181 }
182
183 pub fn style(mut self, style: Style) -> Self {
185 self.style = style;
186 self.block = self.block.map(|v| v.style(style));
187 self
188 }
189
190 pub fn cursor_style(mut self, style: Style) -> Self {
192 self.cursor_style = Some(style);
193 self
194 }
195
196 pub fn block(mut self, block: Block<'a>) -> Self {
198 self.block = Some(block.style(self.style));
199 self
200 }
201
202 #[deprecated(since = "1.1.0", note = "use width_for()")]
204 pub fn width(&self) -> u16 {
205 let nr_width = if let Some(text_area) = self.text_area {
206 (text_area.vscroll.offset() + 50).ilog10() as u16 + 1
207 } else if let Some(end) = self.end {
208 end.ilog10() as u16 + 1
209 } else if let Some(start) = self.start {
210 (start + 50).ilog10() as u16 + 1
211 } else {
212 3
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 block_width = {
226 let area = self.block.inner_if_some(Rect::new(0, 0, 2, 2));
227 2 - area.width
228 };
229
230 nr_width + flag_width + self.margin.0 + self.margin.1 + block_width + 1
231 }
232
233 pub fn width_for(start_nr: usize, flag_width: u16, margin: (u16, u16), block: u16) -> u16 {
235 let nr_width = (start_nr + 50).ilog10() as u16 + 1;
236 nr_width + flag_width + margin.0 + margin.1 + block + 1
237 }
238}
239
240impl Default for LineNumberStyle {
241 fn default() -> Self {
242 Self {
243 flag_width: None,
244 margin: None,
245 format: None,
246 style: Default::default(),
247 cursor: None,
248 block: None,
249 non_exhaustive: NonExhaustive,
250 }
251 }
252}
253
254impl StatefulWidget for LineNumbers<'_> {
255 type State = LineNumberState;
256
257 #[allow(clippy::manual_unwrap_or_default)]
258 #[allow(clippy::manual_unwrap_or)]
259 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
260 state.area = area;
261 state.inner = self.block.inner_if_some(area);
262
263 state.start = if let Some(text_area) = self.text_area {
264 text_area.offset().1 as upos_type
265 } else if let Some(start) = self.start {
266 start
267 } else {
268 0
269 };
270 let end = if let Some(text_area) = self.text_area {
271 text_area.len_lines()
272 } else if let Some(end) = self.end {
273 end
274 } else {
275 state.start + state.inner.height as upos_type
276 };
277
278 let nr_width = if let Some(text_area) = self.text_area {
279 (text_area.vscroll.offset() + 50).ilog10() as u16 + 1
280 } else if let Some(end) = self.end {
281 end.ilog10() as u16 + 1
282 } else if let Some(start) = self.start {
283 (start + 50).ilog10() as u16 + 1
284 } else {
285 3
286 };
287
288 let flag_width = if let Some(flag_width) = self.flag_width {
289 flag_width
290 } else {
291 self.flags
292 .iter()
293 .map(|v| v.width() as u16)
294 .max()
295 .unwrap_or_default()
296 };
297
298 let format = if let Some(format) = self.format {
299 format
300 } else {
301 let mut f = "#".repeat(nr_width.saturating_sub(1) as usize);
302 f.push('0');
303 NumberFormat::new(f).expect("valid")
304 };
305
306 let cursor_style = if let Some(cursor_style) = self.cursor_style {
307 cursor_style
308 } else {
309 self.style
310 };
311
312 if let Some(block) = self.block {
313 block.render(area, buf);
314 } else {
315 buf.set_style(area, self.style);
316 }
317
318 let cursor = if let Some(text_area) = self.text_area {
319 text_area.cursor()
320 } else if let Some(cursor) = self.cursor {
321 TextPosition::new(0, cursor)
322 } else {
323 TextPosition::new(0, upos_type::MAX)
324 };
325
326 let mut tmp = String::new();
327 let mut prev_nr = upos_type::MAX;
328
329 for y in state.inner.top()..state.inner.bottom() {
330 let nr;
331 let rel_nr;
332 let render_nr;
333 let render_cursor;
334
335 if let Some(text_area) = self.text_area {
336 let rel_y = y - state.inner.y;
337 if let Some(pos) = text_area.relative_screen_to_pos((0, rel_y as i16)) {
338 nr = pos.y;
339 if self.relative {
340 rel_nr = nr.abs_diff(cursor.y);
341 } else {
342 rel_nr = nr;
343 }
344 render_nr = pos.y != prev_nr;
345 render_cursor = pos.y == cursor.y;
346 } else {
347 nr = 0;
348 rel_nr = 0;
349 render_nr = false;
350 render_cursor = false;
351 }
352 } else {
353 nr = state.start + (y - state.inner.y) as upos_type;
354 render_nr = nr < end;
355 render_cursor = Some(nr) == self.cursor;
356 if self.relative {
357 rel_nr = nr.abs_diff(self.cursor.unwrap_or_default());
358 } else {
359 rel_nr = nr;
360 }
361 }
362
363 tmp.clear();
364 if render_nr {
365 _ = format.fmt_to(rel_nr, &mut tmp);
366 }
367
368 let style = if render_cursor {
369 cursor_style
370 } else {
371 self.style
372 };
373
374 let nr_area = Rect::new(
375 state.inner.x + self.margin.0, y,
377 nr_width,
378 1,
379 )
380 .intersection(area);
381 buf.set_stringn(nr_area.x, nr_area.y, &tmp, nr_area.width as usize, style);
382
383 if let Some(flags) = self.flags.get((y - state.inner.y) as usize) {
384 flags.render(
385 Rect::new(
386 state.inner.x + self.margin.0 + nr_width + 1,
387 y,
388 flag_width,
389 1,
390 ),
391 buf,
392 );
393 }
394
395 prev_nr = nr;
396 }
397 }
398}
399
400impl Default for LineNumberState {
401 fn default() -> Self {
402 Self {
403 area: Default::default(),
404 inner: Default::default(),
405 start: 0,
406 mouse: Default::default(),
407 non_exhaustive: NonExhaustive,
408 }
409 }
410}
411
412impl LineNumberState {
413 pub fn new() -> Self {
414 Self::default()
415 }
416}