Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_text/src/lines.rs

15.9 KiB, 5 runs

created by r1870400018:1103, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1use crate::{
2 Text,
3 highlight::{
4 Highlight,
5 Highlighter,
6 },
7};
8
9use oxedyne_fe2o3_core::prelude::*;
10use oxedyne_fe2o3_geom::{
11 dim::{
12 Coord,
13 Dim,
14 Span,
15 },
16 rect::{
17 AbsRect,
18 AbsSize,
19 },
20};
21
22use std::{
23 collections::BinaryHeap,
24 fmt,
25};
26
27
28#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
29pub struct LineRange {
30 pub line: Dim,
31 pub span: Span,
32}
33
34impl LineRange {
35
36 pub fn new<D: Into<Dim>>(
37 line: D,
38 (start, len): (D, D),
39 )
40 -> Self
41 {
42 Self {
43 line: line.into(),
44 span: Span::from((start.into(), len.into())),
45 }
46 }
47
48 pub fn to_abs_rect(&self) -> AbsRect {
49 AbsRect {
50 top_left: Coord::new((self.span.start(), self.line)),
51 size: AbsSize::new((self.span.len(), Dim(1))),
52 }
53 }
54}
55
56#[derive(Clone, Debug, Default)]
57pub struct TextLines<
58 T: Clone + fmt::Debug + Default,
59 D: Clone + fmt::Debug + Default,
60> {
61 pub lines: Vec<Text<T>>,
62 pub widths: BinaryHeap<usize>,
63 pub changed: bool,
64 pub highlighter: Option<Highlighter<D>>,
65}
66
67impl <
68 T: Clone + fmt::Debug + Default,
69 D: Clone + fmt::Debug + Default,
70 I: IntoIterator<Item = Text<T>>
71>
72 From<I> for TextLines<T, D>
73{
74 fn from(lines: I) -> Self {
75 let mut text_lines = TextLines {
76 lines: Vec::new(),
77 widths: BinaryHeap::new(),
78 ..Default::default()
79 };
80 for line in lines {
81 let width = line.len();
82 text_lines.lines.push(line);
83 text_lines.widths.push(width);
84 }
85 text_lines
86 }
87}
88
89impl<
90 T: Clone + fmt::Debug + Default,
91 D: Clone + fmt::Debug + Default,
92>
93 TextLines<T, D>
94{
95 pub fn new(
96 lines: Vec<Text<T>>,
97 highlighter: Option<Highlighter<D>>,
98 )
99 -> Self
100 {
101 let mut widths = BinaryHeap::new();
102 for line in &lines {
103 let width = line.len();
104 widths.push(width);
105 }
106
107 Self {
108 lines,
109 widths,
110 highlighter,
111 ..Default::default()
112 }
113 }
114
115 pub fn max_width(&self) -> usize {
116 self.widths.peek().copied().unwrap_or(0)
117 }
118
119 pub fn len(&self) -> usize {
120 self.lines.len()
121 }
122
123 pub fn is_empty(&self) -> bool {
124 self.lines.is_empty()
125 }
126
127 pub fn get_highlighter(&self) -> Option<&Highlighter<D>> {
128 self.highlighter.as_ref()
129 }
130
131 pub fn get_highlighter_mut(&mut self) -> Option<&mut Highlighter<D>> {
132 self.highlighter.as_mut()
133 }
134
135 pub fn set_highlighter(&mut self, highlighter: Option<Highlighter<D>>) {
136 self.highlighter = highlighter;
137 }
138
139 pub fn has_changed(&self) -> bool {
140 self.changed
141 }
142
143 pub fn set_changed(&mut self, changed: bool) {
144 self.changed = changed;
145 }
146
147 pub fn last(&self) -> Option<&Text<T>> {
148 if !self.is_empty() {
149 Some(&self.lines[self.lines.len() - 1])
150 } else {
151 None
152 }
153 }
154
155 /// Get type of last line, if it exists.
156 pub fn type_of_last(&self) -> Option<T> {
157 if let Some(last) = self.last() {
158 Some(last.typ().clone())
159 } else {
160 None
161 }
162 }
163
164 pub fn size(&self) -> AbsSize {
165 AbsSize::from((self.max_width(), self.len()))
166 }
167
168 pub fn append_string(&mut self, s: String) {
169 if s.len() > 0 {
170 for line in s.lines() {
171 let txt = Text::new(line, None);
172 let width = txt.len();
173 self.lines.push(txt);
174 self.widths.push(width);
175 }
176 self.changed = true;
177 }
178 }
179
180 /// Append a new line of text. If an associated highlight is included, it is assumed to apply
181 /// to the new line, and so the enclosed line value is updated.
182 pub fn append_text_line(
183 &mut self,
184 txt: Text<T>,
185 highlight: Option<Highlight<D>>,
186 ) {
187 if !txt.is_empty() {
188 let width = txt.len();
189 self.lines.push(txt);
190 self.widths.push(width);
191 if let Some(mut new_highlight) = highlight {
192 new_highlight.range.line = Dim::new(self.len() - 1);
193 match &mut self.highlighter {
194 Some(highlighter) => {
195 highlighter.ranges.push(new_highlight);
196 }
197 None => {
198 self.highlighter = Some(Highlighter::new(vec![new_highlight], None));
199 }
200 }
201 }
202 self.changed = true;
203 }
204 }
205
206 /// Append a new set of lines to the current `TextLines`. The line numbers of the associated
207 /// highlights in the `TextLines` to be appended are adjusted.
208 pub fn append_text_lines(
209 &mut self,
210 text_lines: Self,
211 ) {
212 let orig_len = self.len();
213 if text_lines.len() > 0 {
214 for line in text_lines.lines {
215 self.append_text_line(line, None);
216 }
217 }
218 if let Some(mut new_highlighter) = text_lines.highlighter {
219 new_highlighter.ranges.iter_mut().for_each(|highlight| {
220 highlight.range.line += orig_len;
221 });
222 match &mut self.highlighter {
223 Some(highlighter) => {
224 for new_highlight in new_highlighter.ranges {
225 highlighter.ranges.push(new_highlight);
226 }
227 }
228 None => {
229 self.highlighter = Some(Highlighter::new(new_highlighter.ranges, None));
230 }
231 }
232 }
233 }
234
235 /// Insert a new line of text after the given line. If an associated highlight is included, it
236 /// is assumed to apply to the new line, and so the enclosed line value is updated. If the
237 /// insertion line number exceeds the current length, the new text is simply appended.
238 pub fn insert_text_line(
239 &mut self,
240 y: usize,
241 txt: Text<T>,
242 highlight: Option<Highlight<D>>,
243 ) {
244 let width = txt.len();
245 if y < self.lines.len() {
246 // Insert a new line.
247 self.lines.insert(y + 1, txt);
248 self.inc_highlight_lines(y + 1);
249 } else {
250 // Append a new line.
251 self.lines.push(txt);
252 }
253 self.widths.push(width);
254 if let Some(mut new_highlight) = highlight {
255 new_highlight.range.line = Dim::new(y + 1);
256 match &mut self.highlighter {
257 Some(highlighter) => {
258 highlighter.insert(new_highlight);
259 }
260 None => {
261 self.highlighter = Some(Highlighter::new(vec![new_highlight], None));
262 }
263 }
264 }
265 }
266
267 /// Delete any highlight that encloses the given coordinates `(x, y)`.
268 pub fn delete_highlight_enclosing(&mut self, (x, y): (Dim, Dim)) {
269 if let Some(highlighter) = &mut self.highlighter {
270 highlighter.delete_highlight_enclosing((x, y));
271 }
272 }
273
274 /// Delete any highlight on the given line `y`.
275 pub fn delete_highlight_line(&mut self, y: usize) {
276 if let Some(highlighter) = &mut self.highlighter {
277 highlighter.delete_highlight_line(y);
278 }
279 }
280
281 /// Increment the line number for all highlights for which the current line number is equal to
282 /// or greater than the given `y`.
283 pub fn inc_highlight_lines(&mut self, y: usize) {
284 if let Some(highlighter) = &mut self.highlighter {
285 highlighter.inc_highlight_lines(y);
286 }
287 }
288
289 /// Decrement the line number for all highlights for which the current line number is equal to
290 /// or less than the given `y`.
291 pub fn dec_highlight_lines(&mut self, y: usize) {
292 if let Some(highlighter) = &mut self.highlighter {
293 highlighter.dec_highlight_lines(y);
294 }
295 }
296
297 pub fn add_char(&mut self, cursor: &mut Coord, c: char, replace: bool) {
298 let (x, y) = cursor.tup();
299 if y < self.lines.len() {
300 let width = self.lines[y.as_index()].txt.chars().count();
301
302 self.delete_highlight_enclosing((x, y));
303
304 if x <= width {
305 let mut chars: Vec<char> = self.lines[y.as_index()].txt.chars().collect();
306 if replace {
307 chars[x.as_index()] = c;
308 } else {
309 chars.insert(x.as_index(), c);
310 }
311 let new_line: String = chars.into_iter().collect();
312 self.remove_width(width);
313 let new_width = new_line.chars().count();
314 self.widths.push(new_width);
315 self.lines[y.as_index()].txt = new_line;
316 } else {
317 // End of line.
318 self.lines[y.as_index()].txt.push(c);
319 self.remove_width(width);
320 let new_width = width + 1;
321 self.widths.push(new_width);
322 }
323 cursor.inc_x(Dim(1));
324
325 } else {
326 // New line.
327 let mut line = String::new();
328 line.push(c);
329 let new_width = 1;
330 self.widths.push(new_width);
331 self.lines.push(Text::new(line, self.type_of_last()));
332 cursor.y = Dim::new(self.lines.len() - 1);
333 cursor.x = Dim(0);
334 }
335 }
336
337 pub fn add_str(&mut self, cursor: &mut Coord, s: &str, replace: bool) {
338 let (x, y) = cursor.tup();
339 if y < self.lines.len() {
340 let width = self.lines[y.as_index()].txt.chars().count();
341
342 self.delete_highlight_enclosing((x, y));
343
344 if x <= width {
345 // Insert into current line.
346 let mut chars: Vec<char> = self.lines[y.as_index()].txt.chars().collect();
347 if replace {
348 chars.splice(x.as_index()..(x + s.chars().count()).as_index(), s.chars());
349 } else {
350 for (i, c) in s.chars().enumerate() {
351 chars.insert(x.as_index() + i, c);
352 }
353 }
354 let new_line: String = chars.into_iter().collect();
355 self.remove_width(width);
356 let new_width = new_line.chars().count();
357 self.widths.push(new_width);
358 self.lines[y.as_index()].txt = new_line;
359 } else {
360 // End of line.
361 self.lines[y.as_index()].txt.push_str(s);
362 self.remove_width(width);
363 let new_width = width + s.chars().count();
364 self.widths.push(new_width);
365 }
366 cursor.x += s.chars().count();
367
368 } else {
369 // The string starts a newly appended line.
370 let mut line = String::new();
371 line.push_str(s);
372 let new_width = s.chars().count();
373 self.widths.push(new_width);
374 self.lines.push(Text::new(line, self.type_of_last()));
375 cursor.y = Dim::new(self.lines.len() - 1);
376 cursor.x = Dim::new(s.chars().count());
377 }
378 }
379
380 pub fn backspace(&mut self, cursor: &mut Coord) {
381 let (x, y) = cursor.tup();
382 if y < self.lines.len() {
383 let width = self.lines[y.as_index()].txt.chars().count();
384
385 self.delete_highlight_enclosing((x - 1, y));
386
387 if x > Dim(0) {
388 // Shorten current line.
389 let mut chars: Vec<char> = self.lines[y.as_index()].txt.chars().collect();
390 chars.remove((x - 1).as_index());
391 let new_line: String = chars.into_iter().collect();
392 self.remove_width(width);
393 let new_width = new_line.chars().count();
394 self.widths.push(new_width);
395 self.lines[y.as_index()].txt = new_line;
396 cursor.dec_x(Dim(1));
397 } else if y > Dim(0) {
398 // Join current line with previous line.
399 let prev_line_width = self.lines[(y - 1).as_index()].txt.chars().count();
400 let current_line = self.lines.remove(y.as_index());
401 self.remove_width(width);
402
403 self.lines[(y - 1).as_index()].txt.push_str(&current_line.txt);
404 self.remove_width(prev_line_width);
405 let new_width = self.lines[(y - 1).as_index()].txt.chars().count();
406 self.widths.push(new_width);
407 self.dec_highlight_lines(y.as_index());
408 cursor.y -= 1;
409 cursor.x = Dim::new(prev_line_width);
410 }
411 }
412 }
413
414 pub fn delete_char(&mut self, cursor: &mut Coord) {
415 let (x, y) = cursor.tup();
416 if y < self.lines.len() {
417 let width = self.lines[y.as_index()].txt.chars().count();
418
419 self.delete_highlight_enclosing((x, y));
420
421 if x < width {
422 // Pull remainder of current line to the left.
423 let mut chars: Vec<char> = self.lines[y.as_index()].txt.chars().collect();
424 chars.remove(x.as_index());
425 let new_line: String = chars.into_iter().collect();
426 self.remove_width(width);
427 let new_width = new_line.chars().count();
428 self.widths.push(new_width);
429 self.lines[y.as_index()].txt = new_line;
430 } else if y < self.lines.len() - 1 {
431 // Join current line with next line.
432 let next_line = self.lines.remove((y + 1).as_index());
433 self.remove_width(width);
434 self.remove_width(next_line.txt.chars().count());
435
436 self.lines[y.as_index()].txt.push_str(&next_line.txt);
437 let new_width = self.lines[y.as_index()].txt.chars().count();
438 self.widths.push(new_width);
439 self.dec_highlight_lines((y + 1).as_index());
440 }
441 }
442 }
443
444 /// Insert a new line after the line specified by the given coordinates.
445 pub fn enter_new_line(&mut self, cursor: &mut Coord) {
446 let (x, y) = cursor.tup();
447 if y < self.lines.len() {
448 let width = self.lines[y.as_index()].txt.chars().count();
449
450 if x < width {
451 let typ = self.lines[y.as_index()].typ.clone();
452 // Split the line at the cursor position.
453 let left = self.lines[y.as_index()].txt[..x.as_index()].to_string();
454 let right = self.lines[y.as_index()].txt[x.as_index()..].to_string();
455 let left_width = left.chars().count();
456 let right_width = right.chars().count();
457
458 self.remove_width(width);
459 self.widths.push(left_width);
460 self.widths.push(right_width);
461
462 self.lines[y.as_index()].txt = left.to_string();
463 self.lines.insert(
464 (y + 1).as_index(),
465 Text::new(right.to_string(), Some(typ)),
466 );
467 self.delete_highlight_enclosing((x, y));
468 self.inc_highlight_lines((y + 1).as_index());
469 } else {
470 // Insert a new line.
471 self.lines.insert((y + 1).as_index(), Text::new("", self.type_of_last()));
472 self.widths.push(0);
473 }
474 } else {
475 // Append a new line.
476 self.lines.push(Text::new("", self.type_of_last()));
477 self.widths.push(0);
478 }
479 cursor.y += 1;
480 cursor.x = Dim(0);
481 }
482
483 pub fn remove_line(&mut self, y: usize) {
484 if y < self.lines.len() {
485 let removed_width = self.lines[y].txt.chars().count();
486 self.lines.remove(y);
487 self.remove_width(removed_width);
488 self.delete_highlight_line(y);
489 self.dec_highlight_lines(y);
490 }
491 }
492
493 pub fn remove_width(&mut self, width: usize) {
494 self.widths.retain(|&w| w != width);
495 }
496}