1use std::{fmt::Alignment, sync::Once};
14
15use duat_core::{
16 buffer::Buffer,
17 context::Handle,
18 data::Pass,
19 form,
20 hook::{self, BufferUpdated, OnMouseEvent},
21 mode::{MouseButton, MouseEventKind},
22 text::{Builder, Spacer, Text, TextMut},
23 ui::{PushSpecs, Side, Widget},
24};
25
26pub struct LineNumbers {
39 text: Text,
40 pub relative: bool,
44 pub align: Alignment,
48 pub main_align: Alignment,
52 pub show_wraps: bool,
56}
57
58impl LineNumbers {
59 pub fn builder() -> LineNumbersOpts {
62 LineNumbersOpts::default()
63 }
64
65 fn calculate_width(&self, pa: &Pass, buffer: &Handle) -> f32 {
67 let len = buffer.read(pa).text().end_point().line();
68 len.ilog10() as f32
69 }
70
71 fn form_text(&self, pa: &Pass, buffer: &Handle) -> Text {
72 let (main_line_num, printed_line_numbers) = {
73 let printed_line_numbers = buffer.printed_line_numbers(pa);
74 let buf = buffer.read(pa);
75
76 let main_line = if buf.selections().is_empty() {
77 usize::MAX
78 } else {
79 buf.selections().main().caret().line()
80 };
81
82 (main_line, printed_line_numbers)
83 };
84
85 let mut builder = Text::builder();
86 let mut last_was_ghost = false;
87
88 for (idx, line) in printed_line_numbers.iter().enumerate() {
89 if line.is_ghost {
90 last_was_ghost = true;
91 builder.push("\n");
92 continue;
93 }
94
95 let align = if line.number == main_line_num {
96 self.main_align
97 } else {
98 self.align
99 };
100
101 if align != Alignment::Left {
102 builder.push(Spacer);
103 }
104
105 let is_wrapped = line.is_wrapped && idx > 0 && !last_was_ghost;
106 match (line.number == main_line_num, is_wrapped) {
107 (false, false) => {}
108 (true, false) => builder.push(form::id_of!("linenum.main")),
109 (false, true) => builder.push(form::id_of!("linenum.wrapped")),
110 (true, true) => builder.push(form::id_of!("linenum.wrapped.main")),
111 }
112
113 push_text(&mut builder, line.number, main_line_num, is_wrapped, self);
114
115 if align == Alignment::Center {
116 builder.push(Spacer);
117 }
118
119 builder.push("\n");
120 builder.push(form::DEFAULT_ID);
121 last_was_ghost = false;
122 }
123
124 builder.build()
125 }
126}
127
128impl Widget for LineNumbers {
129 fn text(&self) -> &Text {
130 &self.text
131 }
132
133 fn text_mut(&mut self) -> TextMut<'_> {
134 self.text.as_mut()
135 }
136}
137
138#[derive(Clone, Copy, Debug)]
149pub struct LineNumbersOpts {
150 pub relative: bool,
154 pub align: Alignment,
158 pub main_align: Alignment,
162 pub show_wraps: bool,
166 pub on_the_right: bool,
170}
171
172impl LineNumbersOpts {
173 pub const fn new() -> Self {
175 Self {
176 relative: false,
177 align: Alignment::Left,
178 main_align: Alignment::Right,
179 show_wraps: false,
180 on_the_right: false,
181 }
182 }
183
184 pub fn push_on(self, pa: &mut Pass, buffer: &Handle) -> Handle<LineNumbers> {
190 static ONCE: Once = Once::new();
191 ONCE.call_once(|| {
192 hook::add::<BufferUpdated>(|pa, buffer| {
193 for (linenumbers, _) in buffer.get_related::<LineNumbers>(pa) {
194 let width = linenumbers.read(pa).calculate_width(pa, buffer);
195 linenumbers.area().set_width(pa, width + 1.0).unwrap();
196
197 linenumbers.write(pa).text = linenumbers.read(pa).form_text(pa, buffer);
198 }
199 })
200 .lateness(usize::MAX);
201
202 hook::add::<OnMouseEvent<LineNumbers>>(|pa, event| {
203 let line = |pa, handle: &Handle| {
204 let lines = handle.printed_line_numbers(pa);
205 event
206 .points
207 .and_then(|tpp| lines.get(tpp.points().real.line()))
208 .map(|line| line.number)
209 .unwrap_or(handle.text(pa).end_point().line())
210 };
211
212 let (buffer, _) = event.handle.get_related::<Buffer>(pa).remove(0);
213
214 match event.kind {
215 MouseEventKind::Down(MouseButton::Left) => {
216 let line = line(pa, &buffer);
217
218 buffer.selections_mut(pa).remove_extras();
219 buffer.edit_main(pa, |mut c| {
220 c.unset_anchor();
221 c.move_to_coords(line, 0)
222 })
223 }
224 MouseEventKind::Drag(MouseButton::Left) => {
225 let line = line(pa, &buffer);
226
227 buffer.selections_mut(pa).remove_extras();
228 buffer.edit_main(pa, |mut c| {
229 c.set_anchor_if_needed();
230 c.move_to_coords(line, 0)
231 })
232 }
233 MouseEventKind::ScrollDown => {
234 let opts = buffer.opts(pa);
235 let (buf, area) = buffer.write_with_area(pa);
236
237 area.scroll_ver(buf.text(), 3, opts);
238 }
239 MouseEventKind::ScrollUp => {
240 let opts = buffer.opts(pa);
241 let (buf, area) = buffer.write_with_area(pa);
242
243 area.scroll_ver(buf.text(), -3, opts);
244 }
245 _ => {}
246 }
247 });
248 });
249
250 let mut linenumbers = LineNumbers {
251 text: Text::default(),
252 relative: self.relative,
253 align: self.align,
254 main_align: self.main_align,
255 show_wraps: self.show_wraps,
256 };
257 linenumbers.text = linenumbers.form_text(pa, buffer);
258
259 let specs = PushSpecs {
260 side: if self.on_the_right {
261 Side::Right
262 } else {
263 Side::Left
264 },
265 ..Default::default()
266 };
267
268 let linenumbers = buffer.push_outer_widget(pa, linenumbers, specs);
269
270 let width = linenumbers.read(pa).calculate_width(pa, buffer);
271 linenumbers.area().set_width(pa, width + 1.0).unwrap();
272
273 linenumbers
274 }
275}
276
277impl Default for LineNumbersOpts {
278 fn default() -> Self {
279 Self::new()
280 }
281}
282
283fn push_text(b: &mut Builder, line: usize, main: usize, is_wrapped: bool, opts: &LineNumbers) {
285 if (!is_wrapped || opts.show_wraps) && main != usize::MAX {
286 b.push(if opts.relative {
287 if line != main {
288 line.abs_diff(main)
289 } else {
290 line + 1
291 }
292 } else {
293 line + 1
294 });
295 }
296}