1use std::{fmt::Alignment, marker::PhantomData};
14
15use duat_core::{prelude::*, text::Builder, ui::Constraint};
16
17pub struct LineNumbers<U: Ui> {
23 handle: FileHandle<U>,
24 text: Text,
25 cfg: LineNumbersOptions<U>,
26}
27
28impl<U: Ui> LineNumbers<U> {
29 fn calculate_width(&self, pa: &Pass) -> f32 {
31 let len = self.handle.read(pa, |file, _| file.text().len().line());
32 len.ilog10() as f32
33 }
34
35 fn form_text(&self, pa: &Pass) -> Text {
36 let (main_line, printed_lines) = self.handle.read(pa, |file, _| {
37 let main_line = if file.selections().is_empty() {
38 usize::MAX
39 } else {
40 file.selections().get_main().unwrap().line()
41 };
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 *line == main_line {
51 align(&mut builder, self.cfg.main_align);
52 }
53
54 match (*line == main_line, is_wrapped) {
55 (false, false) => {}
56 (true, false) => {
57 let id = form::id_of!("linenum.main");
58 builder.push(id)
59 }
60 (false, true) => builder.push(form::id_of!("linenum.wrapped")),
61 (true, true) => builder.push(form::id_of!("linenum.wrapped.main")),
62 }
63
64 let is_wrapped = *is_wrapped && index > 0;
65 push_text(&mut builder, *line, main_line, is_wrapped, &self.cfg);
66
67 if *line == main_line {
68 align(&mut builder, self.cfg.align);
69 }
70 }
71
72 builder.build()
73 }
74
75 pub fn options(&self) -> &LineNumbersOptions<U> {
77 &self.cfg
78 }
79
80 pub fn options_mut(&mut self) -> &mut LineNumbersOptions<U> {
82 &mut self.cfg
83 }
84}
85
86impl<U: Ui> Widget<U> for LineNumbers<U> {
87 type Cfg = LineNumbersOptions<U>;
88
89 fn update(pa: &mut Pass, handle: Handle<Self, U>) {
90 let width = handle.read(pa, |ln, _| ln.calculate_width(pa));
91 handle
92 .area()
93 .constrain_hor([Constraint::Len(width + 1.0)])
94 .unwrap();
95
96 let text = handle.read(pa, |ln, _| ln.form_text(pa));
97 handle.write(pa, |ln, _| ln.text = text);
98 }
99
100 fn needs_update(&self) -> bool {
101 self.handle.has_changed()
102 }
103
104 fn cfg() -> Self::Cfg {
105 Self::Cfg {
106 num_rel: LineNum::Abs,
107 align: Alignment::Left,
108 main_align: Alignment::Right,
109 show_wraps: false,
110 specs: PushSpecs::left(),
111 _ghost: PhantomData,
112 }
113 }
114
115 fn text(&self) -> &Text {
116 &self.text
117 }
118
119 fn text_mut(&mut self) -> &mut Text {
120 &mut self.text
121 }
122
123 fn once() -> Result<(), Text> {
124 form::set_weak("linenum.main", Form::yellow());
125 form::set_weak("linenum.wrapped", Form::cyan().italic());
126 form::set_weak("linenum.wrapped.main", "linenum.wrapped");
127 Ok(())
128 }
129}
130
131#[derive(Debug, Clone, Copy)]
133#[doc(hidden)]
134pub struct LineNumbersOptions<U> {
135 num_rel: LineNum = LineNum::Abs,
136 align: Alignment = Alignment::Left,
137 main_align: Alignment = Alignment::Right,
138 show_wraps: bool = false,
139 specs: PushSpecs = PushSpecs::left(),
140 _ghost: PhantomData<U> = PhantomData,
141}
142
143impl<U> LineNumbersOptions<U> {
144 pub fn absolute(self) -> Self {
145 Self { num_rel: LineNum::Abs, ..self }
146 }
147
148 pub fn relative(self) -> Self {
149 Self { num_rel: LineNum::Rel, ..self }
150 }
151
152 pub fn rel_abs(self) -> Self {
153 Self { num_rel: LineNum::RelAbs, ..self }
154 }
155
156 pub fn align_left(self) -> Self {
157 Self {
158 main_align: Alignment::Left,
159 align: Alignment::Left,
160 ..self
161 }
162 }
163
164 pub fn align_center(self) -> Self {
165 Self {
166 main_align: Alignment::Center,
167 align: Alignment::Center,
168 ..self
169 }
170 }
171
172 pub fn align_right(self) -> Self {
173 Self {
174 main_align: Alignment::Right,
175 align: Alignment::Right,
176 ..self
177 }
178 }
179
180 pub fn align_main_left(self) -> Self {
181 Self { main_align: Alignment::Left, ..self }
182 }
183
184 pub fn align_main_center(self) -> Self {
185 Self { main_align: Alignment::Center, ..self }
186 }
187
188 pub fn align_main_right(self) -> Self {
189 Self { main_align: Alignment::Right, ..self }
190 }
191
192 pub fn show_wraps(self) -> Self {
193 Self { show_wraps: true, ..self }
194 }
195
196 pub fn hide_wraps(self) -> Self {
197 Self { show_wraps: false, ..self }
198 }
199
200 pub fn on_the_right(self) -> Self {
201 Self { specs: self.specs.to_right(), ..self }
202 }
203}
204
205impl<U: Ui> WidgetCfg<U> for LineNumbersOptions<U> {
206 type Widget = LineNumbers<U>;
207
208 fn build(self, pa: &mut Pass, handle: Option<FileHandle<U>>) -> (Self::Widget, PushSpecs) {
209 let Some(handle) = handle else {
210 panic!("For now, you can't push LineNumbers to something that is not a File");
211 };
212 let specs = self.specs;
213
214 let mut widget = LineNumbers { handle, text: Text::default(), cfg: self };
215 widget.text = widget.form_text(pa);
216
217 (widget, specs)
218 }
219}
220
221fn push_text<U>(
223 b: &mut Builder,
224 line: usize,
225 main: usize,
226 is_wrapped: bool,
227 cfg: &LineNumbersOptions<U>,
228) {
229 if (!is_wrapped || cfg.show_wraps) && main != usize::MAX {
230 let num = match cfg.num_rel {
231 LineNum::Abs => line + 1,
232 LineNum::Rel => line.abs_diff(main),
233 LineNum::RelAbs => {
234 if line != main {
235 line.abs_diff(main)
236 } else {
237 line + 1
238 }
239 }
240 };
241 b.push(num);
242 }
243
244 b.push("\n");
245 b.push(form::DEFAULT_ID);
246}
247
248fn align(b: &mut Builder, alignment: Alignment) {
249 match alignment {
250 Alignment::Left => b.push(AlignLeft),
251 Alignment::Center => b.push(AlignCenter),
252 Alignment::Right => b.push(AlignRight),
253 }
254}
255
256#[derive(Default, Debug, Clone, Copy)]
258enum LineNum {
259 #[default]
260 Abs,
262 Rel,
265 RelAbs,
268}