1use std::{fmt::Alignment, marker::PhantomData};
14
15use crate::{
16 context::{self, FixedFile},
17 form::{self, Form},
18 text::{AlignCenter, AlignLeft, AlignRight, Builder, Text, text},
19 ui::{Area, Constraint, PushSpecs, Ui},
20 widgets::{Widget, WidgetCfg},
21};
22
23pub struct LineNumbers<U: Ui> {
24 ff: FixedFile<U>,
25 text: Text,
26 cfg: LineNumbersOptions<U>,
27}
28
29impl<U: Ui> LineNumbers<U> {
30 fn calculate_width(&mut self) -> f32 {
32 let len = self.ff.read().0.text().len().line();
33 len.ilog10() as f32
34 }
35
36 fn update_text(&mut self) {
37 let (main_line, printed_lines) = {
38 let (file, _) = self.ff.read();
39 let main_line = match file.cursors().is_empty() {
40 true => usize::MAX,
41 false => file.cursors().get_main().unwrap().line(),
42 };
43 (main_line, file.printed_lines().to_vec())
44 };
45
46 let mut builder = Text::builder();
47 align(&mut builder, self.cfg.align);
48
49 for (index, (line, is_wrapped)) in printed_lines.iter().enumerate() {
50 if main_line == *line {
51 align(&mut builder, self.cfg.main_align);
52 }
53
54 match (main_line == *line, is_wrapped) {
55 (false, false) => text!(builder, [LineNum]),
56 (true, false) => text!(builder, [MainLineNum]),
57 (false, true) => text!(builder, [WrappedLineNum]),
58 (true, true) => text!(builder, [WrappedMainLineNum]),
59 }
60
61 let is_wrapped = *is_wrapped && index > 0;
62 push_text(&mut builder, *line, main_line, is_wrapped, &self.cfg);
63
64 if main_line == *line {
65 align(&mut builder, self.cfg.align);
66 }
67 }
68
69 self.text = builder.finish();
70 }
71
72 pub fn options(&self) -> &LineNumbersOptions<U> {
74 &self.cfg
75 }
76
77 pub fn options_mut(&mut self) -> &mut LineNumbersOptions<U> {
79 &mut self.cfg
80 }
81}
82
83impl<U: Ui> Widget<U> for LineNumbers<U> {
84 type Cfg = LineNumbersOptions<U>;
85
86 fn cfg() -> Self::Cfg {
87 Self::Cfg {
88 num_rel: LineNum::Abs,
89 align: Alignment::Left,
90 main_align: Alignment::Right,
91 show_wraps: false,
92 specs: PushSpecs::left(),
93 _ghost: PhantomData,
94 }
95 }
96
97 fn update(&mut self, area: &U::Area) {
98 let width = self.calculate_width();
99 area.constrain_hor([Constraint::Len(width + 1.0)]).unwrap();
100
101 self.update_text();
102 }
103
104 fn text(&self) -> &Text {
105 &self.text
106 }
107
108 fn text_mut(&mut self) -> &mut Text {
109 &mut self.text
110 }
111
112 fn once() -> Result<(), Text> {
113 form::set_weak("LineNum", Form::grey());
114 form::set_weak("MainLineNum", Form::yellow());
115 form::set_weak("WrappedLineNum", Form::cyan().italic());
116 form::set_weak("WrappedMainLineNum", "WrappedLineNum");
117 Ok(())
118 }
119}
120
121#[derive(Default, Debug, Clone, Copy)]
123pub enum LineNum {
124 #[default]
125 Abs,
127 Rel,
130 RelAbs,
132}
133
134#[derive(Debug, Clone, Copy)]
136#[doc(hidden)]
137pub struct LineNumbersOptions<U> {
138 pub num_rel: LineNum,
139 pub align: Alignment,
140 pub main_align: Alignment,
141 pub show_wraps: bool,
142 specs: PushSpecs,
143 _ghost: PhantomData<U>,
144}
145
146impl<U> LineNumbersOptions<U> {
147 pub fn absolute(self) -> Self {
148 Self { num_rel: LineNum::Abs, ..self }
149 }
150
151 pub fn relative(self) -> Self {
152 Self { num_rel: LineNum::Rel, ..self }
153 }
154
155 pub fn rel_abs(self) -> Self {
156 Self { num_rel: LineNum::RelAbs, ..self }
157 }
158
159 pub fn align_left(self) -> Self {
160 Self {
161 main_align: Alignment::Left,
162 align: Alignment::Left,
163 ..self
164 }
165 }
166
167 pub fn align_center(self) -> Self {
168 Self {
169 main_align: Alignment::Center,
170 align: Alignment::Center,
171 ..self
172 }
173 }
174
175 pub fn align_right(self) -> Self {
176 Self {
177 main_align: Alignment::Right,
178 align: Alignment::Right,
179 ..self
180 }
181 }
182
183 pub fn align_main_left(self) -> Self {
184 Self { main_align: Alignment::Left, ..self }
185 }
186
187 pub fn align_main_center(self) -> Self {
188 Self { main_align: Alignment::Center, ..self }
189 }
190
191 pub fn align_main_right(self) -> Self {
192 Self { main_align: Alignment::Right, ..self }
193 }
194
195 pub fn show_wraps(self) -> Self {
196 Self { show_wraps: true, ..self }
197 }
198
199 pub fn hide_wraps(self) -> Self {
200 Self { show_wraps: false, ..self }
201 }
202
203 pub fn on_the_right(self) -> Self {
204 Self { specs: self.specs.to_right(), ..self }
205 }
206}
207
208impl<U: Ui> WidgetCfg<U> for LineNumbersOptions<U> {
209 type Widget = LineNumbers<U>;
210
211 fn build(self, _: bool) -> (Self::Widget, impl Fn() -> bool, PushSpecs) {
212 let ff = context::fixed_file().unwrap();
213 let specs = self.specs;
214
215 let checker = ff.checker();
216 let mut widget = LineNumbers { ff, text: Text::default(), cfg: self };
217 widget.update_text();
218
219 (widget, checker, specs)
220 }
221}
222
223fn push_text<U>(
225 b: &mut Builder,
226 line: usize,
227 main: usize,
228 is_wrapped: bool,
229 cfg: &LineNumbersOptions<U>,
230) {
231 if is_wrapped && !cfg.show_wraps {
232 text!(*b, "\n");
233 } else if main != usize::MAX {
234 let num = match cfg.num_rel {
235 LineNum::Abs => line + 1,
236 LineNum::Rel => line.abs_diff(main),
237 LineNum::RelAbs => {
238 if line != main {
239 line.abs_diff(main)
240 } else {
241 line + 1
242 }
243 }
244 };
245
246 text!(*b, num "\n");
247 } else {
248 text!(*b, { line + 1 } "\n");
249 }
250}
251
252fn align(b: &mut Builder, alignment: Alignment) {
253 match alignment {
254 Alignment::Left => text!(*b, AlignLeft),
255 Alignment::Center => text!(*b, AlignCenter),
256 Alignment::Right => text!(*b, AlignRight),
257 }
258}