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