1 | use core::fmt::{self, Display, Formatter}; |
2 | |
3 | use crate::{ |
4 | grid::{ |
5 | config::{AlignmentHorizontal, CompactMultilineConfig, Indent, Sides}, |
6 | dimension::{DimensionPriority, PoolTableDimension}, |
7 | records::EmptyRecords, |
8 | records::IntoRecords, |
9 | }, |
10 | settings::{Style, TableOption}, |
11 | }; |
12 | |
13 | /// [`PoolTable`] is a table which allows a greater set of possibilities for cell alignment. |
14 | /// It's data is not aligned in any way by default. |
15 | /// |
16 | /// It works similar to the main [`Table`] by default. |
17 | /// |
18 | /// |
19 | /// ``` |
20 | /// use tabled::tables::PoolTable; |
21 | /// |
22 | /// let data = vec![ |
23 | /// vec!["Hello" , "World" , "!" ], |
24 | /// vec!["Salve" , "mondo" , "!" ], |
25 | /// vec!["Hola" , "mundo" , "!" ], |
26 | /// ]; |
27 | /// |
28 | /// let table = PoolTable::new(data).to_string(); |
29 | /// |
30 | /// assert_eq!( |
31 | /// table, |
32 | /// "+-------+-------+---+ \n\ |
33 | /// | Hello | World | ! | \n\ |
34 | /// +-------+-------+---+ \n\ |
35 | /// | Salve | mondo | ! | \n\ |
36 | /// +-------+-------+---+ \n\ |
37 | /// | Hola | mundo | ! | \n\ |
38 | /// +-------+-------+---+" |
39 | /// ) |
40 | /// ``` |
41 | /// |
42 | /// But it allows you to have a different number of columns inside the rows. |
43 | /// |
44 | /// ``` |
45 | /// use tabled::tables::PoolTable; |
46 | /// |
47 | /// let data = vec![ |
48 | /// vec!["Hello" , "World" , "!" ], |
49 | /// vec!["Salve, mondo!" ], |
50 | /// vec!["Hola" , "mundo" , "" , "" , "!" ], |
51 | /// ]; |
52 | /// |
53 | /// let table = PoolTable::new(data).to_string(); |
54 | /// |
55 | /// assert_eq!( |
56 | /// table, |
57 | /// "+---------+---------+----+ \n\ |
58 | /// | Hello | World | ! | \n\ |
59 | /// +---------+---------+----+ \n\ |
60 | /// | Salve, mondo! | \n\ |
61 | /// +------+-------+--+--+---+ \n\ |
62 | /// | Hola | mundo | | | ! | \n\ |
63 | /// +------+-------+--+--+---+" |
64 | /// ) |
65 | /// ``` |
66 | /// |
67 | /// Notice that you also can build a custom table layout by using [`TableValue`]. |
68 | /// |
69 | /// ``` |
70 | /// use tabled::tables::{PoolTable, TableValue}; |
71 | /// |
72 | /// let message = "Hello \nWorld" ; |
73 | /// |
74 | /// let data = TableValue::Column(vec![ |
75 | /// TableValue::Row(vec![ |
76 | /// TableValue::Column(vec![ |
77 | /// TableValue::Cell(String::from(message)), |
78 | /// ]), |
79 | /// TableValue::Column(vec![ |
80 | /// TableValue::Cell(String::from(message)), |
81 | /// TableValue::Row(vec![ |
82 | /// TableValue::Cell(String::from(message)), |
83 | /// TableValue::Cell(String::from(message)), |
84 | /// TableValue::Cell(String::from(message)), |
85 | /// ]) |
86 | /// ]), |
87 | /// ]), |
88 | /// TableValue::Cell(String::from(message)), |
89 | /// ]); |
90 | /// |
91 | /// let table = PoolTable::from(data).to_string(); |
92 | /// |
93 | /// assert_eq!( |
94 | /// table, |
95 | /// "+-------+-----------------------+ \n\ |
96 | /// | Hello | Hello | \n\ |
97 | /// | World | World | \n\ |
98 | /// | +-------+-------+-------+ \n\ |
99 | /// | | Hello | Hello | Hello | \n\ |
100 | /// | | World | World | World | \n\ |
101 | /// +-------+-------+-------+-------+ \n\ |
102 | /// | Hello | \n\ |
103 | /// | World | \n\ |
104 | /// +-------------------------------+" |
105 | /// ) |
106 | /// ``` |
107 | /// |
108 | /// [`Table`]: crate::Table |
109 | #[derive (Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] |
110 | pub struct PoolTable { |
111 | config: CompactMultilineConfig, |
112 | dims: PoolTableDimension, |
113 | value: TableValue, |
114 | } |
115 | |
116 | impl PoolTable { |
117 | /// Creates a [`PoolTable`] out from a record iterator. |
118 | pub fn new<I>(iter: I) -> Self |
119 | where |
120 | I: IntoRecords, |
121 | I::Cell: AsRef<str>, |
122 | { |
123 | let value = TableValue::Column( |
124 | iter.iter_rows() |
125 | .into_iter() |
126 | .map(|row| { |
127 | TableValue::Row( |
128 | row.into_iter() |
129 | .map(|cell| cell.as_ref().to_string()) |
130 | .map(TableValue::Cell) |
131 | .collect(), |
132 | ) |
133 | }) |
134 | .collect(), |
135 | ); |
136 | |
137 | Self { |
138 | config: configure_grid(), |
139 | dims: PoolTableDimension::new(DimensionPriority::List, DimensionPriority::List), |
140 | value, |
141 | } |
142 | } |
143 | |
144 | /// A is a generic function which applies options to the [`PoolTable`] configuration. |
145 | /// |
146 | /// Notice that it has a limited support of options. |
147 | /// |
148 | /// ``` |
149 | /// use tabled::tables::PoolTable; |
150 | /// use tabled::settings::{Style, Padding}; |
151 | /// |
152 | /// let data = vec![ |
153 | /// vec!["Hello" , "World" , "!" ], |
154 | /// vec!["Salve" , "mondo" , "!" ], |
155 | /// vec!["Hola" , "mundo" , "!" ], |
156 | /// ]; |
157 | /// |
158 | /// let table = PoolTable::new(data) |
159 | /// .with(Style::extended()) |
160 | /// .with(Padding::zero()) |
161 | /// .to_string(); |
162 | /// |
163 | /// assert_eq!( |
164 | /// table, |
165 | /// "╔═════╦═════╦═╗ \n\ |
166 | /// ║Hello║World║!║ \n\ |
167 | /// ╠═════╬═════╬═╣ \n\ |
168 | /// ║Salve║mondo║!║ \n\ |
169 | /// ╠═════╬═════╬═╣ \n\ |
170 | /// ║Hola ║mundo║!║ \n\ |
171 | /// ╚═════╩═════╩═╝" |
172 | /// ) |
173 | /// ``` |
174 | pub fn with<O>(&mut self, option: O) -> &mut Self |
175 | where |
176 | O: TableOption<EmptyRecords, CompactMultilineConfig, PoolTableDimension>, |
177 | { |
178 | let mut records = EmptyRecords::default(); |
179 | option.change(&mut records, &mut self.config, &mut self.dims); |
180 | |
181 | self |
182 | } |
183 | } |
184 | |
185 | impl From<TableValue> for PoolTable { |
186 | fn from(value: TableValue) -> Self { |
187 | Self { |
188 | config: configure_grid(), |
189 | dims: PoolTableDimension::new(width:DimensionPriority::List, height:DimensionPriority::List), |
190 | value, |
191 | } |
192 | } |
193 | } |
194 | |
195 | impl Display for PoolTable { |
196 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
197 | print::build_table(&self.value, &self.config, self.dims).fmt(f) |
198 | } |
199 | } |
200 | |
201 | /// [`TableValue`] a structure which is responsible for a [`PoolTable`] layout. |
202 | #[derive (Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] |
203 | pub enum TableValue { |
204 | /// A horizontal row. |
205 | Row(Vec<TableValue>), |
206 | /// A vertical column. |
207 | Column(Vec<TableValue>), |
208 | /// A single cell. |
209 | Cell(String), |
210 | } |
211 | |
212 | fn configure_grid() -> CompactMultilineConfig { |
213 | let pad: Sides = Sides::new( |
214 | left:Indent::spaced(1), |
215 | right:Indent::spaced(1), |
216 | top:Indent::zero(), |
217 | bottom:Indent::zero(), |
218 | ); |
219 | |
220 | let mut cfg: CompactMultilineConfig = CompactMultilineConfig::new(); |
221 | cfg.set_padding(pad); |
222 | cfg.set_alignment_horizontal(alignment:AlignmentHorizontal::Left); |
223 | cfg.set_borders(Style::ascii().get_borders()); |
224 | |
225 | cfg |
226 | } |
227 | |
228 | impl<R, C> TableOption<R, C, PoolTableDimension> for PoolTableDimension { |
229 | fn change(self, _: &mut R, _: &mut C, dimension: &mut PoolTableDimension) { |
230 | *dimension = self; |
231 | } |
232 | } |
233 | |
234 | impl<R, D> TableOption<R, CompactMultilineConfig, D> for CompactMultilineConfig { |
235 | fn change(self, _: &mut R, config: &mut CompactMultilineConfig, _: &mut D) { |
236 | *config = self; |
237 | } |
238 | } |
239 | |
240 | mod print { |
241 | use std::{cmp::max, collections::HashMap, iter::repeat}; |
242 | |
243 | use crate::{ |
244 | builder::Builder, |
245 | grid::{ |
246 | ansi::ANSIStr, |
247 | config::{ |
248 | AlignmentHorizontal, AlignmentVertical, Border, Borders, ColoredConfig, |
249 | CompactMultilineConfig, Indent, Offset, Sides, |
250 | }, |
251 | dimension::{Dimension, DimensionPriority, Estimate, PoolTableDimension}, |
252 | records::Records, |
253 | util::string::{ |
254 | count_lines, get_lines, string_dimension, string_width, string_width_multiline, |
255 | }, |
256 | }, |
257 | settings::{Padding, Style, TableOption}, |
258 | }; |
259 | |
260 | use super::TableValue; |
261 | |
262 | #[derive (Debug, Default)] |
263 | struct PrintContext { |
264 | pos: usize, |
265 | is_last_col: bool, |
266 | is_last_row: bool, |
267 | is_first_col: bool, |
268 | is_first_row: bool, |
269 | kv: bool, |
270 | kv_is_first: bool, |
271 | list: bool, |
272 | list_is_first: bool, |
273 | no_left: bool, |
274 | no_right: bool, |
275 | no_bottom: bool, |
276 | lean_top: bool, |
277 | top_intersection: bool, |
278 | top_left: bool, |
279 | intersections_horizontal: Vec<usize>, |
280 | intersections_vertical: Vec<usize>, |
281 | size: Dim, |
282 | } |
283 | |
284 | struct CellData { |
285 | content: String, |
286 | intersections_horizontal: Vec<usize>, |
287 | intersections_vertical: Vec<usize>, |
288 | } |
289 | |
290 | impl CellData { |
291 | fn new(content: String, i_horizontal: Vec<usize>, i_vertical: Vec<usize>) -> Self { |
292 | Self { |
293 | content, |
294 | intersections_horizontal: i_horizontal, |
295 | intersections_vertical: i_vertical, |
296 | } |
297 | } |
298 | } |
299 | |
300 | pub(super) fn build_table( |
301 | val: &TableValue, |
302 | cfg: &CompactMultilineConfig, |
303 | dims_priority: PoolTableDimension, |
304 | ) -> String { |
305 | let dims = collect_table_dimensions(val, cfg); |
306 | let ctx = PrintContext { |
307 | is_last_col: true, |
308 | is_last_row: true, |
309 | is_first_col: true, |
310 | is_first_row: true, |
311 | size: *dims.all.get(&0).unwrap(), |
312 | ..Default::default() |
313 | }; |
314 | |
315 | let data = _build_table(val, cfg, &dims, dims_priority, ctx); |
316 | let mut table = data.content; |
317 | |
318 | let margin = cfg.get_margin(); |
319 | let has_margin = margin.top.size > 0 |
320 | || margin.bottom.size > 0 |
321 | || margin.left.size > 0 |
322 | || margin.right.size > 0; |
323 | if has_margin { |
324 | let color = convert_border_colors(*cfg.get_margin_color()); |
325 | table = set_margin(&table, *margin, color); |
326 | } |
327 | |
328 | table |
329 | } |
330 | |
331 | fn _build_table( |
332 | val: &TableValue, |
333 | cfg: &CompactMultilineConfig, |
334 | dims: &Dimensions, |
335 | priority: PoolTableDimension, |
336 | ctx: PrintContext, |
337 | ) -> CellData { |
338 | match val { |
339 | TableValue::Cell(text) => generate_value_cell(text, cfg, ctx), |
340 | TableValue::Row(list) => { |
341 | if list.is_empty() { |
342 | return generate_value_cell("" , cfg, ctx); |
343 | } |
344 | |
345 | generate_table_row(list, cfg, dims, priority, ctx) |
346 | } |
347 | TableValue::Column(list) => { |
348 | if list.is_empty() { |
349 | return generate_value_cell("" , cfg, ctx); |
350 | } |
351 | |
352 | generate_table_column(list, cfg, dims, priority, ctx) |
353 | } |
354 | } |
355 | } |
356 | |
357 | fn generate_table_column( |
358 | list: &Vec<TableValue>, |
359 | cfg: &CompactMultilineConfig, |
360 | dims: &Dimensions, |
361 | priority: PoolTableDimension, |
362 | ctx: PrintContext, |
363 | ) -> CellData { |
364 | let array_dims = dims.arrays.get(&ctx.pos).unwrap(); |
365 | |
366 | let height = dims.all.get(&ctx.pos).unwrap().height; |
367 | let additional_height = ctx.size.height - height; |
368 | let (chunk_height, mut rest_height) = split_value(additional_height, list.len()); |
369 | |
370 | let mut intersections_horizontal = ctx.intersections_horizontal; |
371 | let mut intersections_vertical = ctx.intersections_vertical; |
372 | let mut next_vsplit = false; |
373 | let mut next_intersections_vertical = vec![]; |
374 | |
375 | let mut builder = Builder::new(); |
376 | for (i, val) in list.iter().enumerate() { |
377 | let val_pos = *array_dims.index.get(&i).unwrap(); |
378 | |
379 | let mut height = dims.all.get(&val_pos).unwrap().height; |
380 | match priority.height() { |
381 | DimensionPriority::First => { |
382 | if i == 0 { |
383 | height += additional_height; |
384 | } |
385 | } |
386 | DimensionPriority::Last => { |
387 | if i + 1 == list.len() { |
388 | height += additional_height; |
389 | } |
390 | } |
391 | DimensionPriority::List => { |
392 | height += chunk_height; |
393 | |
394 | if rest_height > 0 { |
395 | height += 1; |
396 | rest_height -= 1; // must be safe |
397 | } |
398 | } |
399 | } |
400 | |
401 | let size = Dim::new(ctx.size.width, height); |
402 | |
403 | let (split, intersections_vertical) = |
404 | short_splits3(&mut intersections_vertical, size.height); |
405 | let old_split = next_vsplit; |
406 | next_vsplit = split; |
407 | |
408 | let is_prev_list_not_first = ctx.list && !ctx.list_is_first; |
409 | let valctx = PrintContext { |
410 | pos: val_pos, |
411 | is_last_col: ctx.is_last_col, |
412 | is_last_row: ctx.is_last_row && i + 1 == list.len(), |
413 | is_first_col: ctx.is_first_col, |
414 | is_first_row: ctx.is_first_row && i == 0, |
415 | kv: ctx.kv, |
416 | kv_is_first: ctx.kv_is_first, |
417 | list: true, |
418 | list_is_first: i == 0 && !is_prev_list_not_first, |
419 | no_left: ctx.no_left, |
420 | no_right: ctx.no_right, |
421 | no_bottom: ctx.no_bottom && i + 1 == list.len(), |
422 | lean_top: ctx.lean_top && i == 0, |
423 | top_intersection: (ctx.top_intersection && i == 0) || old_split, |
424 | top_left: ctx.top_left || i > 0, |
425 | intersections_horizontal, |
426 | intersections_vertical, |
427 | size, |
428 | }; |
429 | |
430 | let data = _build_table(val, cfg, dims, priority, valctx); |
431 | intersections_horizontal = data.intersections_horizontal; |
432 | next_intersections_vertical.extend(data.intersections_vertical); |
433 | |
434 | builder.push_record([data.content]); |
435 | } |
436 | |
437 | let table = builder |
438 | .build() |
439 | .with(Style::empty()) |
440 | .with(Padding::zero()) |
441 | .to_string(); |
442 | |
443 | CellData::new(table, intersections_horizontal, next_intersections_vertical) |
444 | } |
445 | |
446 | fn generate_table_row( |
447 | list: &Vec<TableValue>, |
448 | cfg: &CompactMultilineConfig, |
449 | dims: &Dimensions, |
450 | priority: PoolTableDimension, |
451 | ctx: PrintContext, |
452 | ) -> CellData { |
453 | let array_dims = dims.arrays.get(&ctx.pos).unwrap(); |
454 | |
455 | let list_width = dims.all.get(&ctx.pos).unwrap().width; |
456 | let additional_width = ctx.size.width - list_width; |
457 | let (chunk_width, mut rest_width) = split_value(additional_width, list.len()); |
458 | |
459 | let mut intersections_horizontal = ctx.intersections_horizontal; |
460 | let mut intersections_vertical = ctx.intersections_vertical; |
461 | let mut new_intersections_horizontal = vec![]; |
462 | let mut split_next = false; |
463 | |
464 | let mut buf = Vec::with_capacity(list.len()); |
465 | for (i, val) in list.iter().enumerate() { |
466 | let val_pos = *array_dims.index.get(&i).unwrap(); |
467 | |
468 | let mut width = dims.all.get(&val_pos).unwrap().width; |
469 | match priority.width() { |
470 | DimensionPriority::First => { |
471 | if i == 0 { |
472 | width += additional_width; |
473 | } |
474 | } |
475 | DimensionPriority::Last => { |
476 | if i + 1 == list.len() { |
477 | width += additional_width; |
478 | } |
479 | } |
480 | DimensionPriority::List => { |
481 | width += chunk_width; |
482 | |
483 | if rest_width > 0 { |
484 | width += 1; |
485 | rest_width -= 1; // must be safe |
486 | } |
487 | } |
488 | } |
489 | |
490 | let size = Dim::new(width, ctx.size.height); |
491 | |
492 | let (split, intersections_horizontal) = |
493 | short_splits3(&mut intersections_horizontal, width); |
494 | let old_split = split_next; |
495 | split_next = split; |
496 | |
497 | let is_prev_list_not_first = ctx.list && !ctx.list_is_first; |
498 | let valctx = PrintContext { |
499 | pos: val_pos, |
500 | is_first_col: ctx.is_first_col && i == 0, |
501 | is_last_col: ctx.is_last_col && i + 1 == list.len(), |
502 | is_last_row: ctx.is_last_row, |
503 | is_first_row: ctx.is_first_row, |
504 | kv: false, |
505 | kv_is_first: false, |
506 | list: false, |
507 | list_is_first: !is_prev_list_not_first, |
508 | no_left: false, |
509 | no_right: !(ctx.is_last_col && i + 1 == list.len()), |
510 | no_bottom: false, |
511 | lean_top: !(ctx.is_first_col && i == 0), |
512 | top_intersection: (ctx.top_intersection && i == 0) || old_split, |
513 | top_left: ctx.top_left && i == 0, |
514 | intersections_horizontal, |
515 | intersections_vertical, |
516 | size, |
517 | }; |
518 | |
519 | let val = _build_table(val, cfg, dims, priority, valctx); |
520 | intersections_vertical = val.intersections_vertical; |
521 | new_intersections_horizontal.extend(val.intersections_horizontal.iter()); |
522 | let value = val.content; |
523 | |
524 | buf.push(value); |
525 | } |
526 | |
527 | let mut builder = Builder::with_capacity(1, buf.len()); |
528 | builder.push_record(buf); |
529 | |
530 | let mut table = builder.build(); |
531 | let _ = table.with(Style::empty()); |
532 | let _ = table.with(Padding::zero()); |
533 | |
534 | let table = table.to_string(); |
535 | |
536 | CellData::new(table, new_intersections_horizontal, intersections_vertical) |
537 | } |
538 | |
539 | fn generate_value_cell( |
540 | text: &str, |
541 | cfg: &CompactMultilineConfig, |
542 | ctx: PrintContext, |
543 | ) -> CellData { |
544 | let width = ctx.size.width; |
545 | let height = ctx.size.height; |
546 | let table = generate_value_table(text, cfg, ctx); |
547 | CellData::new(table, vec![width], vec![height]) |
548 | } |
549 | |
550 | fn generate_value_table( |
551 | text: &str, |
552 | cfg: &CompactMultilineConfig, |
553 | mut ctx: PrintContext, |
554 | ) -> String { |
555 | if ctx.size.width == 0 || ctx.size.height == 0 { |
556 | return String::new(); |
557 | } |
558 | |
559 | let halignment = cfg.get_alignment_horizontal(); |
560 | let valignment = cfg.get_alignment_vertical(); |
561 | let pad = cfg.get_padding(); |
562 | let pad_color = convert_border_colors(*cfg.get_padding_color()); |
563 | let lines_alignemnt = cfg.get_formatting().allow_lines_alignment; |
564 | |
565 | let mut borders = *cfg.get_borders(); |
566 | |
567 | let bottom_intesection = cfg.get_borders().bottom_intersection.unwrap_or(' ' ); |
568 | let mut horizontal_splits = short_splits(&mut ctx.intersections_horizontal, ctx.size.width); |
569 | squash_splits(&mut horizontal_splits); |
570 | |
571 | let right_intersection = borders.right_intersection.unwrap_or(' ' ); |
572 | let mut vertical_splits = short_splits(&mut ctx.intersections_vertical, ctx.size.height); |
573 | squash_splits(&mut vertical_splits); |
574 | |
575 | config_borders(&mut borders, &ctx); |
576 | let border = create_border(borders); |
577 | |
578 | let borders_colors = *cfg.get_borders_color(); |
579 | let border_color = create_border(borders_colors); |
580 | |
581 | let mut height = ctx.size.height; |
582 | height -= pad.top.size + pad.bottom.size; |
583 | |
584 | let mut width = ctx.size.width; |
585 | width -= pad.left.size + pad.right.size; |
586 | |
587 | let count_lines = count_lines(text); |
588 | let (top, bottom) = indent_vertical(valignment, height, count_lines); |
589 | |
590 | let mut buf = String::new(); |
591 | print_top_line( |
592 | &mut buf, |
593 | border, |
594 | border_color, |
595 | &horizontal_splits, |
596 | bottom_intesection, |
597 | ctx.size.width, |
598 | ); |
599 | |
600 | let mut line_index = 0; |
601 | let mut vertical_splits = &vertical_splits[..]; |
602 | |
603 | for _ in 0..top { |
604 | let mut border = border; |
605 | if vertical_splits.first() == Some(&line_index) { |
606 | border.left = Some(right_intersection); |
607 | vertical_splits = &vertical_splits[1..]; |
608 | } |
609 | |
610 | print_line(&mut buf, border, border_color, None, ' ' , ctx.size.width); |
611 | line_index += 1; |
612 | } |
613 | |
614 | for _ in 0..pad.top.size { |
615 | let mut border = border; |
616 | if vertical_splits.first() == Some(&line_index) { |
617 | border.left = Some(right_intersection); |
618 | vertical_splits = &vertical_splits[1..]; |
619 | } |
620 | |
621 | print_line( |
622 | &mut buf, |
623 | border, |
624 | border_color, |
625 | pad_color.top, |
626 | pad.top.fill, |
627 | ctx.size.width, |
628 | ); |
629 | line_index += 1; |
630 | } |
631 | |
632 | if lines_alignemnt { |
633 | for line in get_lines(text) { |
634 | let line_width = string_width(&line); |
635 | let (left, right) = indent_horizontal(halignment, width, line_width); |
636 | |
637 | if border.has_left() { |
638 | let mut c = border.left.unwrap_or(' ' ); |
639 | if vertical_splits.first() == Some(&line_index) { |
640 | c = right_intersection; |
641 | vertical_splits = &vertical_splits[1..]; |
642 | } |
643 | |
644 | print_char(&mut buf, c, border_color.left); |
645 | } |
646 | |
647 | print_chars(&mut buf, pad.left.fill, pad_color.left, pad.left.size); |
648 | buf.extend(repeat(' ' ).take(left)); |
649 | buf.push_str(&line); |
650 | buf.extend(repeat(' ' ).take(right)); |
651 | print_chars(&mut buf, pad.right.fill, pad_color.right, pad.right.size); |
652 | |
653 | if border.has_right() { |
654 | print_char(&mut buf, border.right.unwrap_or(' ' ), border_color.right); |
655 | } |
656 | |
657 | buf.push(' \n' ); |
658 | |
659 | line_index += 1; |
660 | } |
661 | } else { |
662 | let text_width = string_width_multiline(text); |
663 | let (left, _) = indent_horizontal(halignment, width, text_width); |
664 | |
665 | for line in get_lines(text) { |
666 | let line_width = string_width(&line); |
667 | let right = width - line_width - left; |
668 | |
669 | if border.has_left() { |
670 | let mut c = border.left.unwrap_or(' ' ); |
671 | if vertical_splits.first() == Some(&line_index) { |
672 | c = right_intersection; |
673 | vertical_splits = &vertical_splits[1..]; |
674 | } |
675 | |
676 | print_char(&mut buf, c, border_color.left); |
677 | } |
678 | |
679 | print_chars(&mut buf, pad.left.fill, pad_color.left, pad.left.size); |
680 | buf.extend(repeat(' ' ).take(left)); |
681 | buf.push_str(&line); |
682 | buf.extend(repeat(' ' ).take(right)); |
683 | print_chars(&mut buf, pad.right.fill, pad_color.right, pad.right.size); |
684 | |
685 | if border.has_right() { |
686 | print_char(&mut buf, border.right.unwrap_or(' ' ), border_color.right); |
687 | } |
688 | |
689 | buf.push(' \n' ); |
690 | |
691 | line_index += 1; |
692 | } |
693 | } |
694 | |
695 | for _ in 0..pad.bottom.size { |
696 | let mut border = border; |
697 | if vertical_splits.first() == Some(&line_index) { |
698 | border.left = Some(right_intersection); |
699 | vertical_splits = &vertical_splits[1..]; |
700 | } |
701 | |
702 | print_line( |
703 | &mut buf, |
704 | border, |
705 | border_color, |
706 | pad_color.bottom, |
707 | pad.bottom.fill, |
708 | ctx.size.width, |
709 | ); |
710 | |
711 | line_index += 1; |
712 | } |
713 | |
714 | for _ in 0..bottom { |
715 | let mut border = border; |
716 | if vertical_splits.first() == Some(&line_index) { |
717 | border.left = Some(right_intersection); |
718 | vertical_splits = &vertical_splits[1..]; |
719 | } |
720 | |
721 | print_line(&mut buf, border, border_color, None, ' ' , ctx.size.width); |
722 | line_index += 1; |
723 | } |
724 | |
725 | print_bottom_line(&mut buf, border, border_color, ctx.size.width); |
726 | |
727 | let _ = buf.remove(buf.len() - 1); |
728 | |
729 | buf |
730 | } |
731 | |
732 | fn print_chars(buf: &mut String, c: char, color: Option<ANSIStr<'static>>, width: usize) { |
733 | match color { |
734 | Some(color) => { |
735 | buf.push_str(color.get_prefix()); |
736 | buf.extend(repeat(c).take(width)); |
737 | buf.push_str(color.get_suffix()); |
738 | } |
739 | None => buf.extend(repeat(c).take(width)), |
740 | } |
741 | } |
742 | |
743 | fn print_char(buf: &mut String, c: char, color: Option<ANSIStr<'static>>) { |
744 | match color { |
745 | Some(color) => { |
746 | buf.push_str(color.get_prefix()); |
747 | buf.push(c); |
748 | buf.push_str(color.get_suffix()); |
749 | } |
750 | None => buf.push(c), |
751 | } |
752 | } |
753 | |
754 | fn print_line( |
755 | buf: &mut String, |
756 | border: Border<char>, |
757 | border_color: Border<ANSIStr<'static>>, |
758 | color: Option<ANSIStr<'static>>, |
759 | c: char, |
760 | width: usize, |
761 | ) { |
762 | if border.has_left() { |
763 | let c = border.left.unwrap_or(' ' ); |
764 | print_char(buf, c, border_color.left); |
765 | } |
766 | |
767 | print_chars(buf, c, color, width); |
768 | |
769 | if border.has_right() { |
770 | let c = border.right.unwrap_or(' ' ); |
771 | print_char(buf, c, border_color.right); |
772 | } |
773 | |
774 | buf.push(' \n' ); |
775 | } |
776 | |
777 | fn print_top_line( |
778 | buf: &mut String, |
779 | border: Border<char>, |
780 | color: Border<ANSIStr<'static>>, |
781 | splits: &[usize], |
782 | split_char: char, |
783 | width: usize, |
784 | ) { |
785 | if !border.has_top() { |
786 | return; |
787 | } |
788 | |
789 | let mut used_color: Option<ANSIStr<'static>> = None; |
790 | |
791 | if border.has_left() { |
792 | if let Some(color) = color.left_top_corner { |
793 | used_color = Some(color); |
794 | buf.push_str(color.get_prefix()); |
795 | } |
796 | |
797 | let c = border.left_top_corner.unwrap_or(' ' ); |
798 | buf.push(c); |
799 | } |
800 | |
801 | if let Some(color) = color.top { |
802 | match used_color { |
803 | Some(used) => { |
804 | if used != color { |
805 | buf.push_str(used.get_suffix()); |
806 | buf.push_str(color.get_prefix()); |
807 | } |
808 | } |
809 | None => { |
810 | buf.push_str(color.get_prefix()); |
811 | used_color = Some(color); |
812 | } |
813 | } |
814 | } |
815 | |
816 | let c = border.top.unwrap_or(' ' ); |
817 | if splits.is_empty() { |
818 | buf.extend(repeat(c).take(width)); |
819 | } else { |
820 | let mut splits = splits; |
821 | for i in 0..width { |
822 | if splits.first() == Some(&i) { |
823 | buf.push(split_char); |
824 | splits = &splits[1..]; |
825 | } else { |
826 | buf.push(c); |
827 | } |
828 | } |
829 | } |
830 | |
831 | if border.has_right() { |
832 | if let Some(color) = color.right_top_corner { |
833 | match used_color { |
834 | Some(used) => { |
835 | if used != color { |
836 | buf.push_str(used.get_suffix()); |
837 | buf.push_str(color.get_prefix()); |
838 | } |
839 | } |
840 | None => { |
841 | buf.push_str(color.get_prefix()); |
842 | used_color = Some(color); |
843 | } |
844 | } |
845 | } |
846 | |
847 | let c = border.right_top_corner.unwrap_or(' ' ); |
848 | buf.push(c); |
849 | } |
850 | |
851 | if let Some(used) = used_color { |
852 | buf.push_str(used.get_suffix()); |
853 | } |
854 | |
855 | buf.push(' \n' ); |
856 | } |
857 | |
858 | fn print_bottom_line( |
859 | buf: &mut String, |
860 | border: Border<char>, |
861 | color: Border<ANSIStr<'static>>, |
862 | width: usize, |
863 | ) { |
864 | if !border.has_bottom() { |
865 | return; |
866 | } |
867 | |
868 | let mut used_color: Option<ANSIStr<'static>> = None; |
869 | |
870 | if border.has_left() { |
871 | if let Some(color) = color.left_bottom_corner { |
872 | used_color = Some(color); |
873 | buf.push_str(color.get_prefix()); |
874 | } |
875 | |
876 | let c = border.left_bottom_corner.unwrap_or(' ' ); |
877 | buf.push(c); |
878 | } |
879 | |
880 | if let Some(color) = color.bottom { |
881 | match used_color { |
882 | Some(used) => { |
883 | if used != color { |
884 | buf.push_str(used.get_suffix()); |
885 | buf.push_str(color.get_prefix()); |
886 | } |
887 | } |
888 | None => { |
889 | buf.push_str(color.get_prefix()); |
890 | used_color = Some(color); |
891 | } |
892 | } |
893 | } |
894 | |
895 | let c = border.bottom.unwrap_or(' ' ); |
896 | buf.extend(repeat(c).take(width)); |
897 | |
898 | if border.has_right() { |
899 | if let Some(color) = color.right_bottom_corner { |
900 | match used_color { |
901 | Some(used) => { |
902 | if used != color { |
903 | buf.push_str(used.get_suffix()); |
904 | buf.push_str(color.get_prefix()); |
905 | } |
906 | } |
907 | None => { |
908 | buf.push_str(color.get_prefix()); |
909 | used_color = Some(color); |
910 | } |
911 | } |
912 | } |
913 | |
914 | let c = border.right_bottom_corner.unwrap_or(' ' ); |
915 | buf.push(c); |
916 | } |
917 | |
918 | if let Some(used) = used_color { |
919 | buf.push_str(used.get_suffix()); |
920 | } |
921 | |
922 | buf.push(' \n' ); |
923 | } |
924 | |
925 | fn create_border<T>(borders: Borders<T>) -> Border<T> { |
926 | Border { |
927 | top: borders.top, |
928 | bottom: borders.bottom, |
929 | left: borders.left, |
930 | right: borders.right, |
931 | left_top_corner: borders.top_left, |
932 | left_bottom_corner: borders.bottom_left, |
933 | right_top_corner: borders.top_right, |
934 | right_bottom_corner: borders.bottom_right, |
935 | } |
936 | } |
937 | |
938 | fn config_borders(borders: &mut Borders<char>, ctx: &PrintContext) { |
939 | // set top_left |
940 | { |
941 | if ctx.kv && ctx.kv_is_first { |
942 | borders.top_left = borders.top_intersection; |
943 | } |
944 | |
945 | if ctx.kv && !ctx.kv_is_first { |
946 | borders.top_left = borders.intersection; |
947 | } |
948 | |
949 | if ctx.kv && ctx.list && !ctx.list_is_first { |
950 | borders.top_left = borders.left_intersection; |
951 | } |
952 | |
953 | if ctx.is_first_col && !ctx.is_first_row { |
954 | borders.top_left = borders.left_intersection; |
955 | } |
956 | |
957 | if ctx.lean_top { |
958 | borders.top_left = borders.top_intersection; |
959 | } |
960 | |
961 | if ctx.top_left { |
962 | borders.top_left = borders.left_intersection; |
963 | } |
964 | |
965 | if ctx.top_intersection { |
966 | borders.top_left = borders.intersection; |
967 | } |
968 | } |
969 | |
970 | if ctx.is_last_col && !ctx.is_first_row { |
971 | borders.top_right = borders.right_intersection; |
972 | } |
973 | |
974 | if !ctx.is_first_col && ctx.is_last_row { |
975 | borders.bottom_left = borders.bottom_intersection; |
976 | } |
977 | |
978 | if !ctx.is_last_row || ctx.no_bottom { |
979 | cfg_no_bottom_borders(borders); |
980 | } |
981 | |
982 | if ctx.no_right { |
983 | cfg_no_right_borders(borders); |
984 | } |
985 | } |
986 | |
987 | struct ConfigCell(PrintContext); |
988 | |
989 | impl<R, D> TableOption<R, ColoredConfig, D> for ConfigCell { |
990 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
991 | { |
992 | // we set a horizontal lines to borders to not complicate logic with cleaning it |
993 | |
994 | let mut borders = *cfg.get_borders(); |
995 | if let Some(line) = cfg.get_horizontal_line(0) { |
996 | borders.top = line.main; |
997 | borders.top_left = line.left; |
998 | borders.top_right = line.right; |
999 | } |
1000 | |
1001 | if let Some(line) = cfg.get_horizontal_line(1) { |
1002 | borders.bottom = line.main; |
1003 | borders.bottom_left = line.left; |
1004 | borders.bottom_right = line.right; |
1005 | } |
1006 | |
1007 | cfg_clear_borders(cfg); |
1008 | cfg.set_borders(borders); |
1009 | } |
1010 | |
1011 | let mut ctx = self.0; |
1012 | |
1013 | let has_vertical = cfg.get_borders().has_left(); |
1014 | if !ctx.intersections_horizontal.is_empty() && has_vertical { |
1015 | let mut splits = short_splits(&mut ctx.intersections_horizontal, ctx.size.width); |
1016 | squash_splits(&mut splits); |
1017 | |
1018 | let c = cfg.get_borders().bottom_intersection.unwrap_or(' ' ); |
1019 | cfg_set_top_chars(cfg, &splits, c) |
1020 | } |
1021 | |
1022 | let has_horizontal = cfg.get_borders().has_top(); |
1023 | if !ctx.intersections_vertical.is_empty() && has_horizontal { |
1024 | let mut splits = short_splits(&mut ctx.intersections_vertical, ctx.size.height); |
1025 | squash_splits(&mut splits); |
1026 | |
1027 | let c = cfg.get_borders().right_intersection.unwrap_or(' ' ); |
1028 | cfg_set_left_chars(cfg, &splits, c) |
1029 | } |
1030 | |
1031 | let mut borders = *cfg.get_borders(); |
1032 | |
1033 | // set top_left |
1034 | { |
1035 | if ctx.kv && ctx.kv_is_first { |
1036 | borders.top_left = borders.top_intersection; |
1037 | } |
1038 | |
1039 | if ctx.kv && !ctx.kv_is_first { |
1040 | borders.top_left = borders.intersection; |
1041 | } |
1042 | |
1043 | if ctx.kv && ctx.list && !ctx.list_is_first { |
1044 | borders.top_left = borders.left_intersection; |
1045 | } |
1046 | |
1047 | if ctx.is_first_col && !ctx.is_first_row { |
1048 | borders.top_left = borders.left_intersection; |
1049 | } |
1050 | |
1051 | if ctx.lean_top { |
1052 | borders.top_left = borders.top_intersection; |
1053 | } |
1054 | |
1055 | if ctx.top_left { |
1056 | borders.top_left = borders.left_intersection; |
1057 | } |
1058 | |
1059 | if ctx.top_intersection { |
1060 | borders.top_left = borders.intersection; |
1061 | } |
1062 | } |
1063 | |
1064 | if ctx.is_last_col && !ctx.is_first_row { |
1065 | borders.top_right = borders.right_intersection; |
1066 | } |
1067 | |
1068 | if !ctx.is_first_col && ctx.is_last_row { |
1069 | borders.bottom_left = borders.bottom_intersection; |
1070 | } |
1071 | |
1072 | if !ctx.is_last_row || ctx.no_bottom { |
1073 | cfg_no_bottom_borders(&mut borders); |
1074 | } |
1075 | |
1076 | if ctx.no_right { |
1077 | cfg_no_right_borders(&mut borders); |
1078 | } |
1079 | |
1080 | cfg.set_borders(borders); |
1081 | } |
1082 | } |
1083 | |
1084 | fn cfg_no_bottom_borders(borders: &mut Borders<char>) { |
1085 | borders.bottom = None; |
1086 | borders.bottom_intersection = None; |
1087 | borders.bottom_left = None; |
1088 | borders.bottom_right = None; |
1089 | borders.horizontal = None; |
1090 | } |
1091 | |
1092 | fn cfg_no_right_borders(borders: &mut Borders<char>) { |
1093 | borders.right = None; |
1094 | borders.right_intersection = None; |
1095 | borders.top_right = None; |
1096 | borders.bottom_right = None; |
1097 | borders.vertical = None; |
1098 | } |
1099 | |
1100 | fn cfg_set_top_chars(cfg: &mut ColoredConfig, list: &[usize], c: char) { |
1101 | for &split in list { |
1102 | let offset = split; |
1103 | cfg.set_horizontal_char((0, 0), c, Offset::Begin(offset)); |
1104 | } |
1105 | } |
1106 | |
1107 | fn cfg_set_left_chars(cfg: &mut ColoredConfig, list: &[usize], c: char) { |
1108 | for &offset in list { |
1109 | cfg.set_vertical_char((0, 0), c, Offset::Begin(offset)); |
1110 | } |
1111 | } |
1112 | |
1113 | struct NoTopBorders; |
1114 | |
1115 | impl<R, D> TableOption<R, ColoredConfig, D> for NoTopBorders { |
1116 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1117 | let mut borders = *cfg.get_borders(); |
1118 | borders.top = None; |
1119 | borders.top_intersection = None; |
1120 | borders.top_left = None; |
1121 | borders.top_right = None; |
1122 | |
1123 | cfg.set_borders(borders); |
1124 | } |
1125 | } |
1126 | |
1127 | struct NoBottomBorders; |
1128 | |
1129 | impl<R, D> TableOption<R, ColoredConfig, D> for NoBottomBorders { |
1130 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1131 | let mut borders = *cfg.get_borders(); |
1132 | borders.bottom = None; |
1133 | borders.bottom_intersection = None; |
1134 | borders.bottom_left = None; |
1135 | borders.bottom_right = None; |
1136 | |
1137 | cfg.set_borders(borders); |
1138 | } |
1139 | } |
1140 | |
1141 | struct NoRightBorders; |
1142 | |
1143 | impl<R, D> TableOption<R, ColoredConfig, D> for NoRightBorders { |
1144 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1145 | let mut borders = *cfg.get_borders(); |
1146 | borders.top_right = None; |
1147 | borders.bottom_right = None; |
1148 | borders.right = None; |
1149 | borders.right_intersection = None; |
1150 | |
1151 | cfg.set_borders(borders); |
1152 | } |
1153 | } |
1154 | |
1155 | struct NoLeftBorders; |
1156 | |
1157 | impl<R, D> TableOption<R, ColoredConfig, D> for NoLeftBorders { |
1158 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1159 | let mut borders = *cfg.get_borders(); |
1160 | borders.top_left = None; |
1161 | borders.bottom_left = None; |
1162 | borders.left = None; |
1163 | borders.left_intersection = None; |
1164 | |
1165 | cfg.set_borders(borders); |
1166 | } |
1167 | } |
1168 | |
1169 | struct TopLeftChangeTopIntersection; |
1170 | |
1171 | impl<R, D> TableOption<R, ColoredConfig, D> for TopLeftChangeTopIntersection { |
1172 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1173 | let mut borders = *cfg.get_borders(); |
1174 | borders.top_left = borders.top_intersection; |
1175 | |
1176 | cfg.set_borders(borders); |
1177 | } |
1178 | } |
1179 | |
1180 | struct TopLeftChangeIntersection; |
1181 | |
1182 | impl<R, D> TableOption<R, ColoredConfig, D> for TopLeftChangeIntersection { |
1183 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1184 | let mut borders = *cfg.get_borders(); |
1185 | borders.top_left = borders.intersection; |
1186 | |
1187 | cfg.set_borders(borders); |
1188 | } |
1189 | } |
1190 | |
1191 | struct TopLeftChangeToLeft; |
1192 | |
1193 | impl<R, D> TableOption<R, ColoredConfig, D> for TopLeftChangeToLeft { |
1194 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1195 | let mut borders = *cfg.get_borders(); |
1196 | borders.top_left = borders.left_intersection; |
1197 | |
1198 | cfg.set_borders(borders); |
1199 | } |
1200 | } |
1201 | |
1202 | struct TopRightChangeToRight; |
1203 | |
1204 | impl<R, D> TableOption<R, ColoredConfig, D> for TopRightChangeToRight { |
1205 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1206 | let mut borders = *cfg.get_borders(); |
1207 | borders.top_right = borders.right_intersection; |
1208 | |
1209 | cfg.set_borders(borders); |
1210 | } |
1211 | } |
1212 | |
1213 | struct BottomLeftChangeSplit; |
1214 | |
1215 | impl<R, D> TableOption<R, ColoredConfig, D> for BottomLeftChangeSplit { |
1216 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1217 | let mut borders = *cfg.get_borders(); |
1218 | borders.bottom_left = borders.left_intersection; |
1219 | |
1220 | cfg.set_borders(borders); |
1221 | } |
1222 | } |
1223 | |
1224 | struct BottomLeftChangeSplitToIntersection; |
1225 | |
1226 | impl<R, D> TableOption<R, ColoredConfig, D> for BottomLeftChangeSplitToIntersection { |
1227 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1228 | let mut borders = *cfg.get_borders(); |
1229 | borders.bottom_left = borders.intersection; |
1230 | |
1231 | cfg.set_borders(borders); |
1232 | } |
1233 | } |
1234 | |
1235 | struct BottomRightChangeToRight; |
1236 | |
1237 | impl<R, D> TableOption<R, ColoredConfig, D> for BottomRightChangeToRight { |
1238 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1239 | let mut borders = *cfg.get_borders(); |
1240 | borders.bottom_right = borders.right_intersection; |
1241 | |
1242 | cfg.set_borders(borders); |
1243 | } |
1244 | } |
1245 | |
1246 | struct BottomLeftChangeToBottomIntersection; |
1247 | |
1248 | impl<R, D> TableOption<R, ColoredConfig, D> for BottomLeftChangeToBottomIntersection { |
1249 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1250 | let mut borders = *cfg.get_borders(); |
1251 | borders.bottom_left = borders.bottom_intersection; |
1252 | |
1253 | cfg.set_borders(borders); |
1254 | } |
1255 | } |
1256 | |
1257 | struct SetBottomChars<'a>(&'a [usize], char); |
1258 | |
1259 | impl<R, D> TableOption<R, ColoredConfig, D> for SetBottomChars<'_> |
1260 | where |
1261 | R: Records, |
1262 | for<'a> &'a R: Records, |
1263 | for<'a> D: Dimension + Estimate<&'a R, ColoredConfig>, |
1264 | { |
1265 | fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { |
1266 | dims.estimate(&*records, cfg); |
1267 | |
1268 | let table_width = (0..records.count_columns()) |
1269 | .map(|col| dims.get_width(col)) |
1270 | .sum::<usize>() |
1271 | + cfg.count_vertical(records.count_columns()); |
1272 | let mut current_width = 0; |
1273 | |
1274 | for pos in self.0 { |
1275 | current_width += pos; |
1276 | if current_width > table_width { |
1277 | break; |
1278 | } |
1279 | |
1280 | let split_char = self.1; |
1281 | cfg.set_horizontal_char((1, 0), split_char, Offset::Begin(current_width)); |
1282 | |
1283 | current_width += 1; |
1284 | } |
1285 | } |
1286 | } |
1287 | |
1288 | struct SetTopChars<'a>(&'a [usize], char); |
1289 | |
1290 | impl<R, D> TableOption<R, ColoredConfig, D> for SetTopChars<'_> { |
1291 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1292 | for &split in self.0 { |
1293 | let offset = split; |
1294 | cfg.set_horizontal_char((0, 0), self.1, Offset::Begin(offset)); |
1295 | } |
1296 | } |
1297 | } |
1298 | |
1299 | struct SetLeftChars<'a>(&'a [usize], char); |
1300 | |
1301 | impl<R, D> TableOption<R, ColoredConfig, D> for SetLeftChars<'_> { |
1302 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1303 | for &offset in self.0 { |
1304 | cfg.set_vertical_char((0, 0), self.1, Offset::Begin(offset)); |
1305 | } |
1306 | } |
1307 | } |
1308 | |
1309 | struct GetTopIntersection(char); |
1310 | |
1311 | impl<R, D> TableOption<R, ColoredConfig, D> for &mut GetTopIntersection { |
1312 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1313 | self.0 = cfg.get_borders().top_intersection.unwrap_or(' ' ); |
1314 | } |
1315 | } |
1316 | |
1317 | struct GetBottomIntersection(char); |
1318 | |
1319 | impl<R, D> TableOption<R, ColoredConfig, D> for &mut GetBottomIntersection { |
1320 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
1321 | self.0 = cfg.get_borders().bottom_intersection.unwrap_or(' ' ); |
1322 | } |
1323 | } |
1324 | |
1325 | #[derive (Debug, Default)] |
1326 | struct Dimensions { |
1327 | all: HashMap<usize, Dim>, |
1328 | arrays: HashMap<usize, ArrayDimensions>, |
1329 | } |
1330 | |
1331 | #[derive (Debug, Default, Clone, Copy)] |
1332 | struct Dim { |
1333 | width: usize, |
1334 | height: usize, |
1335 | } |
1336 | |
1337 | impl Dim { |
1338 | fn new(width: usize, height: usize) -> Self { |
1339 | Self { width, height } |
1340 | } |
1341 | } |
1342 | |
1343 | #[derive (Debug, Default)] |
1344 | struct ArrayDimensions { |
1345 | max: Dim, |
1346 | index: HashMap<usize, usize>, |
1347 | } |
1348 | |
1349 | fn collect_table_dimensions(val: &TableValue, cfg: &CompactMultilineConfig) -> Dimensions { |
1350 | let mut buf = Dimensions::default(); |
1351 | let (dim, _) = __collect_table_dims(&mut buf, val, cfg, 0); |
1352 | let _ = buf.all.insert(0, dim); |
1353 | buf |
1354 | } |
1355 | |
1356 | fn __collect_table_dims( |
1357 | buf: &mut Dimensions, |
1358 | val: &TableValue, |
1359 | cfg: &CompactMultilineConfig, |
1360 | pos: usize, |
1361 | ) -> (Dim, usize) { |
1362 | match val { |
1363 | TableValue::Cell(text) => (str_dimension(text, cfg), 0), |
1364 | TableValue::Row(list) => { |
1365 | if list.is_empty() { |
1366 | return (empty_dimension(cfg), 0); |
1367 | } |
1368 | |
1369 | let mut index = ArrayDimensions { |
1370 | max: Dim::default(), |
1371 | index: HashMap::with_capacity(list.len()), |
1372 | }; |
1373 | |
1374 | let mut total_width = 0; |
1375 | |
1376 | let mut count_elements = list.len(); |
1377 | let mut val_pos = pos + 1; |
1378 | for (i, value) in list.iter().enumerate() { |
1379 | let (dim, elements) = __collect_table_dims(buf, value, cfg, val_pos); |
1380 | count_elements += elements; |
1381 | |
1382 | total_width += dim.width; |
1383 | |
1384 | index.max.width = max(index.max.width, dim.width); |
1385 | index.max.height = max(index.max.height, dim.height); |
1386 | |
1387 | let _ = buf.all.insert(val_pos, dim); |
1388 | |
1389 | let _ = index.index.insert(i, val_pos); |
1390 | |
1391 | val_pos += 1 + elements; |
1392 | } |
1393 | |
1394 | let max_height = index.max.height; |
1395 | |
1396 | let _ = buf.arrays.insert(pos, index); |
1397 | |
1398 | let has_vertical = cfg.get_borders().has_left(); |
1399 | total_width += has_vertical as usize * (list.len() - 1); |
1400 | |
1401 | (Dim::new(total_width, max_height), count_elements) |
1402 | } |
1403 | TableValue::Column(list) => { |
1404 | if list.is_empty() { |
1405 | return (empty_dimension(cfg), 0); |
1406 | } |
1407 | |
1408 | let mut index = ArrayDimensions { |
1409 | max: Dim::default(), |
1410 | index: HashMap::with_capacity(list.len()), |
1411 | }; |
1412 | |
1413 | let mut total_height = 0; |
1414 | |
1415 | let mut count_elements = list.len(); |
1416 | let mut val_pos = pos + 1; |
1417 | for (i, value) in list.iter().enumerate() { |
1418 | let (dim, elements) = __collect_table_dims(buf, value, cfg, val_pos); |
1419 | count_elements += elements; |
1420 | |
1421 | total_height += dim.height; |
1422 | |
1423 | index.max.width = max(index.max.width, dim.width); |
1424 | index.max.height = max(index.max.height, dim.height); |
1425 | |
1426 | let _ = buf.all.insert(val_pos, dim); |
1427 | |
1428 | let _ = index.index.insert(i, val_pos); |
1429 | |
1430 | val_pos += 1 + elements; |
1431 | } |
1432 | |
1433 | let max_width = index.max.width; |
1434 | |
1435 | let _ = buf.arrays.insert(pos, index); |
1436 | |
1437 | let has_horizontal = cfg.get_borders().has_top(); |
1438 | total_height += has_horizontal as usize * (list.len() - 1); |
1439 | |
1440 | (Dim::new(max_width, total_height), count_elements) |
1441 | } |
1442 | } |
1443 | } |
1444 | |
1445 | fn empty_dimension(cfg: &CompactMultilineConfig) -> Dim { |
1446 | Dim::new(get_padding_horizontal(cfg), 1 + get_padding_vertical(cfg)) |
1447 | } |
1448 | |
1449 | fn str_dimension(text: &str, cfg: &CompactMultilineConfig) -> Dim { |
1450 | let (count_lines, width) = string_dimension(text); |
1451 | let w = width + get_padding_horizontal(cfg); |
1452 | let h = count_lines + get_padding_vertical(cfg); |
1453 | Dim::new(w, h) |
1454 | } |
1455 | |
1456 | fn get_padding_horizontal(cfg: &CompactMultilineConfig) -> usize { |
1457 | let pad = cfg.get_padding(); |
1458 | pad.left.size + pad.right.size |
1459 | } |
1460 | |
1461 | fn get_padding_vertical(cfg: &CompactMultilineConfig) -> usize { |
1462 | let pad = cfg.get_padding(); |
1463 | pad.top.size + pad.bottom.size |
1464 | } |
1465 | |
1466 | fn split_value(value: usize, by: usize) -> (usize, usize) { |
1467 | let val = value / by; |
1468 | let rest = value - val * by; |
1469 | (val, rest) |
1470 | } |
1471 | |
1472 | fn indent_vertical(al: AlignmentVertical, available: usize, real: usize) -> (usize, usize) { |
1473 | let top = indent_top(al, available, real); |
1474 | let bottom = available - real - top; |
1475 | (top, bottom) |
1476 | } |
1477 | |
1478 | fn indent_horizontal(al: AlignmentHorizontal, available: usize, real: usize) -> (usize, usize) { |
1479 | let top = indent_left(al, available, real); |
1480 | let right = available - real - top; |
1481 | (top, right) |
1482 | } |
1483 | |
1484 | fn indent_top(al: AlignmentVertical, available: usize, real: usize) -> usize { |
1485 | match al { |
1486 | AlignmentVertical::Top => 0, |
1487 | AlignmentVertical::Bottom => available - real, |
1488 | AlignmentVertical::Center => (available - real) / 2, |
1489 | } |
1490 | } |
1491 | |
1492 | fn indent_left(al: AlignmentHorizontal, available: usize, real: usize) -> usize { |
1493 | match al { |
1494 | AlignmentHorizontal::Left => 0, |
1495 | AlignmentHorizontal::Right => available - real, |
1496 | AlignmentHorizontal::Center => (available - real) / 2, |
1497 | } |
1498 | } |
1499 | |
1500 | fn short_splits(splits: &mut Vec<usize>, width: usize) -> Vec<usize> { |
1501 | if splits.is_empty() { |
1502 | return Vec::new(); |
1503 | } |
1504 | |
1505 | let mut out = Vec::new(); |
1506 | let mut pos = 0; |
1507 | for &split in splits.iter() { |
1508 | if pos + split >= width { |
1509 | break; |
1510 | } |
1511 | |
1512 | pos += split; |
1513 | out.push(pos); |
1514 | } |
1515 | |
1516 | let _ = splits.drain(..out.len()); |
1517 | |
1518 | if !splits.is_empty() && pos <= width { |
1519 | let rest = width - pos; |
1520 | splits[0] -= rest; |
1521 | } |
1522 | |
1523 | out |
1524 | } |
1525 | |
1526 | fn short_splits3(splits: &mut Vec<usize>, width: usize) -> (bool, Vec<usize>) { |
1527 | if splits.is_empty() { |
1528 | return (false, Vec::new()); |
1529 | } |
1530 | |
1531 | let mut out = Vec::new(); |
1532 | let mut pos = 0; |
1533 | for &split in splits.iter() { |
1534 | if pos + split >= width { |
1535 | break; |
1536 | } |
1537 | |
1538 | pos += split + 1; |
1539 | out.push(split); |
1540 | } |
1541 | |
1542 | let _ = splits.drain(..out.len()); |
1543 | |
1544 | if splits.is_empty() { |
1545 | return (false, out); |
1546 | } |
1547 | |
1548 | if pos <= width { |
1549 | splits[0] -= width - pos; |
1550 | if splits[0] > 0 { |
1551 | splits[0] -= 1; |
1552 | } else { |
1553 | let _ = splits.remove(0); |
1554 | return (true, out); |
1555 | } |
1556 | } |
1557 | |
1558 | (false, out) |
1559 | } |
1560 | |
1561 | fn squash_splits(splits: &mut [usize]) { |
1562 | splits.iter_mut().enumerate().for_each(|(i, s)| *s += i); |
1563 | } |
1564 | |
1565 | fn set_margin( |
1566 | table: &str, |
1567 | margin: Sides<Indent>, |
1568 | color: Sides<Option<ANSIStr<'static>>>, |
1569 | ) -> String { |
1570 | if table.is_empty() { |
1571 | return String::new(); |
1572 | } |
1573 | |
1574 | let mut buf = String::new(); |
1575 | let width = string_width_multiline(table); |
1576 | let top_color = color.top; |
1577 | let bottom_color = color.bottom; |
1578 | let left_color = color.left; |
1579 | let right_color = color.right; |
1580 | for _ in 0..margin.top.size { |
1581 | print_chars(&mut buf, margin.left.fill, left_color, margin.left.size); |
1582 | print_chars(&mut buf, margin.top.fill, top_color, width); |
1583 | print_chars(&mut buf, margin.right.fill, right_color, margin.right.size); |
1584 | buf.push(' \n' ); |
1585 | } |
1586 | |
1587 | for line in get_lines(table) { |
1588 | print_chars(&mut buf, margin.left.fill, left_color, margin.left.size); |
1589 | buf.push_str(&line); |
1590 | print_chars(&mut buf, margin.right.fill, right_color, margin.right.size); |
1591 | buf.push(' \n' ); |
1592 | } |
1593 | |
1594 | for _ in 0..margin.bottom.size { |
1595 | print_chars(&mut buf, margin.left.fill, left_color, margin.left.size); |
1596 | print_chars(&mut buf, margin.bottom.fill, bottom_color, width); |
1597 | print_chars(&mut buf, margin.right.fill, right_color, margin.right.size); |
1598 | buf.push(' \n' ); |
1599 | } |
1600 | |
1601 | let _ = buf.remove(buf.len() - 1); |
1602 | |
1603 | buf |
1604 | } |
1605 | |
1606 | fn convert_border_colors( |
1607 | pad_color: Sides<ANSIStr<'static>>, |
1608 | ) -> Sides<Option<ANSIStr<'static>>> { |
1609 | Sides::new( |
1610 | (!pad_color.left.is_empty()).then(|| pad_color.left), |
1611 | (!pad_color.right.is_empty()).then(|| pad_color.right), |
1612 | (!pad_color.top.is_empty()).then(|| pad_color.top), |
1613 | (!pad_color.bottom.is_empty()).then(|| pad_color.bottom), |
1614 | ) |
1615 | } |
1616 | |
1617 | fn cfg_clear_borders(cfg: &mut ColoredConfig) { |
1618 | cfg.remove_borders(); |
1619 | cfg.remove_borders_colors(); |
1620 | cfg.remove_vertical_chars(); |
1621 | cfg.remove_horizontal_chars(); |
1622 | cfg.remove_color_line_horizontal(); |
1623 | cfg.remove_color_line_vertical(); |
1624 | } |
1625 | } |
1626 | |