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 = false,
42 pub align: Alignment = Alignment::Left,
46 pub main_align: Alignment = Alignment::Right,
50 pub show_wraps: bool = false,
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(Default, Clone, Copy, Debug)]
140pub struct LineNumbersOpts {
141 pub relative: bool = false,
145 pub align: Alignment = Alignment::Left,
149 pub main_align: Alignment = Alignment::Right,
153 pub show_wraps: bool = false,
157 pub on_the_right: bool = false,
161}
162
163impl LineNumbersOpts {
164 pub fn push_on(self, pa: &mut Pass, handle: &Handle) -> Handle<LineNumbers> {
166 let mut line_numbers = LineNumbers {
167 buffer: handle.clone(),
168 text: Text::default(),
169 relative: self.relative,
170 align: self.align,
171 main_align: self.main_align,
172 show_wraps: self.show_wraps,
173 };
174 line_numbers.text = line_numbers.form_text(pa);
175
176 let specs = PushSpecs {
177 side: if self.on_the_right {
178 Side::Right
179 } else {
180 Side::Left
181 },
182 ..
183 };
184
185 handle.push_outer_widget(pa, line_numbers, specs)
186 }
187}
188
189fn push_text(b: &mut Builder, line: usize, main: usize, is_wrapped: bool, opts: &LineNumbers) {
191 if (!is_wrapped || opts.show_wraps) && main != usize::MAX {
192 let num = if opts.relative {
193 if line != main {
194 line.abs_diff(main)
195 } else {
196 line + 1
197 }
198 } else {
199 line + 1
200 };
201 b.push(num);
202 }
203
204 b.push("\n");
205 b.push(form::DEFAULT_ID);
206}
207
208fn align(b: &mut Builder, alignment: Alignment) {
209 match alignment {
210 Alignment::Left => b.push(AlignLeft),
211 Alignment::Center => b.push(AlignCenter),
212 Alignment::Right => b.push(AlignRight),
213 }
214}