1use std::{fmt::Alignment, marker::PhantomData};
14
15use crate::{
16 context::{self, FileReader},
17 form::{self, Form},
18 text::{Builder, Tag, Text, text},
19 ui::{Area, Constraint, PushSpecs, Ui},
20 widgets::{Widget, WidgetCfg},
21};
22
23pub struct LineNumbers<U: Ui> {
24 reader: FileReader<U>,
25 text: Text,
26 cfg: LineNumbersCfg<U>,
27}
28
29impl<U: Ui> LineNumbers<U> {
30 fn calculate_width(&mut self) -> f32 {
32 let len = self.reader.inspect(|file, _| file.text().len().line()) + 1;
34 len.ilog10() as f32
35 }
36
37 fn update_text(&mut self) {
38 self.text = self.reader.inspect(|file, _| {
39 let printed_lines = file.printed_lines();
40 let cursors = file.cursors().unwrap();
41 let main_line = match cursors.is_empty() {
42 true => usize::MAX,
43 false => cursors.main().line(),
44 };
45
46 let mut builder = Text::builder();
47 text!(builder, { tag_from_align(self.cfg.align) });
48
49 for (index, (line, is_wrapped)) in printed_lines.iter().enumerate() {
50 if main_line == *line {
51 text!(builder, { tag_from_align(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 text!(builder, { tag_from_align(self.cfg.align) });
66 }
67 }
68
69 builder.finish()
70 });
71 }
72
73 pub fn get_cfg(&self) -> LineNumbersCfg<U> {
74 self.cfg
75 }
76
77 pub fn reconfigure(&mut self, cfg: LineNumbersCfg<U>) {
78 self.cfg = cfg
79 }
80}
81
82impl<U: Ui> Widget<U> for LineNumbers<U> {
83 type Cfg = LineNumbersCfg<U>;
84
85 fn cfg() -> Self::Cfg {
86 LineNumbersCfg::new()
87 }
88
89 fn update(&mut self, area: &U::Area) {
90 let width = self.calculate_width();
91 area.constrain_hor(Constraint::Length(width + 1.0)).unwrap();
92
93 self.update_text();
94 }
95
96 fn text(&self) -> &Text {
97 &self.text
98 }
99
100 fn text_mut(&mut self) -> &mut Text {
101 &mut self.text
102 }
103
104 fn once() -> crate::Result<(), ()> {
105 form::set_weak("LineNum", Form::grey());
106 form::set_weak("MainLineNum", Form::yellow());
107 form::set_weak("WrappedLineNum", Form::cyan().italic());
108 form::set_weak("WrappedMainLineNum", "WrappedLineNum");
109 Ok(())
110 }
111}
112
113#[derive(Default, Debug, Copy, Clone)]
115pub enum LineNum {
116 #[default]
117 Abs,
119 Rel,
122 RelAbs,
124}
125
126#[derive(Debug)]
128pub struct LineNumbersCfg<U> {
129 pub num_rel: LineNum,
130 pub align: Alignment,
131 pub main_align: Alignment,
132 pub show_wraps: bool,
133 pub specs: PushSpecs,
134 pub ghost: PhantomData<U>,
135}
136
137impl<U> Copy for LineNumbersCfg<U> {}
138impl<U> Clone for LineNumbersCfg<U> {
139 fn clone(&self) -> Self {
140 *self
141 }
142}
143
144impl<U> Default for LineNumbersCfg<U> {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150impl<U> LineNumbersCfg<U> {
151 pub fn new() -> Self {
152 Self {
153 num_rel: LineNum::Abs,
154 align: Alignment::Left,
155 main_align: Alignment::Right,
156 show_wraps: false,
157 specs: PushSpecs::left(),
158 ghost: PhantomData,
159 }
160 }
161
162 pub fn absolute(self) -> Self {
163 Self { num_rel: LineNum::Abs, ..self }
164 }
165
166 pub fn relative(self) -> Self {
167 Self { num_rel: LineNum::Rel, ..self }
168 }
169
170 pub fn rel_abs(self) -> Self {
171 Self { num_rel: LineNum::RelAbs, ..self }
172 }
173
174 pub fn align_left(self) -> Self {
175 Self {
176 main_align: Alignment::Left,
177 align: Alignment::Left,
178 ..self
179 }
180 }
181
182 pub fn align_center(self) -> Self {
183 Self {
184 main_align: Alignment::Center,
185 align: Alignment::Center,
186 ..self
187 }
188 }
189
190 pub fn align_right(self) -> Self {
191 Self {
192 main_align: Alignment::Right,
193 align: Alignment::Right,
194 ..self
195 }
196 }
197
198 pub fn align_main_left(self) -> Self {
199 Self { main_align: Alignment::Left, ..self }
200 }
201
202 pub fn align_main_center(self) -> Self {
203 Self { main_align: Alignment::Center, ..self }
204 }
205
206 pub fn align_main_right(self) -> Self {
207 Self { main_align: Alignment::Right, ..self }
208 }
209
210 pub fn show_wraps(self) -> Self {
211 Self { show_wraps: true, ..self }
212 }
213
214 pub fn hide_wraps(self) -> Self {
215 Self { show_wraps: false, ..self }
216 }
217
218 pub fn on_the_right(self) -> Self {
219 Self { specs: self.specs.to_right(), ..self }
220 }
221}
222
223impl<U: Ui> WidgetCfg<U> for LineNumbersCfg<U> {
224 type Widget = LineNumbers<U>;
225
226 fn build(self, _: bool) -> (Self::Widget, impl Fn() -> bool, PushSpecs) {
227 let reader = context::cur_file().unwrap().fixed_reader();
228 let specs = self.specs;
229
230 let mut widget = LineNumbers {
231 reader: reader.clone(),
232 text: Text::default(),
233 cfg: self,
234 };
235 widget.update_text();
236
237 (widget, move || reader.has_changed(), specs)
238 }
239}
240
241fn push_text<U>(
243 builder: &mut Builder,
244 line: usize,
245 main: usize,
246 is_wrapped: bool,
247 cfg: &LineNumbersCfg<U>,
248) {
249 if is_wrapped && !cfg.show_wraps {
250 text!(*builder, "\n");
251 } else if main != usize::MAX {
252 let num = match cfg.num_rel {
253 LineNum::Abs => line + 1,
254 LineNum::Rel => line.abs_diff(main),
255 LineNum::RelAbs => {
256 if line != main {
257 line.abs_diff(main)
258 } else {
259 line + 1
260 }
261 }
262 };
263
264 text!(*builder, num "\n");
265 } else {
266 text!(*builder, { line + 1 } "\n");
267 }
268}
269
270fn tag_from_align(alignment: Alignment) -> Option<Tag> {
271 match alignment {
272 Alignment::Left => None,
273 Alignment::Right => Some(Tag::StartAlignRight),
274 Alignment::Center => Some(Tag::StartAlignCenter),
275 }
276}