duat_base/widgets/
line_numbers.rs1use std::fmt::Alignment;
14
15use duat_core::{
16 context::Handle,
17 data::Pass,
18 form,
19 mode::{MouseButton, MouseEvent, MouseEventKind},
20 text::{Builder, Spacer, Text, TextMut},
21 ui::{PushSpecs, Side, Widget},
22};
23
24pub struct LineNumbers {
37 buffer: Handle,
38 text: Text,
39 pub relative: bool,
43 pub align: Alignment,
47 pub main_align: Alignment,
51 pub show_wraps: bool,
55}
56
57impl LineNumbers {
58 pub fn builder() -> LineNumbersOpts {
61 LineNumbersOpts::default()
62 }
63
64 fn calculate_width(&self, pa: &Pass) -> f32 {
66 let len = self.buffer.read(pa).text().len().line();
67 len.ilog10() as f32
68 }
69
70 fn form_text(&self, pa: &Pass) -> Text {
71 let (main_line_num, printed_line_numbers) = {
72 let printed_line_numbers = self.buffer.printed_line_numbers(pa);
73 let buf = self.buffer.read(pa);
74
75 let main_line = if buf.selections().is_empty() {
76 usize::MAX
77 } else {
78 buf.selections().main().caret().line()
79 };
80
81 (main_line, printed_line_numbers)
82 };
83
84 let mut builder = Text::builder();
85
86 for (index, line) in printed_line_numbers.iter().enumerate() {
87 let align = if line.number == main_line_num {
88 self.main_align
89 } else {
90 self.align
91 };
92
93 if align != Alignment::Left {
94 builder.push(Spacer);
95 }
96
97 match (line.number == main_line_num, line.is_wrapped) {
98 (false, false) => {}
99 (true, false) => builder.push(form::id_of!("linenum.main")),
100 (false, true) => builder.push(form::id_of!("linenum.wrapped")),
101 (true, true) => builder.push(form::id_of!("linenum.wrapped.main")),
102 }
103
104 let is_wrapped = line.is_wrapped && index > 0;
105 push_text(&mut builder, line.number, main_line_num, is_wrapped, self);
106
107 if align == Alignment::Center {
108 builder.push(Spacer);
109 }
110
111 builder.push("\n");
112 builder.push(form::DEFAULT_ID);
113 }
114
115 builder.build()
116 }
117}
118
119impl Widget for LineNumbers {
120 fn update(pa: &mut Pass, handle: &Handle<Self>) {
121 let width = handle.read(pa).calculate_width(pa);
122 handle.area().set_width(pa, width + 1.0).unwrap();
123
124 handle.write(pa).text = handle.read(pa).form_text(pa);
125 }
126
127 fn needs_update(&self, pa: &Pass) -> bool {
128 self.buffer.has_changed(pa)
129 }
130
131 fn text(&self) -> &Text {
132 &self.text
133 }
134
135 fn text_mut(&mut self) -> TextMut<'_> {
136 self.text.as_mut()
137 }
138
139 fn on_mouse_event(pa: &mut Pass, handle: &Handle<Self>, event: MouseEvent) {
140 let line = |pa, handle: &Handle| {
141 let lines = handle.printed_line_numbers(pa);
142 event
143 .points
144 .and_then(|tpp| lines.get(tpp.points().real.line()))
145 .map(|line| line.number)
146 .unwrap_or(handle.text(pa).len().line())
147 };
148 match event.kind {
149 MouseEventKind::Down(MouseButton::Left) => {
150 let line = line(pa, &handle.read(pa).buffer);
151 let handle = handle.read(pa).buffer.clone();
152
153 handle.selections_mut(pa).remove_extras();
154 handle.edit_main(pa, |mut c| {
155 c.unset_anchor();
156 c.move_to_coords(line, 0)
157 })
158 }
159 MouseEventKind::Drag(MouseButton::Left) => {
160 let line = line(pa, &handle.read(pa).buffer);
161 let handle = handle.read(pa).buffer.clone();
162
163 handle.selections_mut(pa).remove_extras();
164 handle.edit_main(pa, |mut c| {
165 c.set_anchor_if_needed();
166 c.move_to_coords(line, 0)
167 })
168 }
169 MouseEventKind::ScrollDown => {
170 let handle = handle.read(pa).buffer.clone();
171 let opts = handle.opts(pa);
172 let (buffer, area) = handle.write_with_area(pa);
173
174 area.scroll_ver(buffer.text(), 3, opts);
175 }
176 MouseEventKind::ScrollUp => {
177 let handle = handle.read(pa).buffer.clone();
178 let opts = handle.opts(pa);
179 let (buffer, area) = handle.write_with_area(pa);
180
181 area.scroll_ver(buffer.text(), -3, opts);
182 }
183 _ => {}
184 }
185 }
186}
187
188#[derive(Clone, Copy, Debug)]
199pub struct LineNumbersOpts {
200 pub relative: bool,
204 pub align: Alignment,
208 pub main_align: Alignment,
212 pub show_wraps: bool,
216 pub on_the_right: bool,
220}
221
222impl LineNumbersOpts {
223 pub const fn new() -> Self {
225 Self {
226 relative: false,
227 align: Alignment::Left,
228 main_align: Alignment::Right,
229 show_wraps: false,
230 on_the_right: false,
231 }
232 }
233
234 pub fn push_on(self, pa: &mut Pass, handle: &Handle) -> Handle<LineNumbers> {
236 let mut line_numbers = LineNumbers {
237 buffer: handle.clone(),
238 text: Text::default(),
239 relative: self.relative,
240 align: self.align,
241 main_align: self.main_align,
242 show_wraps: self.show_wraps,
243 };
244 line_numbers.text = line_numbers.form_text(pa);
245
246 let specs = PushSpecs {
247 side: if self.on_the_right {
248 Side::Right
249 } else {
250 Side::Left
251 },
252 ..Default::default()
253 };
254
255 handle.push_outer_widget(pa, line_numbers, specs)
256 }
257}
258
259impl Default for LineNumbersOpts {
260 fn default() -> Self {
261 Self::new()
262 }
263}
264
265fn push_text(b: &mut Builder, line: usize, main: usize, is_wrapped: bool, opts: &LineNumbers) {
267 if (!is_wrapped || opts.show_wraps) && main != usize::MAX {
268 b.push(if opts.relative {
269 if line != main {
270 line.abs_diff(main)
271 } else {
272 line + 1
273 }
274 } else {
275 line + 1
276 });
277 }
278}