1use crate::_private::NonExhaustive;
34use crate::text_area::TextAreaState;
35use crate::{TextPosition, upos_type};
36use format_num_pattern::NumberFormat;
37use rat_cursor::HasScreenCursor;
38use rat_event::util::MouseFlags;
39use rat_event::{HandleEvent, MouseOnly, Outcome, Regular};
40use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
41use rat_reloc::RelocatableState;
42use ratatui_core::buffer::Buffer;
43use ratatui_core::layout::Rect;
44use ratatui_core::style::Style;
45use ratatui_core::text::Line;
46use ratatui_core::widgets::StatefulWidget;
47use ratatui_core::widgets::Widget;
48use ratatui_crossterm::crossterm::event::Event;
49use ratatui_widgets::block::{Block, BlockExt};
50
51#[derive(Debug, Default, Clone)]
57pub struct LineNumbers<'a> {
58 start: Option<upos_type>,
59 end: Option<upos_type>,
60 cursor: Option<upos_type>,
61 text_area: Option<&'a TextAreaState>,
62
63 relative: bool,
64 flags: Vec<Line<'a>>,
65 flag_width: Option<u16>,
66 margin: (u16, u16),
67
68 format: Option<NumberFormat>,
69 style: Style,
70 cursor_style: Option<Style>,
71
72 block: Option<Block<'a>>,
73}
74
75#[derive(Debug, Clone)]
77pub struct LineNumberStyle {
78 pub flag_width: Option<u16>,
79 pub margin: Option<(u16, u16)>,
80 pub format: Option<NumberFormat>,
81 pub style: Style,
82 pub cursor: Option<Style>,
83 pub block: Option<Block<'static>>,
84
85 pub non_exhaustive: NonExhaustive,
86}
87
88#[derive(Debug, Clone)]
90pub struct LineNumberState {
91 pub area: Rect,
92 pub inner: Rect,
93
94 pub start: upos_type,
96
97 pub mouse: MouseFlags,
99
100 pub non_exhaustive: NonExhaustive,
101}
102
103impl<'a> LineNumbers<'a> {
104 pub fn new() -> Self {
105 Self::default()
106 }
107
108 pub fn with_textarea(mut self, text_area: &'a TextAreaState) -> Self {
114 self.text_area = Some(text_area);
115 self
116 }
117
118 pub fn start(mut self, start: upos_type) -> Self {
120 self.start = Some(start);
121 self
122 }
123
124 pub fn end(mut self, end: upos_type) -> Self {
126 self.end = Some(end);
127 self
128 }
129
130 pub fn cursor(mut self, cursor: upos_type) -> Self {
132 self.cursor = Some(cursor);
133 self
134 }
135
136 pub fn relative(mut self, relative: bool) -> Self {
138 self.relative = relative;
139 self
140 }
141
142 pub fn flags(mut self, flags: Vec<Line<'a>>) -> Self {
146 self.flags = flags;
147 self
148 }
149
150 pub fn flag_width(mut self, width: u16) -> Self {
152 self.flag_width = Some(width);
153 self
154 }
155
156 pub fn margin(mut self, margin: (u16, u16)) -> Self {
158 self.margin = margin;
159 self
160 }
161
162 pub fn format(mut self, format: NumberFormat) -> Self {
164 self.format = Some(format);
165 self
166 }
167
168 pub fn styles(mut self, styles: LineNumberStyle) -> Self {
170 self.style = styles.style;
171 if let Some(flag_width) = styles.flag_width {
172 self.flag_width = Some(flag_width);
173 }
174 if let Some(margin) = styles.margin {
175 self.margin = margin;
176 }
177 if let Some(format) = styles.format {
178 self.format = Some(format);
179 }
180 if let Some(cursor_style) = styles.cursor {
181 self.cursor_style = Some(cursor_style);
182 }
183 if let Some(block) = styles.block {
184 self.block = Some(block);
185 }
186 self.block = self.block.map(|v| v.style(self.style));
187 self
188 }
189
190 pub fn style(mut self, style: Style) -> Self {
192 self.style = style;
193 self.block = self.block.map(|v| v.style(style));
194 self
195 }
196
197 pub fn cursor_style(mut self, style: Style) -> Self {
199 self.cursor_style = Some(style);
200 self
201 }
202
203 pub fn block(mut self, block: Block<'a>) -> Self {
205 self.block = Some(block.style(self.style));
206 self
207 }
208
209 pub fn width_for(start_nr: upos_type, flag_width: u16, margin: (u16, u16), block: u16) -> u16 {
211 let nr_width = (start_nr + 50).ilog10() as u16 + 1;
212 nr_width + flag_width + margin.0 + margin.1 + block
213 }
214}
215
216impl Default for LineNumberStyle {
217 fn default() -> Self {
218 Self {
219 flag_width: None,
220 margin: None,
221 format: None,
222 style: Default::default(),
223 cursor: None,
224 block: None,
225 non_exhaustive: NonExhaustive,
226 }
227 }
228}
229
230impl StatefulWidget for LineNumbers<'_> {
231 type State = LineNumberState;
232
233 #[allow(clippy::manual_unwrap_or_default)]
234 #[allow(clippy::manual_unwrap_or)]
235 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
236 state.area = area;
237 state.inner = self.block.inner_if_some(area);
238
239 state.start = if let Some(text_area) = self.text_area {
240 text_area.offset().1 as upos_type
241 } else if let Some(start) = self.start {
242 start
243 } else {
244 0
245 };
246 let end = if let Some(text_area) = self.text_area {
247 text_area.len_lines()
248 } else if let Some(end) = self.end {
249 end
250 } else {
251 state.start + state.inner.height as upos_type
252 };
253
254 let nr_width = if let Some(text_area) = self.text_area {
255 (text_area.vscroll.offset() + 50).ilog10() as u16 + 1
256 } else if let Some(end) = self.end {
257 end.ilog10() as u16 + 1
258 } else if let Some(start) = self.start {
259 (start + 50).ilog10() as u16 + 1
260 } else {
261 3
262 };
263
264 let flag_width = if let Some(flag_width) = self.flag_width {
265 flag_width
266 } else {
267 self.flags
268 .iter()
269 .map(|v| v.width() as u16)
270 .max()
271 .unwrap_or_default()
272 };
273
274 let format = if let Some(format) = self.format {
275 format
276 } else {
277 let mut f = "#".repeat(nr_width.saturating_sub(1) as usize);
278 f.push('0');
279 NumberFormat::new(f).expect("valid")
280 };
281
282 let cursor_style = if let Some(cursor_style) = self.cursor_style {
283 cursor_style
284 } else {
285 self.style
286 };
287
288 if let Some(block) = self.block {
289 block.render(area, buf);
290 } else {
291 buf.set_style(area, self.style);
292 }
293
294 let cursor = if let Some(text_area) = self.text_area {
295 text_area.cursor()
296 } else if let Some(cursor) = self.cursor {
297 TextPosition::new(0, cursor)
298 } else {
299 TextPosition::new(0, upos_type::MAX)
300 };
301
302 let mut tmp = String::new();
303 let mut prev_nr = upos_type::MAX;
304
305 for y in state.inner.top()..state.inner.bottom() {
306 let nr;
307 let rel_nr;
308 let render_nr;
309 let render_cursor;
310
311 if let Some(text_area) = self.text_area {
312 let rel_y = y - state.inner.y;
313 if let Some(pos) = text_area.relative_screen_to_pos((0, rel_y as i16)) {
314 nr = pos.y;
315 if self.relative {
316 rel_nr = nr.abs_diff(cursor.y);
317 } else {
318 rel_nr = nr;
319 }
320 render_nr = pos.y != prev_nr;
321 render_cursor = pos.y == cursor.y;
322 } else {
323 nr = 0;
324 rel_nr = 0;
325 render_nr = false;
326 render_cursor = false;
327 }
328 } else {
329 nr = state.start + (y - state.inner.y) as upos_type;
330 render_nr = nr < end;
331 render_cursor = Some(nr) == self.cursor;
332 if self.relative {
333 rel_nr = nr.abs_diff(self.cursor.unwrap_or_default());
334 } else {
335 rel_nr = nr;
336 }
337 }
338
339 tmp.clear();
340 if render_nr {
341 _ = format.fmt_to(rel_nr, &mut tmp);
342 }
343
344 let style = if render_cursor {
345 cursor_style
346 } else {
347 self.style
348 };
349
350 let nr_area = Rect::new(
351 state.inner.x + self.margin.0, y,
353 nr_width,
354 1,
355 )
356 .intersection(area);
357 buf.set_stringn(nr_area.x, nr_area.y, &tmp, nr_area.width as usize, style);
358
359 if let Some(flags) = self.flags.get((y - state.inner.y) as usize) {
360 flags.render(
361 Rect::new(
362 state.inner.x + self.margin.0 + nr_width + 1,
363 y,
364 flag_width,
365 1,
366 ),
367 buf,
368 );
369 }
370
371 prev_nr = nr;
372 }
373 }
374}
375
376impl Default for LineNumberState {
377 fn default() -> Self {
378 Self {
379 area: Default::default(),
380 inner: Default::default(),
381 start: 0,
382 mouse: Default::default(),
383 non_exhaustive: NonExhaustive,
384 }
385 }
386}
387
388impl HasFocus for LineNumberState {
389 fn build(&self, _builder: &mut FocusBuilder) {
390 }
392
393 fn focus(&self) -> FocusFlag {
394 unimplemented!("not available")
395 }
396
397 fn area(&self) -> Rect {
398 unimplemented!("not available")
399 }
400}
401
402impl RelocatableState for LineNumberState {
403 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
404 self.area.relocate(shift, clip);
405 self.inner.relocate(shift, clip);
406 }
407}
408
409impl HasScreenCursor for LineNumberState {
410 fn screen_cursor(&self) -> Option<(u16, u16)> {
411 None
412 }
413}
414
415impl LineNumberState {
416 pub fn new() -> Self {
417 Self::default()
418 }
419
420 pub fn named(_name: &str) -> Self {
421 Self::default()
422 }
423}
424
425impl HandleEvent<Event, Regular, Outcome> for LineNumberState {
426 fn handle(&mut self, _event: &Event, _qualifier: Regular) -> Outcome {
427 Outcome::Continue
428 }
429}
430
431impl HandleEvent<Event, MouseOnly, Outcome> for LineNumberState {
432 fn handle(&mut self, _event: &Event, _qualifier: MouseOnly) -> Outcome {
433 Outcome::Continue
434 }
435}