1mod borders_config;
4mod entity_map;
5
6use std::collections::HashMap;
7
8use crate::ansi::{ANSIBuf, ANSIStr};
9use crate::config::compact::CompactConfig;
10use crate::config::{
11 AlignmentHorizontal, AlignmentVertical, Border, Borders, Entity, Indent, Offset, Position,
12 Sides,
13};
14use borders_config::BordersConfig;
15
16pub use self::entity_map::EntityMap;
17
18use super::Formatting;
19
20type HorizontalLine = super::HorizontalLine<char>;
22
23type VerticalLine = super::VerticalLine<char>;
25
26#[derive(Debug, PartialEq, Eq, Clone)]
30pub struct SpannedConfig {
31 margin: Sides<MarginIndent>,
32 padding: EntityMap<Sides<Indent>>,
33 padding_color: EntityMap<Sides<Option<ANSIBuf>>>,
34 alignment_h: EntityMap<AlignmentHorizontal>,
35 alignment_v: EntityMap<AlignmentVertical>,
36 formatting_trim_h: EntityMap<bool>,
37 formatting_trim_v: EntityMap<bool>,
38 formatting_line_alignment: EntityMap<bool>,
39 span_columns: HashMap<Position, usize>,
40 span_rows: HashMap<Position, usize>,
41 borders: BordersConfig<char>,
42 borders_colors: BordersConfig<ANSIBuf>,
43 borders_missing_char: char,
44 horizontal_chars: HashMap<Position, HashMap<Offset, char>>,
45 horizontal_colors: HashMap<Position, HashMap<Offset, ANSIBuf>>, vertical_chars: HashMap<Position, HashMap<Offset, char>>,
47 vertical_colors: HashMap<Position, HashMap<Offset, ANSIBuf>>,
48 justification: EntityMap<char>,
49 justification_color: EntityMap<Option<ANSIBuf>>,
50}
51
52impl Default for SpannedConfig {
53 fn default() -> Self {
54 Self {
55 margin: Sides::default(),
56 padding: EntityMap::default(),
57 padding_color: EntityMap::default(),
58 formatting_trim_h: EntityMap::default(),
59 formatting_trim_v: EntityMap::default(),
60 formatting_line_alignment: EntityMap::default(),
61 alignment_h: EntityMap::new(AlignmentHorizontal::Left),
62 alignment_v: EntityMap::new(AlignmentVertical::Top),
63 span_columns: HashMap::default(),
64 span_rows: HashMap::default(),
65 borders: BordersConfig::default(),
66 borders_colors: BordersConfig::default(),
67 borders_missing_char: ' ',
68 horizontal_chars: HashMap::default(),
69 horizontal_colors: HashMap::default(),
70 vertical_chars: HashMap::default(),
71 vertical_colors: HashMap::default(),
72 justification: EntityMap::new(' '),
73 justification_color: EntityMap::default(),
74 }
75 }
76}
77
78impl SpannedConfig {
79 pub fn new() -> Self {
81 Self::default()
82 }
83
84 pub fn set_margin(&mut self, margin: Sides<Indent>) {
86 self.margin.left.indent = margin.left;
87 self.margin.right.indent = margin.right;
88 self.margin.top.indent = margin.top;
89 self.margin.bottom.indent = margin.bottom;
90 }
91
92 pub fn set_margin_color(&mut self, margin: Sides<Option<ANSIBuf>>) {
94 self.margin.left.color = margin.left;
95 self.margin.right.color = margin.right;
96 self.margin.top.color = margin.top;
97 self.margin.bottom.color = margin.bottom;
98 }
99
100 pub fn set_margin_offset(&mut self, margin: Sides<Offset>) {
102 self.margin.left.offset = margin.left;
103 self.margin.right.offset = margin.right;
104 self.margin.top.offset = margin.top;
105 self.margin.bottom.offset = margin.bottom;
106 }
107
108 pub fn get_margin(&self) -> Sides<Indent> {
110 Sides::new(
111 self.margin.left.indent,
112 self.margin.right.indent,
113 self.margin.top.indent,
114 self.margin.bottom.indent,
115 )
116 }
117
118 pub fn get_margin_color(&self) -> Sides<Option<&ANSIBuf>> {
120 Sides::new(
121 self.margin.left.color.as_ref(),
122 self.margin.right.color.as_ref(),
123 self.margin.top.color.as_ref(),
124 self.margin.bottom.color.as_ref(),
125 )
126 }
127
128 pub fn get_margin_offset(&self) -> Sides<Offset> {
130 Sides::new(
131 self.margin.left.offset,
132 self.margin.right.offset,
133 self.margin.top.offset,
134 self.margin.bottom.offset,
135 )
136 }
137
138 pub fn remove_borders(&mut self) {
140 self.borders = BordersConfig::default();
141 }
142
143 pub fn remove_borders_colors(&mut self) {
145 self.borders_colors = BordersConfig::default();
146 }
147
148 pub fn remove_color_line_horizontal(&mut self) {
150 self.horizontal_colors.clear();
151 }
152
153 pub fn remove_color_line_vertical(&mut self) {
155 self.vertical_colors.clear();
156 }
157
158 pub fn remove_horizontal_chars(&mut self) {
160 self.horizontal_chars.clear();
161 }
162
163 pub fn remove_vertical_chars(&mut self) {
165 self.vertical_chars.clear();
166 }
167
168 pub fn set_borders(&mut self, borders: Borders<char>) {
170 self.borders.set_borders(borders);
171 }
172
173 pub fn get_border_default(&self) -> Option<&char> {
175 self.borders.get_global()
176 }
177
178 pub fn set_border_default(&mut self, c: char) {
180 self.borders.set_global(c);
181 }
182
183 pub fn get_borders(&self) -> &Borders<char> {
185 self.borders.get_borders()
186 }
187
188 pub fn insert_horizontal_line(&mut self, line: usize, val: HorizontalLine) {
193 self.borders.insert_horizontal_line(line, val);
194 }
195
196 pub fn remove_horizontal_line(&mut self, line: usize, count_rows: usize) {
201 self.borders.remove_horizontal_line(line, count_rows);
202 }
203
204 pub fn get_vertical_line(&self, line: usize) -> Option<&VerticalLine> {
209 self.borders.get_vertical_line(line)
210 }
211
212 pub fn get_vertical_lines(&self) -> HashMap<usize, VerticalLine> {
217 self.borders.get_vertical_lines()
218 }
219
220 pub fn insert_vertical_line(&mut self, line: usize, val: VerticalLine) {
225 self.borders.insert_vertical_line(line, val);
226 }
227
228 pub fn remove_vertical_line(&mut self, line: usize, count_columns: usize) {
233 self.borders.remove_vertical_line(line, count_columns);
234 }
235
236 pub fn get_horizontal_line(&self, line: usize) -> Option<&HorizontalLine> {
241 self.borders.get_horizontal_line(line)
242 }
243
244 pub fn get_horizontal_lines(&self) -> HashMap<usize, HorizontalLine> {
249 self.borders.get_horizontal_lines()
250 }
251
252 pub fn set_horizontal_char(&mut self, pos: Position, offset: Offset, c: char) {
259 let chars = self
260 .horizontal_chars
261 .entry(pos)
262 .or_insert_with(|| HashMap::with_capacity(1));
263
264 chars.insert(offset, c);
265 }
266
267 pub fn lookup_horizontal_char(&self, pos: Position, offset: usize, end: usize) -> Option<char> {
272 self.horizontal_chars
273 .get(&pos)
274 .and_then(|chars| {
275 chars.get(&Offset::Start(offset)).or_else(|| {
276 if end > offset {
277 if end == 0 {
278 chars.get(&Offset::End(0))
279 } else {
280 chars.get(&Offset::End(end - offset - 1))
281 }
282 } else {
283 None
284 }
285 })
286 })
287 .copied()
288 }
289
290 pub fn is_overridden_horizontal(&self, pos: Position) -> bool {
295 self.horizontal_chars.contains_key(&pos)
296 }
297
298 pub fn remove_overridden_horizontal(&mut self, pos: Position) {
303 self.horizontal_chars.remove(&pos);
304 }
305
306 pub fn set_vertical_char(&mut self, pos: Position, offset: Offset, c: char) {
313 let chars = self
314 .vertical_chars
315 .entry(pos)
316 .or_insert_with(|| HashMap::with_capacity(1));
317
318 chars.insert(offset, c);
319 }
320
321 pub fn lookup_vertical_char(&self, pos: Position, offset: usize, end: usize) -> Option<char> {
326 self.vertical_chars
327 .get(&pos)
328 .and_then(|chars| {
329 chars.get(&Offset::Start(offset)).or_else(|| {
330 if end > offset {
331 if end == 0 {
332 chars.get(&Offset::End(0))
333 } else {
334 chars.get(&Offset::End(end - offset - 1))
335 }
336 } else {
337 None
338 }
339 })
340 })
341 .copied()
342 }
343
344 pub fn is_overridden_vertical(&self, pos: Position) -> bool {
349 self.vertical_chars.contains_key(&pos)
350 }
351
352 pub fn remove_overridden_vertical(&mut self, pos: Position) {
357 self.vertical_chars.remove(&pos);
358 }
359
360 pub fn set_horizontal_char_color(&mut self, pos: Position, offset: Offset, c: ANSIBuf) {
362 let chars = self
363 .horizontal_colors
364 .entry(pos)
365 .or_insert_with(|| HashMap::with_capacity(1));
366
367 chars.insert(offset, c);
368 }
369
370 pub fn lookup_horizontal_color(
372 &self,
373 pos: Position,
374 offset: usize,
375 end: usize,
376 ) -> Option<&ANSIBuf> {
377 self.horizontal_colors.get(&pos).and_then(|chars| {
378 chars.get(&Offset::Start(offset)).or_else(|| {
379 if end > offset {
380 if end == 0 {
381 chars.get(&Offset::End(0))
382 } else {
383 chars.get(&Offset::End(end - offset - 1))
384 }
385 } else {
386 None
387 }
388 })
389 })
390 }
391
392 pub fn set_vertical_char_color(&mut self, pos: Position, offset: Offset, c: ANSIBuf) {
394 let chars = self
395 .vertical_colors
396 .entry(pos)
397 .or_insert_with(|| HashMap::with_capacity(1));
398
399 chars.insert(offset, c);
400 }
401
402 pub fn lookup_vertical_color(
404 &self,
405 pos: Position,
406 offset: usize,
407 end: usize,
408 ) -> Option<&ANSIBuf> {
409 self.vertical_colors.get(&pos).and_then(|chars| {
410 chars.get(&Offset::Start(offset)).or_else(|| {
411 if end > offset {
412 if end == 0 {
413 chars.get(&Offset::End(0))
414 } else {
415 chars.get(&Offset::End(end - offset - 1))
416 }
417 } else {
418 None
419 }
420 })
421 })
422 }
423
424 pub fn set_padding(&mut self, entity: Entity, padding: Sides<Indent>) {
426 self.padding.insert(entity, padding);
427 }
428
429 pub fn set_padding_color(&mut self, entity: Entity, padding: Sides<Option<ANSIBuf>>) {
431 self.padding_color.insert(entity, padding);
432 }
433
434 pub fn get_padding(&self, pos: Position) -> &Sides<Indent> {
436 self.padding.get(pos)
437 }
438
439 pub fn get_padding_color(&self, pos: Position) -> &Sides<Option<ANSIBuf>> {
441 self.padding_color.get(pos)
442 }
443
444 pub fn set_trim_horizontal(&mut self, entity: Entity, on: bool) {
446 self.formatting_trim_h.insert(entity, on);
447 }
448
449 pub fn get_trim_horizonal(&self, pos: Position) -> bool {
451 *self.formatting_trim_h.get(pos)
452 }
453
454 pub fn set_trim_vertical(&mut self, entity: Entity, on: bool) {
456 self.formatting_trim_v.insert(entity, on);
457 }
458
459 pub fn get_trim_vertical(&self, pos: Position) -> bool {
461 *self.formatting_trim_v.get(pos)
462 }
463
464 pub fn set_line_alignment(&mut self, entity: Entity, on: bool) {
466 self.formatting_line_alignment.insert(entity, on);
467 }
468
469 pub fn get_line_alignment(&self, pos: Position) -> bool {
471 *self.formatting_line_alignment.get(pos)
472 }
473
474 pub fn get_formatting(&self, pos: Position) -> Formatting {
476 Formatting::new(
477 *self.formatting_trim_h.get(pos),
478 *self.formatting_trim_v.get(pos),
479 *self.formatting_line_alignment.get(pos),
480 )
481 }
482
483 pub fn set_alignment_vertical(&mut self, entity: Entity, alignment: AlignmentVertical) {
485 self.alignment_v.insert(entity, alignment);
486 }
487
488 pub fn get_alignment_vertical(&self, pos: Position) -> &AlignmentVertical {
490 self.alignment_v.get(pos)
491 }
492
493 pub fn set_alignment_horizontal(&mut self, entity: Entity, alignment: AlignmentHorizontal) {
495 self.alignment_h.insert(entity, alignment);
496 }
497
498 pub fn get_alignment_horizontal(&self, pos: Position) -> &AlignmentHorizontal {
500 self.alignment_h.get(pos)
501 }
502
503 pub fn set_border(&mut self, pos: Position, border: Border<char>) {
505 self.borders.insert_border(pos, border);
506 }
507
508 pub fn get_border(&self, pos: Position, shape: (usize, usize)) -> Border<char> {
510 self.borders.get_border(pos, shape).copied()
511 }
512
513 pub fn get_border_color(&self, pos: Position, shape: (usize, usize)) -> Border<&ANSIBuf> {
515 self.borders_colors.get_border(pos, shape)
516 }
517
518 pub fn set_borders_missing(&mut self, c: char) {
522 self.borders_missing_char = c;
523 }
524
525 pub fn get_borders_missing(&self) -> char {
527 self.borders_missing_char
528 }
529
530 pub fn get_border_color_default(&self) -> Option<&ANSIBuf> {
532 self.borders_colors.get_global()
533 }
534
535 pub fn set_border_color_default(&mut self, clr: ANSIBuf) {
537 self.borders_colors = BordersConfig::default();
538 self.borders_colors.set_global(clr);
539 }
540
541 pub fn get_color_borders(&self) -> &Borders<ANSIBuf> {
543 self.borders_colors.get_borders()
544 }
545
546 pub fn set_borders_color(&mut self, clrs: Borders<ANSIBuf>) {
548 self.borders_colors.set_borders(clrs);
549 }
550
551 pub fn set_border_color(&mut self, pos: Position, border: Border<ANSIBuf>) {
553 self.borders_colors.insert_border(pos, border)
554 }
555
556 pub fn remove_border(&mut self, pos: Position, shape: (usize, usize)) {
562 self.borders.remove_border(pos, shape);
563 }
564
565 pub fn remove_border_color(&mut self, pos: Position, shape: (usize, usize)) {
569 self.borders_colors.remove_border(pos, shape);
570 }
571
572 pub fn get_justification(&self, pos: Position) -> char {
574 *self.justification.get(pos)
575 }
576
577 pub fn get_justification_color(&self, pos: Position) -> Option<&ANSIBuf> {
581 self.justification_color.get(pos).as_ref()
582 }
583
584 pub fn set_justification(&mut self, entity: Entity, c: char) {
586 self.justification.insert(entity, c);
587 }
588
589 pub fn set_justification_color(&mut self, entity: Entity, color: Option<ANSIBuf>) {
593 self.justification_color.insert(entity, color);
594 }
595
596 pub fn get_column_spans(&self) -> HashMap<Position, usize> {
598 self.span_columns.clone()
599 }
600
601 pub fn get_row_spans(&self) -> HashMap<Position, usize> {
603 self.span_rows.clone()
604 }
605
606 pub fn get_column_span(&self, pos: Position) -> Option<usize> {
608 self.span_columns.get(&pos).copied()
609 }
610
611 pub fn get_row_span(&self, pos: Position) -> Option<usize> {
613 self.span_rows.get(&pos).copied()
614 }
615
616 pub fn remove_column_spans(&mut self) {
618 self.span_columns.clear()
619 }
620
621 pub fn remove_row_spans(&mut self) {
623 self.span_rows.clear()
624 }
625
626 pub fn set_column_span(&mut self, pos: Position, span: usize) {
633 set_cell_column_span(self, pos, span);
634 }
635
636 pub fn has_column_spans(&self) -> bool {
638 !self.span_columns.is_empty()
639 }
640
641 pub fn set_row_span(&mut self, pos: Position, span: usize) {
648 set_cell_row_span(self, pos, span);
649 }
650
651 pub fn has_row_spans(&self) -> bool {
653 !self.span_rows.is_empty()
654 }
655
656 pub fn has_border_colors(&self) -> bool {
658 !self.borders_colors.is_empty()
659 }
660
661 pub fn has_offset_chars(&self) -> bool {
663 !self.horizontal_chars.is_empty() || !self.vertical_chars.is_empty()
664 }
665
666 pub fn has_justification(&self) -> bool {
668 !self.justification.is_empty()
669 || !self.justification_color.is_empty()
670 || self.justification_color.as_ref().is_some()
671 }
672
673 pub fn has_padding(&self) -> bool {
675 !self.padding.is_empty()
676 }
677
678 pub fn has_padding_color(&self) -> bool {
680 if !self.padding_color.is_empty() {
681 let map = HashMap::from(self.padding_color.clone());
682 for (entity, value) in map {
683 if matches!(entity, Entity::Global) {
684 continue;
685 }
686
687 if !value.is_empty() {
688 return true;
689 }
690 }
691 }
692
693 !self.padding_color.as_ref().is_empty()
694 }
695
696 pub fn has_formatting(&self) -> bool {
698 !self.formatting_trim_h.is_empty()
699 || !self.formatting_trim_v.is_empty()
700 || !self.formatting_line_alignment.is_empty()
701 }
702
703 pub fn has_alignment_vertical(&self) -> bool {
705 !self.alignment_v.is_empty()
706 }
707
708 pub fn has_alignment_horizontal(&self) -> bool {
710 !self.alignment_h.is_empty()
711 }
712
713 pub fn get_intersection(&self, pos: Position, shape: (usize, usize)) -> Option<char> {
717 let c = self.borders.get_intersection(pos, shape);
718 if let Some(c) = c {
719 return Some(*c);
720 }
721
722 if self.has_horizontal(pos.row, shape.0) && self.has_vertical(pos.col, shape.1) {
723 return Some(self.get_borders_missing());
724 }
725
726 None
727 }
728
729 pub fn get_horizontal(&self, pos: Position, count_rows: usize) -> Option<char> {
733 let c = self.borders.get_horizontal(pos, count_rows);
734 if let Some(c) = c {
735 return Some(*c);
736 }
737
738 if self.has_horizontal(pos.row, count_rows) {
739 return Some(self.get_borders_missing());
740 }
741
742 None
743 }
744
745 pub fn get_vertical(&self, pos: Position, count_columns: usize) -> Option<char> {
749 if let Some(c) = self.borders.get_vertical(pos, count_columns) {
750 return Some(*c);
751 }
752
753 if self.has_vertical(pos.col, count_columns) {
754 return Some(self.get_borders_missing());
755 }
756
757 None
758 }
759
760 pub fn get_horizontal_color(&self, pos: Position, count_rows: usize) -> Option<&ANSIBuf> {
762 self.borders_colors.get_horizontal(pos, count_rows)
763 }
764
765 pub fn get_vertical_color(&self, pos: Position, count_columns: usize) -> Option<&ANSIBuf> {
767 self.borders_colors.get_vertical(pos, count_columns)
768 }
769
770 pub fn get_intersection_color(&self, pos: Position, shape: (usize, usize)) -> Option<&ANSIBuf> {
772 self.borders_colors.get_intersection(pos, shape)
773 }
774
775 pub fn has_horizontal(&self, row: usize, count_rows: usize) -> bool {
779 self.borders.has_horizontal(row, count_rows)
780 }
781
782 pub fn has_vertical(&self, col: usize, count_columns: usize) -> bool {
786 self.borders.has_vertical(col, count_columns)
787 }
788
789 pub fn count_horizontal(&self, count_rows: usize) -> usize {
793 (0..=count_rows)
794 .filter(|&row| self.has_horizontal(row, count_rows))
795 .count()
796 }
797
798 pub fn count_vertical(&self, count_columns: usize) -> usize {
802 (0..=count_columns)
803 .filter(|&col| self.has_vertical(col, count_columns))
804 .count()
805 }
806
807 pub fn is_cell_visible(&self, pos: Position) -> bool {
809 !(self.is_cell_covered_by_column_span(pos)
810 || self.is_cell_covered_by_row_span(pos)
811 || self.is_cell_covered_by_both_spans(pos))
812 }
813
814 pub fn is_cell_covered_by_row_span(&self, pos: Position) -> bool {
816 is_cell_covered_by_row_span(self, pos)
817 }
818
819 pub fn is_cell_covered_by_column_span(&self, pos: Position) -> bool {
821 is_cell_covered_by_column_span(self, pos)
822 }
823
824 pub fn is_cell_covered_by_both_spans(&self, pos: Position) -> bool {
826 is_cell_covered_by_both_spans(self, pos)
827 }
828}
829
830impl From<CompactConfig> for SpannedConfig {
831 fn from(compact: CompactConfig) -> Self {
832 use Entity::Global;
833
834 let mut cfg = Self::default();
835
836 cfg.set_padding(Global, *compact.get_padding());
837 cfg.set_padding_color(Global, to_ansi_color(*compact.get_padding_color()));
838 cfg.set_margin(*compact.get_margin());
839 cfg.set_margin_color(to_ansi_color(*compact.get_margin_color()));
840 cfg.set_alignment_horizontal(Global, compact.get_alignment_horizontal());
841 cfg.set_borders(*compact.get_borders());
842 cfg.set_borders_color(compact.get_borders_color().convert_into());
843
844 cfg
845 }
846}
847
848fn to_ansi_color(b: Sides<ANSIStr<'_>>) -> Sides<Option<ANSIBuf>> {
849 Sides::new(
850 Some(b.left.into()),
851 Some(b.right.into()),
852 Some(b.top.into()),
853 Some(b.bottom.into()),
854 )
855}
856
857fn set_cell_row_span(cfg: &mut SpannedConfig, pos: Position, span: usize) {
858 if span == 0 {
860 return;
861 }
862
863 if span == 1 {
866 cfg.span_rows.remove(&pos);
867 return;
868 }
869
870 cfg.span_rows.insert(pos, span);
871}
872
873fn set_cell_column_span(cfg: &mut SpannedConfig, pos: Position, span: usize) {
874 if span == 0 {
876 return;
877 }
878
879 if span == 1 {
882 cfg.span_columns.remove(&pos);
883 return;
884 }
885
886 cfg.span_columns.insert(pos, span);
887}
888
889fn is_cell_covered_by_column_span(cfg: &SpannedConfig, pos: Position) -> bool {
890 cfg.span_columns
891 .iter()
892 .any(|(p, span)| p.row == pos.row && pos.col > p.col && pos.col < p.col + span)
893}
894
895fn is_cell_covered_by_row_span(cfg: &SpannedConfig, pos: Position) -> bool {
896 cfg.span_rows
897 .iter()
898 .any(|(p, span)| p.col == pos.col && pos.row > p.row && pos.row < p.row + span)
899}
900
901fn is_cell_covered_by_both_spans(cfg: &SpannedConfig, pos: Position) -> bool {
902 if !cfg.has_column_spans() || !cfg.has_row_spans() {
903 return false;
904 }
905
906 cfg.span_rows.iter().any(|(p1, row_span)| {
907 cfg.span_columns
908 .iter()
909 .filter(|(p2, _)| &p1 == p2)
910 .any(|(_, col_span)| {
911 pos.row > p1.row
912 && pos.row < p1.row + row_span
913 && pos.col > p1.col
914 && pos.col < p1.col + col_span
915 })
916 })
917}
918
919#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
921struct MarginIndent {
922 indent: Indent,
924 offset: Offset,
926 color: Option<ANSIBuf>,
928}
929
930impl Default for MarginIndent {
931 fn default() -> Self {
932 Self {
933 indent: Indent::default(),
934 offset: Offset::Start(0),
935 color: None,
936 }
937 }
938}