1use crate::{
2 ui::text_box::{
3 helper_enums::{Boundary, TextBoxEditKind},
4 TextBox,
5 },
6 util::{num_digits, spaces},
7};
8use portable_atomic::AtomicU64;
9use ratatui::{
10 buffer::Buffer,
11 layout::Rect,
12 style::Style,
13 text::{Line, Span, Text},
14 widgets::{Paragraph, Widget},
15};
16use std::{
17 borrow::Cow,
18 cmp::{self, Ordering},
19 collections::VecDeque,
20 iter,
21};
22use unicode_width::UnicodeWidthChar;
23
24#[derive(Debug, Clone)]
25pub struct CursorPos {
26 pub row: usize,
27 pub col: usize,
28 pub offset: usize,
29}
30
31impl CursorPos {
32 pub fn new(row: usize, col: usize, offset: usize) -> Self {
33 Self { row, col, offset }
34 }
35}
36
37#[derive(Clone, Debug)]
38pub struct TextBoxEdit {
39 kind: TextBoxEditKind,
40 before: CursorPos,
41 after: CursorPos,
42}
43
44impl TextBoxEdit {
45 pub fn new(kind: TextBoxEditKind, before: CursorPos, after: CursorPos) -> Self {
46 Self {
47 kind,
48 before,
49 after,
50 }
51 }
52
53 pub fn redo(&self, lines: &mut Vec<String>) {
54 self.kind.apply(lines, &self.before, &self.after);
55 }
56
57 pub fn undo(&self, lines: &mut Vec<String>) {
58 self.kind.invert().apply(lines, &self.after, &self.before); }
60
61 pub fn cursor_before(&self) -> (usize, usize) {
62 (self.before.row, self.before.col)
63 }
64
65 pub fn cursor_after(&self) -> (usize, usize) {
66 (self.after.row, self.after.col)
67 }
68}
69
70#[derive(Clone, Debug)]
71pub struct TextBoxHistory {
72 index: usize,
73 max_items: usize,
74 edits: VecDeque<TextBoxEdit>,
75}
76
77impl TextBoxHistory {
78 pub fn new(max_items: usize) -> Self {
79 Self {
80 index: 0,
81 max_items,
82 edits: VecDeque::new(),
83 }
84 }
85
86 pub fn push(&mut self, edit: TextBoxEdit) {
87 if self.max_items == 0 {
88 return;
89 }
90
91 if self.edits.len() == self.max_items {
92 self.edits.pop_front();
93 self.index = self.index.saturating_sub(1);
94 }
95
96 if self.index < self.edits.len() {
97 self.edits.truncate(self.index);
98 }
99
100 self.index += 1;
101 self.edits.push_back(edit);
102 }
103
104 pub fn redo(&mut self, lines: &mut Vec<String>) -> Option<(usize, usize)> {
105 if self.index == self.edits.len() {
106 return None;
107 }
108 let edit = &self.edits[self.index];
109 edit.redo(lines);
110 self.index += 1;
111 Some(edit.cursor_after())
112 }
113
114 pub fn undo(&mut self, lines: &mut Vec<String>) -> Option<(usize, usize)> {
115 self.index = self.index.checked_sub(1)?;
116 let edit = &self.edits[self.index];
117 edit.undo(lines);
118 Some(edit.cursor_before())
119 }
120
121 pub fn max_items(&self) -> usize {
122 self.max_items
123 }
124}
125
126#[derive(Default, Debug)]
127pub struct TextBoxViewport(AtomicU64);
128
129impl Clone for TextBoxViewport {
130 fn clone(&self) -> Self {
131 let u = self.0.load(std::sync::atomic::Ordering::Relaxed);
132 TextBoxViewport(AtomicU64::new(u))
133 }
134}
135
136impl TextBoxViewport {
137 pub fn scroll_top(&self) -> (u16, u16) {
138 let u = self.0.load(std::sync::atomic::Ordering::Relaxed);
139 ((u >> 16) as u16, u as u16)
140 }
141
142 pub fn rect(&self) -> (u16, u16, u16, u16) {
143 let u = self.0.load(std::sync::atomic::Ordering::Relaxed);
144 let width = (u >> 48) as u16;
145 let height = (u >> 32) as u16;
146 let row = (u >> 16) as u16;
147 let col = u as u16;
148 (row, col, width, height)
149 }
150
151 pub fn position(&self) -> (u16, u16, u16, u16) {
152 let (row_top, col_top, width, height) = self.rect();
153 let row_bottom = row_top.saturating_add(height).saturating_sub(1);
154 let col_bottom = col_top.saturating_add(width).saturating_sub(1);
155
156 (
157 row_top,
158 col_top,
159 cmp::max(row_top, row_bottom),
160 cmp::max(col_top, col_bottom),
161 )
162 }
163
164 fn store(&self, row: u16, col: u16, width: u16, height: u16) {
165 let u =
166 ((width as u64) << 48) | ((height as u64) << 32) | ((row as u64) << 16) | col as u64;
167 self.0.store(u, std::sync::atomic::Ordering::Relaxed);
168 }
169
170 pub fn scroll(&mut self, rows: i16, cols: i16) {
171 fn apply_scroll(pos: u16, delta: i16) -> u16 {
172 if delta >= 0 {
173 pos.saturating_add(delta as u16)
174 } else {
175 pos.saturating_sub(-delta as u16)
176 }
177 }
178
179 let u = self.0.get_mut();
180 let row = apply_scroll((*u >> 16) as u16, rows);
181 let col = apply_scroll(*u as u16, cols);
182 *u = (*u & 0xffff_ffff_0000_0000) | ((row as u64) << 16) | (col as u64);
183 }
184}
185
186pub struct TextBoxRenderer<'a>(&'a TextBox<'a>);
187
188impl<'a> TextBoxRenderer<'a> {
189 pub fn new(textarea: &'a TextBox<'a>) -> Self {
190 Self(textarea)
191 }
192
193 #[inline]
194 fn text(&self, top_row: usize, height: usize) -> Text<'a> {
195 let lines_len = self.0.lines().len();
196 let line_num_len = num_digits(lines_len);
197 let bottom_row = cmp::min(top_row + height, lines_len);
198 let mut lines = Vec::with_capacity(bottom_row - top_row);
199 for (i, line) in self.0.lines()[top_row..bottom_row].iter().enumerate() {
200 lines.push(
201 self.0
202 .get_formatted_line(line.as_str(), top_row + i, line_num_len),
203 );
204 }
205 Text::from(lines)
206 }
207}
208
209impl<'a> Widget for TextBoxRenderer<'a> {
210 fn render(self, area: Rect, buf: &mut Buffer) {
211 let Rect { width, height, .. } = if let Some(b) = self.0.block() {
212 b.inner(area)
213 } else {
214 area
215 };
216
217 fn next_scroll_top(prev_top: u16, cursor: u16, length: u16) -> u16 {
218 if cursor < prev_top {
219 cursor
220 } else if prev_top + length <= cursor {
221 cursor + 1 - length
222 } else {
223 prev_top
224 }
225 }
226
227 let cursor = self.0.cursor();
228 let (top_row, top_col) = self.0.viewport.scroll_top();
229 let top_row = next_scroll_top(top_row, cursor.0 as u16, height);
230 let top_col = next_scroll_top(top_col, cursor.1 as u16, width);
231
232 let (text, style) = if !self.0.placeholder.is_empty() && self.0.is_empty() {
233 let text = Text::from(self.0.placeholder.as_str());
234 (text, self.0.placeholder_style)
235 } else {
236 (self.text(top_row as usize, height as usize), self.0.style())
237 };
238
239 let mut text_area = area;
240 let mut inner = Paragraph::new(text)
241 .style(style)
242 .alignment(self.0.alignment());
243 if let Some(b) = self.0.block() {
244 text_area = b.inner(area);
245 b.clone().render(area, buf)
246 }
247 if top_col != 0 {
248 inner = inner.scroll((0, top_col));
249 }
250
251 self.0.viewport.store(top_row, top_col, width, height);
252
253 inner.render(text_area, buf);
254 }
255}
256
257pub struct TextLineFormatter<'a> {
258 line: &'a str,
259 spans: Vec<Span<'a>>,
260 boundaries: Vec<(Boundary, usize)>,
261 style_begin: Style,
262 cursor_at_end: bool,
263 cursor_style: Style,
264 tab_len: u8,
265 mask: Option<char>,
266 select_at_end: bool,
267 select_style: Style,
268}
269
270impl<'a> TextLineFormatter<'a> {
271 pub fn new(
272 line: &'a str,
273 cursor_style: Style,
274 tab_len: u8,
275 mask: Option<char>,
276 select_style: Style,
277 ) -> Self {
278 Self {
279 line,
280 spans: vec![],
281 boundaries: vec![],
282 style_begin: Style::default(),
283 cursor_at_end: false,
284 cursor_style,
285 tab_len,
286 mask,
287 select_at_end: false,
288 select_style,
289 }
290 }
291
292 pub fn line_number(&mut self, row: usize, line_num_len: u8, style: Style) {
293 let pad = spaces(line_num_len - num_digits(row + 1) + 1);
294 self.spans
295 .push(Span::styled(format!("{}{}) ", pad, row + 1), style));
296 }
297
298 pub fn cursor_line(&mut self, cursor_col: usize, style: Style) {
299 if let Some((start, c)) = self.line.char_indices().nth(cursor_col) {
300 self.boundaries
301 .push((Boundary::Cursor(self.cursor_style), start));
302 self.boundaries.push((Boundary::End, start + c.len_utf8()));
303 } else {
304 self.cursor_at_end = true;
305 }
306 self.style_begin = style;
307 }
308
309 pub fn selection(
310 &mut self,
311 current_row: usize,
312 start_row: usize,
313 start_off: usize,
314 end_row: usize,
315 end_off: usize,
316 ) {
317 let (start, end) = if current_row == start_row {
318 if start_row == end_row {
319 (start_off, end_off)
320 } else {
321 self.select_at_end = true;
322 (start_off, self.line.len())
323 }
324 } else if current_row == end_row {
325 (0, end_off)
326 } else if start_row < current_row && current_row < end_row {
327 self.select_at_end = true;
328 (0, self.line.len())
329 } else {
330 return;
331 };
332 if start != end {
333 self.boundaries
334 .push((Boundary::Select(self.select_style), start));
335 self.boundaries.push((Boundary::End, end));
336 }
337 }
338
339 pub fn into_line(self) -> Line<'a> {
340 let Self {
341 line,
342 mut spans,
343 mut boundaries,
344 tab_len,
345 style_begin,
346 cursor_style,
347 cursor_at_end,
348 mask,
349 select_at_end,
350 select_style,
351 } = self;
352 let mut builder = DisplayTextBuilder::new(tab_len, mask);
353
354 if boundaries.is_empty() {
355 let built = builder.build(line);
356 if !built.is_empty() {
357 spans.push(Span::styled(built, style_begin));
358 }
359 if cursor_at_end {
360 spans.push(Span::styled(" ", cursor_style));
361 } else if select_at_end {
362 spans.push(Span::styled(" ", select_style));
363 }
364 return Line::from(spans);
365 }
366
367 boundaries.sort_unstable_by(|(l, i), (r, j)| match i.cmp(j) {
368 Ordering::Equal => l.cmp(r),
369 o => o,
370 });
371
372 let mut style = style_begin;
373 let mut start = 0;
374 let mut stack = vec![];
375
376 for (next_boundary, end) in boundaries {
377 if start < end {
378 spans.push(Span::styled(builder.build(&line[start..end]), style));
379 }
380
381 style = if let Some(s) = next_boundary.style() {
382 stack.push(style);
383 s
384 } else {
385 stack.pop().unwrap_or(style_begin)
386 };
387 start = end;
388 }
389
390 if start != line.len() {
391 spans.push(Span::styled(builder.build(&line[start..]), style));
392 }
393
394 if cursor_at_end {
395 spans.push(Span::styled(" ", cursor_style));
396 } else if select_at_end {
397 spans.push(Span::styled(" ", select_style));
398 }
399
400 Line::from(spans)
401 }
402}
403
404struct DisplayTextBuilder {
405 tab_len: u8,
406 width: usize,
407 mask: Option<char>,
408}
409
410impl DisplayTextBuilder {
411 fn new(tab_len: u8, mask: Option<char>) -> Self {
412 Self {
413 tab_len,
414 width: 0,
415 mask,
416 }
417 }
418
419 fn build<'s>(&mut self, s: &'s str) -> Cow<'s, str> {
420 if let Some(ch) = self.mask {
421 let masked = iter::repeat(ch).take(s.chars().count()).collect();
423 return Cow::Owned(masked);
424 }
425
426 let tab = spaces(self.tab_len);
427 let mut buf = String::new();
428 for (i, c) in s.char_indices() {
429 if c == '\t' {
430 if buf.is_empty() {
431 buf.reserve(s.len());
432 buf.push_str(&s[..i]);
433 }
434 if self.tab_len > 0 {
435 let len = self.tab_len as usize - (self.width % self.tab_len as usize);
436 buf.push_str(&tab[..len]);
437 self.width += len;
438 }
439 } else {
440 if !buf.is_empty() {
441 buf.push(c);
442 }
443 self.width += c.width().unwrap_or(0);
444 }
445 }
446
447 if !buf.is_empty() {
448 Cow::Owned(buf)
449 } else {
450 Cow::Borrowed(s)
451 }
452 }
453}