duat_base/widgets/
line_numbers.rs1use std::fmt::Alignment;
14
15use duat_core::{
16 context::Handle,
17 data::Pass,
18 form,
19 text::{AlignCenter, AlignLeft, AlignRight, Builder, Text},
20 ui::{PushSpecs, Side, Widget},
21};
22
23pub struct LineNumbers {
36 buffer: Handle,
37 text: Text,
38 pub relative: bool,
42 pub align: Alignment,
46 pub main_align: Alignment,
50 pub show_wraps: bool,
54}
55
56impl LineNumbers {
57 pub fn builder() -> LineNumbersOpts {
60 LineNumbersOpts::default()
61 }
62
63 fn calculate_width(&self, pa: &Pass) -> f32 {
65 let len = self.buffer.read(pa).text().len().line();
66 len.ilog10() as f32
67 }
68
69 fn form_text(&self, pa: &Pass) -> Text {
70 let (main_line, printed_lines) = {
71 let buffer = self.buffer.read(pa);
72 let main_line = if buffer.selections().is_empty() {
73 usize::MAX
74 } else {
75 buffer.selections().get_main().unwrap().line()
76 };
77
78 (main_line, buffer.printed_lines().to_vec())
79 };
80
81 let mut builder = Text::builder();
82 align(&mut builder, self.align);
83
84 for (index, (line, is_wrapped)) in printed_lines.iter().enumerate() {
85 if *line == main_line {
86 align(&mut builder, self.main_align);
87 }
88
89 match (*line == main_line, is_wrapped) {
90 (false, false) => {}
91 (true, false) => builder.push(form::id_of!("linenum.main")),
92 (false, true) => builder.push(form::id_of!("linenum.wrapped")),
93 (true, true) => builder.push(form::id_of!("linenum.wrapped.main")),
94 }
95
96 let is_wrapped = *is_wrapped && index > 0;
97 push_text(&mut builder, *line, main_line, is_wrapped, self);
98
99 if *line == main_line {
100 align(&mut builder, self.align);
101 }
102 }
103
104 builder.build()
105 }
106}
107
108impl Widget for LineNumbers {
109 fn update(pa: &mut Pass, handle: &Handle<Self>) {
110 let width = handle.read(pa).calculate_width(pa);
111 handle.area().set_width(pa, width + 1.0).unwrap();
112
113 handle.write(pa).text = handle.read(pa).form_text(pa);
114 }
115
116 fn needs_update(&self, pa: &Pass) -> bool {
117 self.buffer.has_changed(pa)
118 }
119
120 fn text(&self) -> &Text {
121 &self.text
122 }
123
124 fn text_mut(&mut self) -> &mut Text {
125 &mut self.text
126 }
127}
128
129#[derive(Clone, Copy, Debug)]
140pub struct LineNumbersOpts {
141 pub relative: bool,
145 pub align: Alignment,
149 pub main_align: Alignment,
153 pub show_wraps: bool,
157 pub on_the_right: bool,
161}
162
163impl LineNumbersOpts {
164 pub const fn new() -> Self {
166 Self {
167 relative: false,
168 align: Alignment::Left,
169 main_align: Alignment::Right,
170 show_wraps: false,
171 on_the_right: false,
172 }
173 }
174
175 pub fn push_on(self, pa: &mut Pass, handle: &Handle) -> Handle<LineNumbers> {
177 let mut line_numbers = LineNumbers {
178 buffer: handle.clone(),
179 text: Text::default(),
180 relative: self.relative,
181 align: self.align,
182 main_align: self.main_align,
183 show_wraps: self.show_wraps,
184 };
185 line_numbers.text = line_numbers.form_text(pa);
186
187 let specs = PushSpecs {
188 side: if self.on_the_right {
189 Side::Right
190 } else {
191 Side::Left
192 },
193 ..Default::default()
194 };
195
196 handle.push_outer_widget(pa, line_numbers, specs)
197 }
198}
199
200impl Default for LineNumbersOpts {
201 fn default() -> Self {
202 Self::new()
203 }
204}
205
206fn push_text(b: &mut Builder, line: usize, main: usize, is_wrapped: bool, opts: &LineNumbers) {
208 if (!is_wrapped || opts.show_wraps) && main != usize::MAX {
209 let num = if opts.relative {
210 if line != main {
211 line.abs_diff(main)
212 } else {
213 line + 1
214 }
215 } else {
216 line + 1
217 };
218 b.push(num);
219 }
220
221 b.push("\n");
222 b.push(form::DEFAULT_ID);
223}
224
225fn align(b: &mut Builder, alignment: Alignment) {
226 match alignment {
227 Alignment::Left => b.push(AlignLeft),
228 Alignment::Center => b.push(AlignCenter),
229 Alignment::Right => b.push(AlignRight),
230 }
231}