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 pub fn width_for(start_nr: upos_type, flag_width: u16, margin: (u16, u16), block: u16) -> u16 {
204 let nr_width = (start_nr + 50).ilog10() as u16 + 1;
205 nr_width + flag_width + margin.0 + margin.1 + block
206 }
207}
208
209impl Default for LineNumberStyle {
210 fn default() -> Self {
211 Self {
212 flag_width: None,
213 margin: None,
214 format: None,
215 style: Default::default(),
216 cursor: None,
217 block: None,
218 non_exhaustive: NonExhaustive,
219 }
220 }
221}
222
223impl StatefulWidget for LineNumbers<'_> {
224 type State = LineNumberState;
225
226 #[allow(clippy::manual_unwrap_or_default)]
227 #[allow(clippy::manual_unwrap_or)]
228 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
229 state.area = area;
230 state.inner = self.block.inner_if_some(area);
231
232 state.start = if let Some(text_area) = self.text_area {
233 text_area.offset().1 as upos_type
234 } else if let Some(start) = self.start {
235 start
236 } else {
237 0
238 };
239 let end = if let Some(text_area) = self.text_area {
240 text_area.len_lines()
241 } else if let Some(end) = self.end {
242 end
243 } else {
244 state.start + state.inner.height as upos_type
245 };
246
247 let nr_width = if let Some(text_area) = self.text_area {
248 (text_area.vscroll.offset() + 50).ilog10() as u16 + 1
249 } else if let Some(end) = self.end {
250 end.ilog10() as u16 + 1
251 } else if let Some(start) = self.start {
252 (start + 50).ilog10() as u16 + 1
253 } else {
254 3
255 };
256
257 let flag_width = if let Some(flag_width) = self.flag_width {
258 flag_width
259 } else {
260 self.flags
261 .iter()
262 .map(|v| v.width() as u16)
263 .max()
264 .unwrap_or_default()
265 };
266
267 let format = if let Some(format) = self.format {
268 format
269 } else {
270 let mut f = "#".repeat(nr_width.saturating_sub(1) as usize);
271 f.push('0');
272 NumberFormat::new(f).expect("valid")
273 };
274
275 let cursor_style = if let Some(cursor_style) = self.cursor_style {
276 cursor_style
277 } else {
278 self.style
279 };
280
281 if let Some(block) = self.block {
282 block.render(area, buf);
283 } else {
284 buf.set_style(area, self.style);
285 }
286
287 let cursor = if let Some(text_area) = self.text_area {
288 text_area.cursor()
289 } else if let Some(cursor) = self.cursor {
290 TextPosition::new(0, cursor)
291 } else {
292 TextPosition::new(0, upos_type::MAX)
293 };
294
295 let mut tmp = String::new();
296 let mut prev_nr = upos_type::MAX;
297
298 for y in state.inner.top()..state.inner.bottom() {
299 let nr;
300 let rel_nr;
301 let render_nr;
302 let render_cursor;
303
304 if let Some(text_area) = self.text_area {
305 let rel_y = y - state.inner.y;
306 if let Some(pos) = text_area.relative_screen_to_pos((0, rel_y as i16)) {
307 nr = pos.y;
308 if self.relative {
309 rel_nr = nr.abs_diff(cursor.y);
310 } else {
311 rel_nr = nr;
312 }
313 render_nr = pos.y != prev_nr;
314 render_cursor = pos.y == cursor.y;
315 } else {
316 nr = 0;
317 rel_nr = 0;
318 render_nr = false;
319 render_cursor = false;
320 }
321 } else {
322 nr = state.start + (y - state.inner.y) as upos_type;
323 render_nr = nr < end;
324 render_cursor = Some(nr) == self.cursor;
325 if self.relative {
326 rel_nr = nr.abs_diff(self.cursor.unwrap_or_default());
327 } else {
328 rel_nr = nr;
329 }
330 }
331
332 tmp.clear();
333 if render_nr {
334 _ = format.fmt_to(rel_nr, &mut tmp);
335 }
336
337 let style = if render_cursor {
338 cursor_style
339 } else {
340 self.style
341 };
342
343 let nr_area = Rect::new(
344 state.inner.x + self.margin.0, y,
346 nr_width,
347 1,
348 )
349 .intersection(area);
350 buf.set_stringn(nr_area.x, nr_area.y, &tmp, nr_area.width as usize, style);
351
352 if let Some(flags) = self.flags.get((y - state.inner.y) as usize) {
353 flags.render(
354 Rect::new(
355 state.inner.x + self.margin.0 + nr_width + 1,
356 y,
357 flag_width,
358 1,
359 ),
360 buf,
361 );
362 }
363
364 prev_nr = nr;
365 }
366 }
367}
368
369impl Default for LineNumberState {
370 fn default() -> Self {
371 Self {
372 area: Default::default(),
373 inner: Default::default(),
374 start: 0,
375 mouse: Default::default(),
376 non_exhaustive: NonExhaustive,
377 }
378 }
379}
380
381impl LineNumberState {
382 pub fn new() -> Self {
383 Self::default()
384 }
385}