1use core::borrow::Borrow;
4use std::{
5 borrow::Cow,
6 cmp,
7 fmt::{self, Write},
8};
9
10use crate::{
11 ansi::{ANSIBuf, ANSIFmt},
12 colors::Colors,
13 config::{
14 spanned::SpannedConfig, AlignmentHorizontal, AlignmentVertical, Formatting, Indent, Offset,
15 Position, Sides,
16 },
17 dimension::Dimension,
18 records::{ExactRecords, PeekableRecords, Records},
19 util::string::get_line_width,
20};
21
22#[derive(Debug, Clone)]
28pub struct PeekableGrid<R, G, D, C> {
29 records: R,
30 config: G,
31 dimension: D,
32 colors: C,
33}
34
35impl<R, G, D, C> PeekableGrid<R, G, D, C> {
36 pub fn new(records: R, config: G, dimension: D, colors: C) -> Self {
38 PeekableGrid {
39 records,
40 config,
41 dimension,
42 colors,
43 }
44 }
45}
46
47impl<R, G, D, C> PeekableGrid<R, G, D, C> {
48 pub fn build<F>(&self, mut f: F) -> fmt::Result
50 where
51 R: Records + PeekableRecords + ExactRecords,
52 D: Dimension,
53 C: Colors,
54 G: Borrow<SpannedConfig>,
55 F: Write,
56 {
57 if self.records.count_columns() == 0 || self.records.hint_count_rows() == Some(0) {
58 return Ok(());
59 }
60
61 let ctx = PrintCtx {
62 cfg: self.config.borrow(),
63 colors: &self.colors,
64 dims: &self.dimension,
65 records: &self.records,
66 };
67
68 print_grid(&mut f, ctx)
69 }
70}
71
72impl<R, G, D, C> fmt::Display for PeekableGrid<R, G, D, C>
73where
74 R: Records + PeekableRecords + ExactRecords,
75 D: Dimension,
76 C: Colors,
77 G: Borrow<SpannedConfig>,
78{
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 self.build(f)
81 }
82}
83
84#[derive(Debug, Copy, Clone)]
85struct PrintCtx<'a, R, D, C> {
86 records: &'a R,
87 cfg: &'a SpannedConfig,
88 dims: &'a D,
89 colors: &'a C,
90}
91
92fn print_grid<F, R, D, C>(f: &mut F, ctx: PrintCtx<'_, R, D, C>) -> fmt::Result
93where
94 F: Write,
95 R: Records + PeekableRecords + ExactRecords,
96 D: Dimension,
97 C: Colors,
98{
99 let has_spans = ctx.cfg.has_column_spans() || ctx.cfg.has_row_spans();
100 if has_spans {
101 return grid_spanned::build_grid(f, ctx);
102 }
103
104 let is_basic = !ctx.cfg.has_border_colors()
105 && !ctx.cfg.has_padding_color()
106 && !ctx.cfg.has_justification()
107 && !ctx.cfg.has_offset_chars()
108 && !has_margin(ctx.cfg)
109 && ctx.colors.is_empty();
110
111 if is_basic {
112 grid_basic::build_grid(f, ctx)
113 } else {
114 grid_not_spanned::build_grid(f, ctx)
115 }
116}
117
118fn has_margin(cfg: &SpannedConfig) -> bool {
119 let margin = cfg.get_margin();
120 margin.left.size > 0 || margin.right.size > 0 || margin.top.size > 0 || margin.bottom.size > 0
121}
122
123mod grid_basic {
124 use super::*;
125
126 struct TextCfg {
127 alignment: AlignmentHorizontal,
128 formatting: Formatting,
129 justification: char,
130 }
131
132 #[derive(Debug, Clone, Copy)]
133 struct Shape {
134 count_rows: usize,
135 count_columns: usize,
136 }
137
138 struct HIndent {
139 left: usize,
140 right: usize,
141 }
142
143 pub(super) fn build_grid<F, R, D, C>(f: &mut F, ctx: PrintCtx<'_, R, D, C>) -> fmt::Result
144 where
145 F: Write,
146 R: Records + PeekableRecords + ExactRecords,
147 D: Dimension,
148 {
149 let shape = Shape {
150 count_rows: ctx.records.count_rows(),
151 count_columns: ctx.records.count_columns(),
152 };
153
154 let mut new_line = false;
155
156 for row in 0..shape.count_rows {
157 let height = ctx.dims.get_height(row);
158
159 let has_horizontal = ctx.cfg.has_horizontal(row, shape.count_rows);
160
161 if new_line && (has_horizontal || height > 0) {
162 f.write_char('\n')?;
163 new_line = false;
164 }
165
166 if has_horizontal {
167 print_split_line(f, ctx.cfg, ctx.dims, row, shape)?;
168
169 if height > 0 {
170 f.write_char('\n')?;
171 } else {
172 new_line = true;
173 }
174 }
175
176 if height > 0 {
177 print_grid_line(f, &ctx, shape, height, row, 0)?;
178
179 for i in 1..height {
180 f.write_char('\n')?;
181
182 print_grid_line(f, &ctx, shape, height, row, i)?;
183 }
184
185 new_line = true;
186 }
187 }
188
189 if ctx.cfg.has_horizontal(shape.count_rows, shape.count_rows) {
190 f.write_char('\n')?;
191 print_split_line(f, ctx.cfg, ctx.dims, shape.count_rows, shape)?;
192 }
193
194 Ok(())
195 }
196
197 fn print_grid_line<F, R, D, C>(
198 f: &mut F,
199 ctx: &PrintCtx<'_, R, D, C>,
200 shape: Shape,
201 height: usize,
202 row: usize,
203 line: usize,
204 ) -> fmt::Result
205 where
206 F: Write,
207 R: Records + PeekableRecords + ExactRecords,
208 D: Dimension,
209 {
210 for col in 0..shape.count_columns {
211 let pos = (row, col).into();
212 print_vertical_char(f, ctx.cfg, pos, shape.count_columns)?;
213 print_cell_line(f, ctx, height, pos, line)?;
214 }
215
216 let pos = (row, shape.count_columns).into();
217 print_vertical_char(f, ctx.cfg, pos, shape.count_columns)?;
218
219 Ok(())
220 }
221
222 fn print_split_line<F, D>(
223 f: &mut F,
224 cfg: &SpannedConfig,
225 dimension: &D,
226 row: usize,
227 shape: Shape,
228 ) -> fmt::Result
229 where
230 F: Write,
231 D: Dimension,
232 {
233 print_vertical_intersection(f, cfg, (row, 0).into(), shape)?;
234
235 for col in 0..shape.count_columns {
236 let width = dimension.get_width(col);
237
238 if width > 0 {
240 let pos = (row, col).into();
241 let main = cfg.get_horizontal(pos, shape.count_rows);
242 match main {
243 Some(c) => repeat_char(f, c, width)?,
244 None => repeat_char(f, ' ', width)?,
245 }
246 }
247
248 let pos = (row, col + 1).into();
249 print_vertical_intersection(f, cfg, pos, shape)?;
250 }
251
252 Ok(())
253 }
254
255 fn print_vertical_intersection<F>(
256 f: &mut F,
257 cfg: &SpannedConfig,
258 pos: Position,
259 shape: Shape,
260 ) -> fmt::Result
261 where
262 F: fmt::Write,
263 {
264 let intersection = cfg.get_intersection(pos, (shape.count_rows, shape.count_columns));
265 let intersection = match intersection {
266 Some(c) => c,
267 None => return Ok(()),
268 };
269
270 if !cfg.has_vertical(pos.col, shape.count_columns) {
276 return Ok(());
277 }
278
279 f.write_char(intersection)?;
280
281 Ok(())
282 }
283
284 fn print_vertical_char<F>(
285 f: &mut F,
286 cfg: &SpannedConfig,
287 pos: Position,
288 count_columns: usize,
289 ) -> fmt::Result
290 where
291 F: Write,
292 {
293 let symbol = match cfg.get_vertical(pos, count_columns) {
294 Some(c) => c,
295 None => return Ok(()),
296 };
297
298 f.write_char(symbol)?;
299
300 Ok(())
301 }
302
303 fn print_cell_line<F, R, D, C>(
304 f: &mut F,
305 ctx: &PrintCtx<'_, R, D, C>,
306 height: usize,
307 pos: Position,
308 line: usize,
309 ) -> fmt::Result
310 where
311 F: Write,
312 R: Records + PeekableRecords + ExactRecords,
313 D: Dimension,
314 {
315 let width = ctx.dims.get_width(pos.col);
316
317 let pad = ctx.cfg.get_padding(pos);
318 let valignment = *ctx.cfg.get_alignment_vertical(pos);
319 let text_cfg = TextCfg {
320 alignment: *ctx.cfg.get_alignment_horizontal(pos),
321 formatting: ctx.cfg.get_formatting(pos),
322 justification: ctx.cfg.get_justification(pos),
323 };
324
325 let mut cell_height = ctx.records.count_lines(pos);
326 if text_cfg.formatting.vertical_trim {
327 cell_height -= count_empty_lines_at_start(ctx.records, pos)
328 + count_empty_lines_at_end(ctx.records, pos);
329 }
330
331 if cell_height > height {
332 cell_height = height;
334 }
335
336 let indent = top_indent(pad, valignment, cell_height, height);
337 if indent > line {
338 return repeat_char(f, pad.top.fill, width);
339 }
340
341 let mut index = line - indent;
342 let cell_has_this_line = cell_height > index;
343 if !cell_has_this_line {
344 return repeat_char(f, pad.bottom.fill, width);
346 }
347
348 if text_cfg.formatting.vertical_trim {
349 let empty_lines = count_empty_lines_at_start(ctx.records, pos);
350 index += empty_lines;
351
352 if index > ctx.records.count_lines(pos) {
353 return repeat_char(f, pad.top.fill, width);
354 }
355 }
356
357 let width = width - pad.left.size - pad.right.size;
358
359 repeat_char(f, pad.left.fill, pad.left.size)?;
360 print_line(f, ctx.records, pos, index, width, text_cfg)?;
361 repeat_char(f, pad.right.fill, pad.right.size)?;
362
363 Ok(())
364 }
365
366 fn print_line<F, R>(
367 f: &mut F,
368 records: &R,
369 pos: Position,
370 index: usize,
371 available: usize,
372 cfg: TextCfg,
373 ) -> fmt::Result
374 where
375 F: Write,
376 R: Records + PeekableRecords,
377 {
378 let line = records.get_line(pos, index);
379 let (line, line_width) = if cfg.formatting.horizontal_trim {
380 let line = string_trim(line);
381 let width = get_line_width(&line);
382 (line, width)
383 } else {
384 let width = records.get_line_width(pos, index);
385 (Cow::Borrowed(line), width)
386 };
387
388 if cfg.formatting.allow_lines_alignment {
389 let indent = calculate_indent(cfg.alignment, line_width, available);
390 return print_text_padded(f, &line, cfg.justification, indent);
391 }
392
393 let cell_width = if cfg.formatting.horizontal_trim {
394 (0..records.count_lines(pos))
395 .map(|i| records.get_line(pos, i))
396 .map(|line| get_line_width(line.trim()))
397 .max()
398 .unwrap_or_default()
399 } else {
400 records.get_width(pos)
401 };
402
403 let indent = calculate_indent(cfg.alignment, cell_width, available);
404 print_text_padded(f, &line, cfg.justification, indent)?;
405
406 let rest_width = cell_width - line_width;
407 repeat_char(f, cfg.justification, rest_width)?;
408
409 Ok(())
410 }
411
412 fn print_text_padded<F>(f: &mut F, text: &str, space: char, indent: HIndent) -> fmt::Result
413 where
414 F: Write,
415 {
416 repeat_char(f, space, indent.left)?;
417 f.write_str(text)?;
418 repeat_char(f, space, indent.right)?;
419
420 Ok(())
421 }
422
423 fn top_indent(
424 pad: &Sides<Indent>,
425 alignment: AlignmentVertical,
426 height: usize,
427 available: usize,
428 ) -> usize {
429 let available = available - pad.top.size;
430 let indent = indent_from_top(alignment, available, height);
431
432 indent + pad.top.size
433 }
434
435 fn indent_from_top(alignment: AlignmentVertical, available: usize, real: usize) -> usize {
436 match alignment {
437 AlignmentVertical::Top => 0,
438 AlignmentVertical::Bottom => available - real,
439 AlignmentVertical::Center => (available - real) / 2,
440 }
441 }
442
443 fn calculate_indent(alignment: AlignmentHorizontal, width: usize, available: usize) -> HIndent {
444 let diff = available - width;
445
446 let (left, right) = match alignment {
447 AlignmentHorizontal::Left => (0, diff),
448 AlignmentHorizontal::Right => (diff, 0),
449 AlignmentHorizontal::Center => {
450 let left = diff / 2;
451 let rest = diff - left;
452 (left, rest)
453 }
454 };
455
456 HIndent { left, right }
457 }
458
459 fn repeat_char<F>(f: &mut F, c: char, n: usize) -> fmt::Result
460 where
461 F: Write,
462 {
463 for _ in 0..n {
464 f.write_char(c)?;
465 }
466
467 Ok(())
468 }
469
470 fn count_empty_lines_at_end<R>(records: &R, pos: Position) -> usize
471 where
472 R: Records + PeekableRecords,
473 {
474 (0..records.count_lines(pos))
475 .map(|i| records.get_line(pos, i))
476 .rev()
477 .take_while(|l| l.trim().is_empty())
478 .count()
479 }
480
481 fn count_empty_lines_at_start<R>(records: &R, pos: Position) -> usize
482 where
483 R: Records + PeekableRecords,
484 {
485 (0..records.count_lines(pos))
486 .map(|i| records.get_line(pos, i))
487 .take_while(|s| s.trim().is_empty())
488 .count()
489 }
490
491 fn string_trim(text: &str) -> Cow<'_, str> {
493 #[cfg(feature = "ansi")]
494 {
495 ansi_str::AnsiStr::ansi_trim(text)
496 }
497
498 #[cfg(not(feature = "ansi"))]
499 {
500 text.trim().into()
501 }
502 }
503}
504
505mod grid_not_spanned {
506 use super::*;
507
508 struct TextCfg<C, C1> {
509 alignment: AlignmentHorizontal,
510 formatting: Formatting,
511 color: Option<C>,
512 justification: Colored<char, C1>,
513 }
514
515 struct Colored<T, C> {
516 data: T,
517 color: Option<C>,
518 }
519
520 impl<T, C> Colored<T, C> {
521 fn new(data: T, color: Option<C>) -> Self {
522 Self { data, color }
523 }
524 }
525
526 #[derive(Debug, Clone, Copy)]
527 struct Shape {
528 count_rows: usize,
529 count_columns: usize,
530 }
531
532 struct HIndent {
533 left: usize,
534 right: usize,
535 }
536
537 pub(super) fn build_grid<F, R, D, C>(f: &mut F, ctx: PrintCtx<'_, R, D, C>) -> fmt::Result
538 where
539 F: Write,
540 R: Records + PeekableRecords + ExactRecords,
541 D: Dimension,
542 C: Colors,
543 {
544 let shape = Shape {
545 count_rows: ctx.records.count_rows(),
546 count_columns: ctx.records.count_columns(),
547 };
548
549 let total_width = total_width(ctx.cfg, ctx.dims, shape.count_columns);
550
551 let margin = ctx.cfg.get_margin();
552 let total_width_with_margin = total_width + margin.left.size + margin.right.size;
553
554 let total_height = total_height(ctx.cfg, ctx.dims, shape.count_rows);
555
556 if margin.top.size > 0 {
557 print_margin_top(f, ctx.cfg, total_width_with_margin)?;
558 f.write_char('\n')?;
559 }
560
561 let mut table_line = 0;
562 let mut prev_empty_horizontal = false;
563 for row in 0..shape.count_rows {
564 let height = ctx.dims.get_height(row);
565
566 if ctx.cfg.has_horizontal(row, shape.count_rows) {
567 if prev_empty_horizontal {
568 f.write_char('\n')?;
569 }
570
571 print_margin_left(f, ctx.cfg, table_line, total_height)?;
572 print_split_line(f, ctx.cfg, ctx.dims, row, shape)?;
573 print_margin_right(f, ctx.cfg, table_line, total_height)?;
574
575 if height > 0 {
576 f.write_char('\n')?;
577 prev_empty_horizontal = false;
578 } else {
579 prev_empty_horizontal = true;
580 }
581
582 table_line += 1;
583 } else if height > 0 && prev_empty_horizontal {
584 f.write_char('\n')?;
585 prev_empty_horizontal = false;
586 }
587
588 for i in 0..height {
589 print_margin_left(f, ctx.cfg, table_line, total_height)?;
590
591 for col in 0..shape.count_columns {
592 let pos = (row, col).into();
593 print_vertical_char(f, ctx.cfg, pos, i, height, shape.count_columns)?;
594 print_cell_line(f, &ctx, height, pos, i)?;
595
596 let is_last_column = col + 1 == shape.count_columns;
597 if is_last_column {
598 let pos = (row, col + 1).into();
599 print_vertical_char(f, ctx.cfg, pos, i, height, shape.count_columns)?;
600 }
601 }
602
603 print_margin_right(f, ctx.cfg, table_line, total_height)?;
604
605 let is_last_line = i + 1 == height;
606 let is_last_row = row + 1 == shape.count_rows;
607 if !(is_last_line && is_last_row) {
608 f.write_char('\n')?;
609 }
610
611 table_line += 1;
612 }
613 }
614
615 if ctx.cfg.has_horizontal(shape.count_rows, shape.count_rows) {
616 f.write_char('\n')?;
617 print_margin_left(f, ctx.cfg, table_line, total_height)?;
618 print_split_line(f, ctx.cfg, ctx.dims, shape.count_rows, shape)?;
619 print_margin_right(f, ctx.cfg, table_line, total_height)?;
620 }
621
622 if margin.bottom.size > 0 {
623 f.write_char('\n')?;
624 print_margin_bottom(f, ctx.cfg, total_width_with_margin)?;
625 }
626
627 Ok(())
628 }
629
630 fn print_split_line<F, D>(
631 f: &mut F,
632 cfg: &SpannedConfig,
633 dimension: &D,
634 row: usize,
635 shape: Shape,
636 ) -> fmt::Result
637 where
638 F: Write,
639 D: Dimension,
640 {
641 let mut used_color = None;
642 print_vertical_intersection(f, cfg, (row, 0).into(), shape, &mut used_color)?;
643
644 for col in 0..shape.count_columns {
645 let width = dimension.get_width(col);
646
647 if width > 0 {
649 let pos = (row, col).into();
650 let main = cfg.get_horizontal(pos, shape.count_rows);
651 match main {
652 Some(c) => {
653 let clr = cfg.get_horizontal_color(pos, shape.count_rows);
654 prepare_coloring(f, clr, &mut used_color)?;
655 print_horizontal_border(f, cfg, pos, width, c, &used_color)?;
656 }
657 None => repeat_char(f, ' ', width)?,
658 }
659 }
660
661 let pos = (row, col + 1).into();
662 print_vertical_intersection(f, cfg, pos, shape, &mut used_color)?;
663 }
664
665 if let Some(clr) = used_color.take() {
666 clr.fmt_ansi_suffix(f)?;
667 }
668
669 Ok(())
670 }
671
672 fn print_vertical_intersection<'a, F>(
673 f: &mut F,
674 cfg: &'a SpannedConfig,
675 pos: Position,
676 shape: Shape,
677 used_color: &mut Option<&'a ANSIBuf>,
678 ) -> fmt::Result
679 where
680 F: fmt::Write,
681 {
682 let intersection = match cfg.get_intersection(pos, (shape.count_rows, shape.count_columns))
683 {
684 Some(c) => c,
685 None => return Ok(()),
686 };
687
688 if !cfg.has_vertical(pos.col, shape.count_columns) {
694 return Ok(());
695 }
696
697 let color = cfg.get_intersection_color(pos, (shape.count_rows, shape.count_columns));
698 prepare_coloring(f, color, used_color)?;
699 f.write_char(intersection)?;
700
701 Ok(())
702 }
703
704 fn prepare_coloring<'a, F>(
705 f: &mut F,
706 clr: Option<&'a ANSIBuf>,
707 used_color: &mut Option<&'a ANSIBuf>,
708 ) -> fmt::Result
709 where
710 F: Write,
711 {
712 match clr {
713 Some(clr) => match used_color.as_mut() {
714 Some(used_clr) => {
715 if **used_clr != *clr {
716 used_clr.fmt_ansi_suffix(f)?;
717 clr.fmt_ansi_prefix(f)?;
718 *used_clr = clr;
719 }
720 }
721 None => {
722 clr.fmt_ansi_prefix(f)?;
723 *used_color = Some(clr);
724 }
725 },
726 None => {
727 if let Some(clr) = used_color.take() {
728 clr.fmt_ansi_suffix(f)?
729 }
730 }
731 }
732
733 Ok(())
734 }
735
736 fn print_vertical_char<F>(
737 f: &mut F,
738 cfg: &SpannedConfig,
739 pos: Position,
740 line: usize,
741 count_lines: usize,
742 count_columns: usize,
743 ) -> fmt::Result
744 where
745 F: Write,
746 {
747 let symbol = match cfg.get_vertical(pos, count_columns) {
748 Some(c) => c,
749 None => return Ok(()),
750 };
751
752 let symbol = cfg
753 .lookup_vertical_char(pos, line, count_lines)
754 .unwrap_or(symbol);
755
756 let color = cfg
757 .get_vertical_color(pos, count_columns)
758 .or_else(|| cfg.lookup_vertical_color(pos, line, count_lines));
759
760 match color {
761 Some(clr) => {
762 clr.fmt_ansi_prefix(f)?;
763 f.write_char(symbol)?;
764 clr.fmt_ansi_suffix(f)?;
765 }
766 None => f.write_char(symbol)?,
767 }
768
769 Ok(())
770 }
771
772 fn print_horizontal_border<F>(
773 f: &mut F,
774 cfg: &SpannedConfig,
775 pos: Position,
776 width: usize,
777 c: char,
778 used_color: &Option<&ANSIBuf>,
779 ) -> fmt::Result
780 where
781 F: Write,
782 {
783 if !cfg.is_overridden_horizontal(pos) {
784 return repeat_char(f, c, width);
785 }
786
787 for i in 0..width {
788 let c = cfg.lookup_horizontal_char(pos, i, width).unwrap_or(c);
789 match cfg.lookup_horizontal_color(pos, i, width) {
790 Some(color) => match used_color {
791 Some(clr) => {
792 clr.fmt_ansi_suffix(f)?;
793 color.fmt_ansi_prefix(f)?;
794 f.write_char(c)?;
795 color.fmt_ansi_suffix(f)?;
796 clr.fmt_ansi_prefix(f)?;
797 }
798 None => {
799 color.fmt_ansi_prefix(f)?;
800 f.write_char(c)?;
801 color.fmt_ansi_suffix(f)?;
802 }
803 },
804 _ => f.write_char(c)?,
805 }
806 }
807
808 Ok(())
809 }
810
811 fn print_cell_line<F, R, D, C>(
812 f: &mut F,
813 ctx: &PrintCtx<'_, R, D, C>,
814 height: usize,
815 pos: Position,
816 line: usize,
817 ) -> fmt::Result
818 where
819 F: Write,
820 R: Records + PeekableRecords + ExactRecords,
821 C: Colors,
822 D: Dimension,
823 {
824 let width = ctx.dims.get_width(pos.col);
825
826 let formatting = ctx.cfg.get_formatting(pos);
827 let text_cfg = TextCfg {
828 alignment: *ctx.cfg.get_alignment_horizontal(pos),
829 color: ctx.colors.get_color(pos),
830 justification: Colored::new(
831 ctx.cfg.get_justification(pos),
832 ctx.cfg.get_justification_color(pos),
833 ),
834 formatting,
835 };
836
837 let pad = ctx.cfg.get_padding(pos);
838 let pad_color = ctx.cfg.get_padding_color(pos);
839 let valignment = *ctx.cfg.get_alignment_vertical(pos);
840
841 let mut cell_height = ctx.records.count_lines(pos);
842 if formatting.vertical_trim {
843 cell_height -= count_empty_lines_at_start(ctx.records, pos)
844 + count_empty_lines_at_end(ctx.records, pos);
845 }
846
847 if cell_height > height {
848 cell_height = height;
850 }
851
852 let indent = top_indent(pad, valignment, cell_height, height);
853 if indent > line {
854 return print_indent(f, pad.top.fill, width, pad_color.top.as_ref());
855 }
856
857 let mut index = line - indent;
858 let cell_has_this_line = cell_height > index;
859 if !cell_has_this_line {
860 return print_indent(f, pad.bottom.fill, width, pad_color.bottom.as_ref());
862 }
863
864 if formatting.vertical_trim {
865 let empty_lines = count_empty_lines_at_start(ctx.records, pos);
866 index += empty_lines;
867
868 if index > ctx.records.count_lines(pos) {
869 return print_indent(f, pad.top.fill, width, pad_color.top.as_ref());
870 }
871 }
872
873 let width = width - pad.left.size - pad.right.size;
874
875 print_indent(f, pad.left.fill, pad.left.size, pad_color.left.as_ref())?;
876 print_line(f, ctx.records, pos, index, width, text_cfg)?;
877 print_indent(f, pad.right.fill, pad.right.size, pad_color.right.as_ref())?;
878
879 Ok(())
880 }
881
882 fn print_line<F, R, C>(
883 f: &mut F,
884 records: &R,
885 pos: Position,
886 index: usize,
887 available: usize,
888 cfg: TextCfg<C, &'_ ANSIBuf>,
889 ) -> fmt::Result
890 where
891 F: Write,
892 R: Records + PeekableRecords,
893 C: ANSIFmt,
894 {
895 let line = records.get_line(pos, index);
896 let (line, line_width) = if cfg.formatting.horizontal_trim {
897 let line = string_trim(line);
898 let width = get_line_width(&line);
899 (line, width)
900 } else {
901 let width = records.get_line_width(pos, index);
902 (Cow::Borrowed(line), width)
903 };
904
905 if cfg.formatting.allow_lines_alignment {
906 let indent = calculate_indent(cfg.alignment, line_width, available);
907 let text = Colored::new(line.as_ref(), cfg.color);
908 return print_text_padded(f, &text, &cfg.justification, indent);
909 }
910
911 let cell_width = if cfg.formatting.horizontal_trim {
912 (0..records.count_lines(pos))
913 .map(|i| records.get_line(pos, i))
914 .map(|line| get_line_width(line.trim()))
915 .max()
916 .unwrap_or_default()
917 } else {
918 records.get_width(pos)
919 };
920
921 let indent = calculate_indent(cfg.alignment, cell_width, available);
922 let text = Colored::new(line.as_ref(), cfg.color);
923 print_text_padded(f, &text, &cfg.justification, indent)?;
924
925 let rest_width = cell_width - line_width;
926 print_indent2(f, &cfg.justification, rest_width)?;
927
928 Ok(())
929 }
930
931 fn print_text_padded<F, C, C1>(
932 f: &mut F,
933 text: &Colored<&str, C>,
934 justification: &Colored<char, C1>,
935 indent: HIndent,
936 ) -> fmt::Result
937 where
938 F: Write,
939 C: ANSIFmt,
940 C1: ANSIFmt,
941 {
942 print_indent2(f, justification, indent.left)?;
943 print_text2(f, text)?;
944 print_indent2(f, justification, indent.right)?;
945
946 Ok(())
947 }
948
949 fn print_text2<F, C>(f: &mut F, text: &Colored<&str, C>) -> fmt::Result
950 where
951 F: Write,
952 C: ANSIFmt,
953 {
954 match &text.color {
955 Some(color) => {
956 color.fmt_ansi_prefix(f)?;
957 f.write_str(text.data)?;
958 color.fmt_ansi_suffix(f)
959 }
960 None => f.write_str(text.data),
961 }
962 }
963
964 fn top_indent(
965 pad: &Sides<Indent>,
966 alignment: AlignmentVertical,
967 height: usize,
968 available: usize,
969 ) -> usize {
970 let available = available - pad.top.size;
971 let indent = indent_from_top(alignment, available, height);
972
973 indent + pad.top.size
974 }
975
976 fn indent_from_top(alignment: AlignmentVertical, available: usize, real: usize) -> usize {
977 match alignment {
978 AlignmentVertical::Top => 0,
979 AlignmentVertical::Bottom => available - real,
980 AlignmentVertical::Center => (available - real) / 2,
981 }
982 }
983
984 fn calculate_indent(alignment: AlignmentHorizontal, width: usize, available: usize) -> HIndent {
985 let diff = available - width;
986
987 let (left, right) = match alignment {
988 AlignmentHorizontal::Left => (0, diff),
989 AlignmentHorizontal::Right => (diff, 0),
990 AlignmentHorizontal::Center => {
991 let left = diff / 2;
992 let rest = diff - left;
993 (left, rest)
994 }
995 };
996
997 HIndent { left, right }
998 }
999
1000 fn repeat_char<F>(f: &mut F, c: char, n: usize) -> fmt::Result
1001 where
1002 F: Write,
1003 {
1004 for _ in 0..n {
1005 f.write_char(c)?;
1006 }
1007
1008 Ok(())
1009 }
1010
1011 fn count_empty_lines_at_end<R>(records: &R, pos: Position) -> usize
1012 where
1013 R: Records + PeekableRecords,
1014 {
1015 (0..records.count_lines(pos))
1016 .map(|i| records.get_line(pos, i))
1017 .rev()
1018 .take_while(|l| l.trim().is_empty())
1019 .count()
1020 }
1021
1022 fn count_empty_lines_at_start<R>(records: &R, pos: Position) -> usize
1023 where
1024 R: Records + PeekableRecords,
1025 {
1026 (0..records.count_lines(pos))
1027 .map(|i| records.get_line(pos, i))
1028 .take_while(|s| s.trim().is_empty())
1029 .count()
1030 }
1031
1032 fn total_width<D>(cfg: &SpannedConfig, dimension: &D, count_columns: usize) -> usize
1033 where
1034 D: Dimension,
1035 {
1036 (0..count_columns)
1037 .map(|i| dimension.get_width(i))
1038 .sum::<usize>()
1039 + cfg.count_vertical(count_columns)
1040 }
1041
1042 fn total_height<D>(cfg: &SpannedConfig, dimension: &D, count_rows: usize) -> usize
1043 where
1044 D: Dimension,
1045 {
1046 (0..count_rows)
1047 .map(|i| dimension.get_height(i))
1048 .sum::<usize>()
1049 + cfg.count_horizontal(count_rows)
1050 }
1051
1052 fn print_margin_top<F>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result
1053 where
1054 F: Write,
1055 {
1056 let indent = cfg.get_margin().top;
1057 let offset = cfg.get_margin_offset().top;
1058 let color = cfg.get_margin_color();
1059 let color = color.top;
1060 print_indent_lines(f, indent, offset, color, width)
1061 }
1062
1063 fn print_margin_bottom<F>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result
1064 where
1065 F: Write,
1066 {
1067 let indent = cfg.get_margin().bottom;
1068 let offset = cfg.get_margin_offset().bottom;
1069 let color = cfg.get_margin_color();
1070 let color = color.bottom;
1071 print_indent_lines(f, indent, offset, color, width)
1072 }
1073
1074 fn print_margin_left<F>(
1075 f: &mut F,
1076 cfg: &SpannedConfig,
1077 line: usize,
1078 height: usize,
1079 ) -> fmt::Result
1080 where
1081 F: Write,
1082 {
1083 let indent = cfg.get_margin().left;
1084 let offset = cfg.get_margin_offset().left;
1085 let color = cfg.get_margin_color();
1086 let color = color.left;
1087 print_margin_vertical(f, indent, offset, color, line, height)
1088 }
1089
1090 fn print_margin_right<F>(
1091 f: &mut F,
1092 cfg: &SpannedConfig,
1093 line: usize,
1094 height: usize,
1095 ) -> fmt::Result
1096 where
1097 F: Write,
1098 {
1099 let indent = cfg.get_margin().right;
1100 let offset = cfg.get_margin_offset().right;
1101 let color = cfg.get_margin_color();
1102 let color = color.right;
1103 print_margin_vertical(f, indent, offset, color, line, height)
1104 }
1105
1106 fn print_margin_vertical<F>(
1107 f: &mut F,
1108 indent: Indent,
1109 offset: Offset,
1110 color: Option<&ANSIBuf>,
1111 line: usize,
1112 height: usize,
1113 ) -> fmt::Result
1114 where
1115 F: Write,
1116 {
1117 if indent.size == 0 {
1118 return Ok(());
1119 }
1120
1121 match offset {
1122 Offset::Start(offset) => {
1123 let offset = cmp::min(offset, height);
1124 if line >= offset {
1125 print_indent(f, indent.fill, indent.size, color)?;
1126 } else {
1127 repeat_char(f, ' ', indent.size)?;
1128 }
1129 }
1130 Offset::End(offset) => {
1131 let offset = cmp::min(offset, height);
1132 let pos = height - offset;
1133
1134 if line >= pos {
1135 repeat_char(f, ' ', indent.size)?;
1136 } else {
1137 print_indent(f, indent.fill, indent.size, color)?;
1138 }
1139 }
1140 }
1141
1142 Ok(())
1143 }
1144
1145 fn print_indent_lines<F>(
1146 f: &mut F,
1147 indent: Indent,
1148 offset: Offset,
1149 color: Option<&ANSIBuf>,
1150 width: usize,
1151 ) -> fmt::Result
1152 where
1153 F: Write,
1154 {
1155 if indent.size == 0 {
1156 return Ok(());
1157 }
1158
1159 let (start_offset, end_offset) = match offset {
1160 Offset::Start(start) => (start, 0),
1161 Offset::End(end) => (0, end),
1162 };
1163
1164 let start_offset = std::cmp::min(start_offset, width);
1165 let end_offset = std::cmp::min(end_offset, width);
1166 let indent_size = width - start_offset - end_offset;
1167
1168 for i in 0..indent.size {
1169 if start_offset > 0 {
1170 repeat_char(f, ' ', start_offset)?;
1171 }
1172
1173 if indent_size > 0 {
1174 print_indent(f, indent.fill, indent_size, color)?;
1175 }
1176
1177 if end_offset > 0 {
1178 repeat_char(f, ' ', end_offset)?;
1179 }
1180
1181 if i + 1 != indent.size {
1182 f.write_char('\n')?;
1183 }
1184 }
1185
1186 Ok(())
1187 }
1188
1189 fn print_indent<F, C>(f: &mut F, c: char, n: usize, color: Option<C>) -> fmt::Result
1190 where
1191 F: Write,
1192 C: ANSIFmt,
1193 {
1194 if n == 0 {
1195 return Ok(());
1196 }
1197
1198 match color {
1199 Some(color) => {
1200 color.fmt_ansi_prefix(f)?;
1201 repeat_char(f, c, n)?;
1202 color.fmt_ansi_suffix(f)
1203 }
1204 None => repeat_char(f, c, n),
1205 }
1206 }
1207
1208 fn print_indent2<F, C>(f: &mut F, c: &Colored<char, C>, n: usize) -> fmt::Result
1209 where
1210 F: Write,
1211 C: ANSIFmt,
1212 {
1213 if n == 0 {
1214 return Ok(());
1215 }
1216
1217 match &c.color {
1218 Some(color) => {
1219 color.fmt_ansi_prefix(f)?;
1220 repeat_char(f, c.data, n)?;
1221 color.fmt_ansi_suffix(f)
1222 }
1223 None => repeat_char(f, c.data, n),
1224 }
1225 }
1226
1227 fn string_trim(text: &str) -> Cow<'_, str> {
1229 #[cfg(feature = "ansi")]
1230 {
1231 ansi_str::AnsiStr::ansi_trim(text)
1232 }
1233
1234 #[cfg(not(feature = "ansi"))]
1235 {
1236 text.trim().into()
1237 }
1238 }
1239}
1240
1241mod grid_spanned {
1242 use super::*;
1243
1244 struct TextCfg<C, C1> {
1245 alignment: AlignmentHorizontal,
1246 formatting: Formatting,
1247 color: Option<C>,
1248 justification: Colored<char, C1>,
1249 }
1250
1251 struct Colored<T, C> {
1252 data: T,
1253 color: Option<C>,
1254 }
1255
1256 impl<T, C> Colored<T, C> {
1257 fn new(data: T, color: Option<C>) -> Self {
1258 Self { data, color }
1259 }
1260 }
1261
1262 #[derive(Debug, Copy, Clone)]
1263 struct Shape {
1264 count_rows: usize,
1265 count_columns: usize,
1266 }
1267
1268 struct HIndent {
1269 left: usize,
1270 right: usize,
1271 }
1272
1273 pub(super) fn build_grid<F, R, D, C>(f: &mut F, ctx: PrintCtx<'_, R, D, C>) -> fmt::Result
1274 where
1275 F: Write,
1276 R: Records + PeekableRecords + ExactRecords,
1277 D: Dimension,
1278 C: Colors,
1279 {
1280 let shape = Shape {
1281 count_rows: ctx.records.count_rows(),
1282 count_columns: ctx.records.count_columns(),
1283 };
1284
1285 let total_width = total_width(ctx.cfg, ctx.dims, shape.count_columns);
1286
1287 let margin = ctx.cfg.get_margin();
1288 let total_width_with_margin = total_width + margin.left.size + margin.right.size;
1289
1290 let total_height = total_height(ctx.cfg, ctx.dims, shape.count_rows);
1291
1292 if margin.top.size > 0 {
1293 print_margin_top(f, ctx.cfg, total_width_with_margin)?;
1294 f.write_char('\n')?;
1295 }
1296
1297 let mut table_line = 0;
1298 let mut prev_empty_horizontal = false;
1299 for row in 0..shape.count_rows {
1300 let count_lines = ctx.dims.get_height(row);
1301
1302 if ctx.cfg.has_horizontal(row, shape.count_rows) {
1303 if prev_empty_horizontal {
1304 f.write_char('\n')?;
1305 }
1306
1307 print_margin_left(f, ctx.cfg, table_line, total_height)?;
1308 print_split_line_spanned(f, &ctx, row, shape)?;
1309 print_margin_right(f, ctx.cfg, table_line, total_height)?;
1310
1311 if count_lines > 0 {
1312 f.write_char('\n')?;
1313 prev_empty_horizontal = false;
1314 } else {
1315 prev_empty_horizontal = true;
1316 }
1317
1318 table_line += 1;
1319 } else if count_lines > 0 && prev_empty_horizontal {
1320 f.write_char('\n')?;
1321 prev_empty_horizontal = false;
1322 }
1323
1324 for i in 0..count_lines {
1325 print_margin_left(f, ctx.cfg, table_line, total_height)?;
1326
1327 for col in 0..shape.count_columns {
1328 let pos = (row, col).into();
1329
1330 if ctx.cfg.is_cell_covered_by_both_spans(pos) {
1331 continue;
1332 }
1333
1334 if ctx.cfg.is_cell_covered_by_column_span(pos) {
1335 let is_last_column = col + 1 == shape.count_columns;
1336 if is_last_column {
1337 let pos = (row, col + 1).into();
1338 let count_columns = shape.count_columns;
1339 print_vertical_char(f, ctx.cfg, pos, i, count_lines, count_columns)?;
1340 }
1341
1342 continue;
1343 }
1344
1345 print_vertical_char(f, ctx.cfg, pos, i, count_lines, shape.count_columns)?;
1346
1347 if ctx.cfg.is_cell_covered_by_row_span(pos) {
1348 let original_row = closest_visible_row(ctx.cfg, pos).unwrap();
1351
1352 let mut skip_lines = (original_row..row)
1354 .map(|i| ctx.dims.get_height(i))
1355 .sum::<usize>();
1356
1357 skip_lines += (original_row + 1..=row)
1358 .map(|row| ctx.cfg.has_horizontal(row, shape.count_rows) as usize)
1359 .sum::<usize>();
1360
1361 let line = i + skip_lines;
1362 let pos = (original_row, col).into();
1363
1364 let width = get_cell_width(ctx.cfg, ctx.dims, pos, shape.count_columns);
1365 let height = get_cell_height(ctx.cfg, ctx.dims, pos, shape.count_rows);
1366
1367 print_cell_line(f, &ctx, width, height, pos, line)?;
1368 } else {
1369 let width = get_cell_width(ctx.cfg, ctx.dims, pos, shape.count_columns);
1370 let height = get_cell_height(ctx.cfg, ctx.dims, pos, shape.count_rows);
1371 print_cell_line(f, &ctx, width, height, pos, i)?;
1372 }
1373
1374 let is_last_column = col + 1 == shape.count_columns;
1375 if is_last_column {
1376 let pos = (row, col + 1).into();
1377 print_vertical_char(f, ctx.cfg, pos, i, count_lines, shape.count_columns)?;
1378 }
1379 }
1380
1381 print_margin_right(f, ctx.cfg, table_line, total_height)?;
1382
1383 let is_last_line = i + 1 == count_lines;
1384 let is_last_row = row + 1 == shape.count_rows;
1385 if !(is_last_line && is_last_row) {
1386 f.write_char('\n')?;
1387 }
1388
1389 table_line += 1;
1390 }
1391 }
1392
1393 if ctx.cfg.has_horizontal(shape.count_rows, shape.count_rows) {
1394 f.write_char('\n')?;
1395 print_margin_left(f, ctx.cfg, table_line, total_height)?;
1396 print_split_line(f, ctx.cfg, ctx.dims, shape.count_rows, shape)?;
1397 print_margin_right(f, ctx.cfg, table_line, total_height)?;
1398 }
1399
1400 if margin.bottom.size > 0 {
1401 f.write_char('\n')?;
1402 print_margin_bottom(f, ctx.cfg, total_width_with_margin)?;
1403 }
1404
1405 Ok(())
1406 }
1407
1408 fn print_split_line_spanned<F, R, D, C>(
1409 f: &mut F,
1410 ctx: &PrintCtx<'_, R, D, C>,
1411 row: usize,
1412 shape: Shape,
1413 ) -> fmt::Result
1414 where
1415 F: Write,
1416 R: Records + ExactRecords + PeekableRecords,
1417 D: Dimension,
1418 C: Colors,
1419 {
1420 let mut used_color = None;
1421
1422 let pos = (row, 0).into();
1423 print_vertical_intersection(f, ctx.cfg, pos, shape, &mut used_color)?;
1424
1425 for col in 0..shape.count_columns {
1426 let pos = (row, col).into();
1427 if ctx.cfg.is_cell_covered_by_both_spans(pos) {
1428 continue;
1429 }
1430
1431 if ctx.cfg.is_cell_covered_by_row_span(pos) {
1432 prepare_coloring(f, None, &mut used_color)?;
1436
1437 let original_row = closest_visible_row(ctx.cfg, pos).unwrap();
1438
1439 let mut skip_lines = (original_row..row)
1441 .map(|i| ctx.dims.get_height(i))
1442 .sum::<usize>();
1443
1444 if row > 0 {
1446 skip_lines += (original_row..row - 1)
1447 .map(|row| ctx.cfg.has_horizontal(row + 1, shape.count_rows) as usize)
1448 .sum::<usize>();
1449 }
1450
1451 let pos = (original_row, col).into();
1452 let height = get_cell_height(ctx.cfg, ctx.dims, pos, shape.count_rows);
1453 let width = get_cell_width(ctx.cfg, ctx.dims, pos, shape.count_columns);
1454 let line = skip_lines;
1455
1456 print_cell_line(f, ctx, width, height, pos, line)?;
1457
1458 let mut col = col;
1460 if let Some(span) = ctx.cfg.get_column_span(pos) {
1461 col += span - 1;
1462 }
1463
1464 let pos = (row, col + 1).into();
1465 print_vertical_intersection(f, ctx.cfg, pos, shape, &mut used_color)?;
1466
1467 continue;
1468 }
1469
1470 let width = ctx.dims.get_width(col);
1471 if width > 0 {
1472 let main = ctx.cfg.get_horizontal(pos, shape.count_rows);
1474 match main {
1475 Some(c) => {
1476 let clr = ctx.cfg.get_horizontal_color(pos, shape.count_rows);
1477 prepare_coloring(f, clr, &mut used_color)?;
1478 print_horizontal_border(f, ctx.cfg, pos, width, c, &used_color)?;
1479 }
1480 None => repeat_char(f, ' ', width)?,
1481 }
1482 }
1483
1484 let pos = (row, col + 1).into();
1485 print_vertical_intersection(f, ctx.cfg, pos, shape, &mut used_color)?;
1486 }
1487
1488 if let Some(clr) = used_color {
1489 clr.fmt_ansi_suffix(f)?;
1490 }
1491
1492 Ok(())
1493 }
1494
1495 fn print_vertical_char<F>(
1496 f: &mut F,
1497 cfg: &SpannedConfig,
1498 pos: Position,
1499 line: usize,
1500 count_lines: usize,
1501 count_columns: usize,
1502 ) -> fmt::Result
1503 where
1504 F: Write,
1505 {
1506 let symbol = match cfg.get_vertical(pos, count_columns) {
1507 Some(c) => c,
1508 None => return Ok(()),
1509 };
1510
1511 let symbol = cfg
1512 .lookup_vertical_char(pos, line, count_lines)
1513 .unwrap_or(symbol);
1514
1515 let color = cfg
1516 .get_vertical_color(pos, count_columns)
1517 .or_else(|| cfg.lookup_vertical_color(pos, line, count_lines));
1518
1519 match color {
1520 Some(clr) => {
1521 clr.fmt_ansi_prefix(f)?;
1522 f.write_char(symbol)?;
1523 clr.fmt_ansi_suffix(f)?;
1524 }
1525 None => f.write_char(symbol)?,
1526 }
1527
1528 Ok(())
1529 }
1530
1531 fn print_vertical_intersection<'a, F>(
1532 f: &mut F,
1533 cfg: &'a SpannedConfig,
1534 pos: Position,
1535 shape: Shape,
1536 used_color: &mut Option<&'a ANSIBuf>,
1537 ) -> fmt::Result
1538 where
1539 F: fmt::Write,
1540 {
1541 let intersection = match cfg.get_intersection(pos, (shape.count_rows, shape.count_columns))
1542 {
1543 Some(c) => c,
1544 None => return Ok(()),
1545 };
1546
1547 if !cfg.has_vertical(pos.col, shape.count_columns) {
1553 return Ok(());
1554 }
1555
1556 let color = cfg.get_intersection_color(pos, (shape.count_rows, shape.count_columns));
1557 prepare_coloring(f, color, used_color)?;
1558 f.write_char(intersection)?;
1559
1560 Ok(())
1561 }
1562
1563 fn prepare_coloring<'a, F>(
1564 f: &mut F,
1565 clr: Option<&'a ANSIBuf>,
1566 used_color: &mut Option<&'a ANSIBuf>,
1567 ) -> fmt::Result
1568 where
1569 F: Write,
1570 {
1571 match clr {
1572 Some(clr) => match used_color.as_mut() {
1573 Some(used_clr) => {
1574 if **used_clr != *clr {
1575 used_clr.fmt_ansi_suffix(f)?;
1576 clr.fmt_ansi_prefix(f)?;
1577 *used_clr = clr;
1578 }
1579 }
1580 None => {
1581 clr.fmt_ansi_prefix(f)?;
1582 *used_color = Some(clr);
1583 }
1584 },
1585 None => {
1586 if let Some(clr) = used_color.take() {
1587 clr.fmt_ansi_suffix(f)?
1588 }
1589 }
1590 }
1591
1592 Ok(())
1593 }
1594
1595 fn print_split_line<F, D>(
1596 f: &mut F,
1597 cfg: &SpannedConfig,
1598 dimension: &D,
1599 row: usize,
1600 shape: Shape,
1601 ) -> fmt::Result
1602 where
1603 F: Write,
1604 D: Dimension,
1605 {
1606 let mut used_color = None;
1607 print_vertical_intersection(f, cfg, (row, 0).into(), shape, &mut used_color)?;
1608
1609 for col in 0..shape.count_columns {
1610 let width = dimension.get_width(col);
1611
1612 if width > 0 {
1614 let pos = (row, col).into();
1615 let main = cfg.get_horizontal(pos, shape.count_rows);
1616 match main {
1617 Some(c) => {
1618 let clr = cfg.get_horizontal_color(pos, shape.count_rows);
1619 prepare_coloring(f, clr, &mut used_color)?;
1620 print_horizontal_border(f, cfg, pos, width, c, &used_color)?;
1621 }
1622 None => repeat_char(f, ' ', width)?,
1623 }
1624 }
1625
1626 let pos = (row, col + 1).into();
1627 print_vertical_intersection(f, cfg, pos, shape, &mut used_color)?;
1628 }
1629
1630 if let Some(clr) = used_color.take() {
1631 clr.fmt_ansi_suffix(f)?;
1632 }
1633
1634 Ok(())
1635 }
1636
1637 fn print_horizontal_border<F>(
1638 f: &mut F,
1639 cfg: &SpannedConfig,
1640 pos: Position,
1641 width: usize,
1642 c: char,
1643 used_color: &Option<&ANSIBuf>,
1644 ) -> fmt::Result
1645 where
1646 F: Write,
1647 {
1648 if !cfg.is_overridden_horizontal(pos) {
1649 return repeat_char(f, c, width);
1650 }
1651
1652 for i in 0..width {
1653 let c = cfg.lookup_horizontal_char(pos, i, width).unwrap_or(c);
1654 match cfg.lookup_horizontal_color(pos, i, width) {
1655 Some(color) => match used_color {
1656 Some(clr) => {
1657 clr.fmt_ansi_suffix(f)?;
1658 color.fmt_ansi_prefix(f)?;
1659 f.write_char(c)?;
1660 color.fmt_ansi_suffix(f)?;
1661 clr.fmt_ansi_prefix(f)?;
1662 }
1663 None => {
1664 color.fmt_ansi_prefix(f)?;
1665 f.write_char(c)?;
1666 color.fmt_ansi_suffix(f)?;
1667 }
1668 },
1669 _ => f.write_char(c)?,
1670 }
1671 }
1672
1673 Ok(())
1674 }
1675
1676 fn print_cell_line<F, R, D, C>(
1677 f: &mut F,
1678 ctx: &PrintCtx<'_, R, D, C>,
1679 width: usize,
1680 height: usize,
1681 pos: Position,
1682 line: usize,
1683 ) -> fmt::Result
1684 where
1685 F: Write,
1686 R: Records + PeekableRecords + ExactRecords,
1687 C: Colors,
1688 {
1689 let mut cell_height = ctx.records.count_lines(pos);
1690 let formatting = ctx.cfg.get_formatting(pos);
1691 if formatting.vertical_trim {
1692 cell_height -= count_empty_lines_at_start(ctx.records, pos)
1693 + count_empty_lines_at_end(ctx.records, pos);
1694 }
1695
1696 if cell_height > height {
1697 cell_height = height;
1699 }
1700
1701 let pad = ctx.cfg.get_padding(pos);
1702 let pad_color = ctx.cfg.get_padding_color(pos);
1703 let alignment = ctx.cfg.get_alignment_vertical(pos);
1704 let indent = top_indent(pad, *alignment, cell_height, height);
1705 if indent > line {
1706 return print_indent(f, pad.top.fill, width, pad_color.top.as_ref());
1707 }
1708
1709 let mut index = line - indent;
1710 let cell_has_this_line = cell_height > index;
1711 if !cell_has_this_line {
1712 return print_indent(f, pad.bottom.fill, width, pad_color.bottom.as_ref());
1714 }
1715
1716 if formatting.vertical_trim {
1717 let empty_lines = count_empty_lines_at_start(ctx.records, pos);
1718 index += empty_lines;
1719
1720 if index > ctx.records.count_lines(pos) {
1721 return print_indent(f, pad.top.fill, width, pad_color.top.as_ref());
1722 }
1723 }
1724
1725 print_indent(f, pad.left.fill, pad.left.size, pad_color.left.as_ref())?;
1726
1727 let width = width - pad.left.size - pad.right.size;
1728
1729 let line_cfg = TextCfg {
1730 alignment: *ctx.cfg.get_alignment_horizontal(pos),
1731 color: ctx.colors.get_color(pos),
1732 justification: Colored::new(
1733 ctx.cfg.get_justification(pos),
1734 ctx.cfg.get_justification_color(pos),
1735 ),
1736 formatting,
1737 };
1738
1739 print_line(f, ctx.records, pos, index, width, line_cfg)?;
1740
1741 print_indent(f, pad.right.fill, pad.right.size, pad_color.right.as_ref())?;
1742
1743 Ok(())
1744 }
1745
1746 fn print_line<F, R, C, C1>(
1747 f: &mut F,
1748 records: &R,
1749 pos: Position,
1750 index: usize,
1751 available: usize,
1752 text_cfg: TextCfg<C, C1>,
1753 ) -> fmt::Result
1754 where
1755 F: Write,
1756 R: Records + PeekableRecords,
1757 C: ANSIFmt,
1758 C1: ANSIFmt,
1759 {
1760 let line = records.get_line(pos, index);
1761 let (line, line_width) = if text_cfg.formatting.horizontal_trim {
1762 let line = string_trim(line);
1763 let width = get_line_width(&line);
1764 (line, width)
1765 } else {
1766 let width = records.get_line_width(pos, index);
1767 (Cow::Borrowed(line), width)
1768 };
1769
1770 if text_cfg.formatting.allow_lines_alignment {
1771 let indent = calculate_indent(text_cfg.alignment, line_width, available);
1772 let text = Colored::new(line.as_ref(), text_cfg.color);
1773 return print_text_with_pad(f, &text, &text_cfg.justification, indent);
1774 }
1775
1776 let cell_width = if text_cfg.formatting.horizontal_trim {
1777 (0..records.count_lines(pos))
1778 .map(|i| records.get_line(pos, i))
1779 .map(|line| get_line_width(line.trim()))
1780 .max()
1781 .unwrap_or_default()
1782 } else {
1783 records.get_width(pos)
1784 };
1785
1786 let indent = calculate_indent(text_cfg.alignment, cell_width, available);
1787 let text = Colored::new(line.as_ref(), text_cfg.color.as_ref());
1788 print_text_with_pad(f, &text, &text_cfg.justification, indent)?;
1789
1790 let rest_width = cell_width - line_width;
1791 print_indent(
1792 f,
1793 text_cfg.justification.data,
1794 rest_width,
1795 text_cfg.justification.color.as_ref(),
1796 )?;
1797
1798 Ok(())
1799 }
1800
1801 fn print_text_with_pad<F, C, C1>(
1802 f: &mut F,
1803 text: &Colored<&str, C>,
1804 space: &Colored<char, C1>,
1805 indent: HIndent,
1806 ) -> fmt::Result
1807 where
1808 F: Write,
1809 C: ANSIFmt,
1810 C1: ANSIFmt,
1811 {
1812 print_indent(f, space.data, indent.left, space.color.as_ref())?;
1813 print_text(f, text.data, text.color.as_ref())?;
1814 print_indent(f, space.data, indent.right, space.color.as_ref())?;
1815 Ok(())
1816 }
1817
1818 fn print_text<F, C>(f: &mut F, text: &str, clr: Option<C>) -> fmt::Result
1819 where
1820 F: Write,
1821 C: ANSIFmt,
1822 {
1823 match clr {
1824 Some(color) => {
1825 color.fmt_ansi_prefix(f)?;
1826 f.write_str(text)?;
1827 color.fmt_ansi_suffix(f)
1828 }
1829 None => f.write_str(text),
1830 }
1831 }
1832
1833 fn top_indent(
1834 pad: &Sides<Indent>,
1835 alignment: AlignmentVertical,
1836 cell_height: usize,
1837 available: usize,
1838 ) -> usize {
1839 let height = available - pad.top.size;
1840 let indent = indent_from_top(alignment, height, cell_height);
1841
1842 indent + pad.top.size
1843 }
1844
1845 fn indent_from_top(alignment: AlignmentVertical, available: usize, real: usize) -> usize {
1846 match alignment {
1847 AlignmentVertical::Top => 0,
1848 AlignmentVertical::Bottom => available - real,
1849 AlignmentVertical::Center => (available - real) / 2,
1850 }
1851 }
1852
1853 fn calculate_indent(alignment: AlignmentHorizontal, width: usize, available: usize) -> HIndent {
1854 let diff = available - width;
1855
1856 let (left, right) = match alignment {
1857 AlignmentHorizontal::Left => (0, diff),
1858 AlignmentHorizontal::Right => (diff, 0),
1859 AlignmentHorizontal::Center => {
1860 let left = diff / 2;
1861 let rest = diff - left;
1862 (left, rest)
1863 }
1864 };
1865
1866 HIndent { left, right }
1867 }
1868
1869 fn repeat_char<F>(f: &mut F, c: char, n: usize) -> fmt::Result
1870 where
1871 F: Write,
1872 {
1873 for _ in 0..n {
1874 f.write_char(c)?;
1875 }
1876
1877 Ok(())
1878 }
1879
1880 fn count_empty_lines_at_end<R>(records: &R, pos: Position) -> usize
1881 where
1882 R: Records + PeekableRecords,
1883 {
1884 (0..records.count_lines(pos))
1885 .map(|i| records.get_line(pos, i))
1886 .rev()
1887 .take_while(|l| l.trim().is_empty())
1888 .count()
1889 }
1890
1891 fn count_empty_lines_at_start<R>(records: &R, pos: Position) -> usize
1892 where
1893 R: Records + PeekableRecords,
1894 {
1895 (0..records.count_lines(pos))
1896 .map(|i| records.get_line(pos, i))
1897 .take_while(|s| s.trim().is_empty())
1898 .count()
1899 }
1900
1901 fn total_width<D>(cfg: &SpannedConfig, dimension: &D, count_columns: usize) -> usize
1902 where
1903 D: Dimension,
1904 {
1905 (0..count_columns)
1906 .map(|i| dimension.get_width(i))
1907 .sum::<usize>()
1908 + cfg.count_vertical(count_columns)
1909 }
1910
1911 fn total_height<D>(cfg: &SpannedConfig, dimension: &D, count_rows: usize) -> usize
1912 where
1913 D: Dimension,
1914 {
1915 (0..count_rows)
1916 .map(|i| dimension.get_height(i))
1917 .sum::<usize>()
1918 + cfg.count_horizontal(count_rows)
1919 }
1920
1921 fn print_margin_top<F>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result
1922 where
1923 F: Write,
1924 {
1925 let indent = cfg.get_margin().top;
1926 let offset = cfg.get_margin_offset().top;
1927 let color = cfg.get_margin_color();
1928 let color = color.top;
1929 print_indent_lines(f, &indent, &offset, color, width)
1930 }
1931
1932 fn print_margin_bottom<F>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result
1933 where
1934 F: Write,
1935 {
1936 let indent = cfg.get_margin().bottom;
1937 let offset = cfg.get_margin_offset().bottom;
1938 let color = cfg.get_margin_color();
1939 let color = color.bottom;
1940 print_indent_lines(f, &indent, &offset, color, width)
1941 }
1942
1943 fn print_margin_left<F>(
1944 f: &mut F,
1945 cfg: &SpannedConfig,
1946 line: usize,
1947 height: usize,
1948 ) -> fmt::Result
1949 where
1950 F: Write,
1951 {
1952 let indent = cfg.get_margin().left;
1953 let offset = cfg.get_margin_offset().left;
1954 let color = cfg.get_margin_color();
1955 let color = color.left;
1956 print_margin_vertical(f, indent, offset, color, line, height)
1957 }
1958
1959 fn print_margin_right<F>(
1960 f: &mut F,
1961 cfg: &SpannedConfig,
1962 line: usize,
1963 height: usize,
1964 ) -> fmt::Result
1965 where
1966 F: Write,
1967 {
1968 let indent = cfg.get_margin().right;
1969 let offset = cfg.get_margin_offset().right;
1970 let color = cfg.get_margin_color();
1971 let color = color.right;
1972 print_margin_vertical(f, indent, offset, color, line, height)
1973 }
1974
1975 fn print_margin_vertical<F>(
1976 f: &mut F,
1977 indent: Indent,
1978 offset: Offset,
1979 color: Option<&ANSIBuf>,
1980 line: usize,
1981 height: usize,
1982 ) -> fmt::Result
1983 where
1984 F: Write,
1985 {
1986 if indent.size == 0 {
1987 return Ok(());
1988 }
1989
1990 match offset {
1991 Offset::Start(offset) => {
1992 let offset = cmp::min(offset, height);
1993 if line >= offset {
1994 print_indent(f, indent.fill, indent.size, color)?;
1995 } else {
1996 repeat_char(f, ' ', indent.size)?;
1997 }
1998 }
1999 Offset::End(offset) => {
2000 let offset = cmp::min(offset, height);
2001 let pos = height - offset;
2002
2003 if line >= pos {
2004 repeat_char(f, ' ', indent.size)?;
2005 } else {
2006 print_indent(f, indent.fill, indent.size, color)?;
2007 }
2008 }
2009 }
2010
2011 Ok(())
2012 }
2013
2014 fn print_indent_lines<F>(
2015 f: &mut F,
2016 indent: &Indent,
2017 offset: &Offset,
2018 color: Option<&ANSIBuf>,
2019 width: usize,
2020 ) -> fmt::Result
2021 where
2022 F: Write,
2023 {
2024 if indent.size == 0 {
2025 return Ok(());
2026 }
2027
2028 let (start_offset, end_offset) = match offset {
2029 Offset::Start(start) => (*start, 0),
2030 Offset::End(end) => (0, *end),
2031 };
2032
2033 let start_offset = std::cmp::min(start_offset, width);
2034 let end_offset = std::cmp::min(end_offset, width);
2035 let indent_size = width - start_offset - end_offset;
2036
2037 for i in 0..indent.size {
2038 if start_offset > 0 {
2039 repeat_char(f, ' ', start_offset)?;
2040 }
2041
2042 if indent_size > 0 {
2043 print_indent(f, indent.fill, indent_size, color)?;
2044 }
2045
2046 if end_offset > 0 {
2047 repeat_char(f, ' ', end_offset)?;
2048 }
2049
2050 if i + 1 != indent.size {
2051 f.write_char('\n')?;
2052 }
2053 }
2054
2055 Ok(())
2056 }
2057
2058 fn print_indent<F, C>(f: &mut F, c: char, n: usize, color: Option<C>) -> fmt::Result
2059 where
2060 F: Write,
2061 C: ANSIFmt,
2062 {
2063 if n == 0 {
2064 return Ok(());
2065 }
2066
2067 match color {
2068 Some(color) => {
2069 color.fmt_ansi_prefix(f)?;
2070 repeat_char(f, c, n)?;
2071 color.fmt_ansi_suffix(f)
2072 }
2073 None => repeat_char(f, c, n),
2074 }
2075 }
2076
2077 fn get_cell_width<D>(cfg: &SpannedConfig, dims: &D, pos: Position, max: usize) -> usize
2078 where
2079 D: Dimension,
2080 {
2081 match cfg.get_column_span(pos) {
2082 Some(span) => {
2083 let start = pos.col;
2084 let end = start + span;
2085 range_width(dims, start, end) + count_verticals_range(cfg, start, end, max)
2086 }
2087 None => dims.get_width(pos.col),
2088 }
2089 }
2090
2091 fn range_width<D>(dims: &D, start: usize, end: usize) -> usize
2092 where
2093 D: Dimension,
2094 {
2095 (start..end).map(|col| dims.get_width(col)).sum::<usize>()
2096 }
2097
2098 fn count_verticals_range(cfg: &SpannedConfig, start: usize, end: usize, max: usize) -> usize {
2099 (start + 1..end)
2100 .map(|i| cfg.has_vertical(i, max) as usize)
2101 .sum()
2102 }
2103
2104 fn get_cell_height<D>(cfg: &SpannedConfig, dims: &D, pos: Position, max: usize) -> usize
2105 where
2106 D: Dimension,
2107 {
2108 match cfg.get_row_span(pos) {
2109 Some(span) => {
2110 let start = pos.row;
2111 let end = pos.row + span;
2112 range_height(dims, start, end) + count_horizontals_range(cfg, start, end, max)
2113 }
2114 None => dims.get_height(pos.row),
2115 }
2116 }
2117
2118 fn range_height<D>(dims: &D, start: usize, end: usize) -> usize
2119 where
2120 D: Dimension,
2121 {
2122 (start..end).map(|col| dims.get_height(col)).sum::<usize>()
2123 }
2124
2125 fn count_horizontals_range(cfg: &SpannedConfig, start: usize, end: usize, max: usize) -> usize {
2126 (start + 1..end)
2127 .map(|i| cfg.has_horizontal(i, max) as usize)
2128 .sum()
2129 }
2130
2131 fn closest_visible_row(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> {
2132 loop {
2133 if cfg.is_cell_visible(pos) {
2134 return Some(pos.row);
2135 }
2136
2137 if pos.row == 0 {
2138 return None;
2139 }
2140
2141 pos -= (1, 0);
2142 }
2143 }
2144
2145 fn string_trim(text: &str) -> Cow<'_, str> {
2147 #[cfg(feature = "ansi")]
2148 {
2149 ansi_str::AnsiStr::ansi_trim(text)
2150 }
2151
2152 #[cfg(not(feature = "ansi"))]
2153 {
2154 text.trim().into()
2155 }
2156 }
2157}