1use std::cmp::max;
95use std::fmt;
96use std::iter::repeat_n;
97use unicode_width::UnicodeWidthStr;
98
99fn unicode_width_strip_ansi(astring: &str) -> usize {
100 nu_utils::strip_ansi_unlikely(astring).width()
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum Alignment {
107 Left,
109
110 Right,
112}
113
114#[derive(PartialEq, Eq, Debug, Clone)]
120pub struct Cell {
121 pub contents: String,
123
124 pub width: Width,
126
127 pub alignment: Alignment,
129}
130
131impl From<String> for Cell {
132 fn from(string: String) -> Self {
133 Self {
134 width: unicode_width_strip_ansi(&string),
135 contents: string,
136 alignment: Alignment::Left,
137 }
138 }
139}
140
141impl<'a> From<&'a str> for Cell {
142 fn from(string: &'a str) -> Self {
143 Self {
144 width: unicode_width_strip_ansi(string),
145 contents: string.into(),
146 alignment: Alignment::Left,
147 }
148 }
149}
150
151#[derive(PartialEq, Eq, Debug, Copy, Clone)]
153pub enum Direction {
154 LeftToRight,
157
158 TopToBottom,
161}
162
163pub type Width = usize;
165
166#[derive(PartialEq, Eq, Debug)]
169pub enum Filling {
170 Spaces(Width),
172
173 Text(String),
176}
177
178impl Filling {
179 fn width(&self) -> Width {
180 match *self {
181 Filling::Spaces(w) => w,
182 Filling::Text(ref t) => unicode_width_strip_ansi(&t[..]),
183 }
184 }
185}
186
187#[derive(PartialEq, Eq, Debug)]
190pub struct GridOptions {
191 pub direction: Direction,
194
195 pub filling: Filling,
197}
198
199#[derive(PartialEq, Eq, Debug)]
200struct Dimensions {
201 num_lines: Width,
203
204 widths: Vec<Width>,
207}
208
209impl Dimensions {
210 fn total_width(&self, separator_width: Width) -> Width {
211 if self.widths.is_empty() {
212 0
213 } else {
214 let values = self.widths.iter().sum::<Width>();
215 let separators = separator_width * (self.widths.len() - 1);
216 values + separators
217 }
218 }
219}
220
221#[derive(Eq, PartialEq, Debug)]
225pub struct Grid {
226 options: GridOptions,
227 cells: Vec<Cell>,
228 widest_cell_length: Width,
229 width_sum: Width,
230 cell_count: usize,
231}
232
233impl Grid {
234 pub fn new(options: GridOptions) -> Self {
236 let cells = Vec::new();
237 Self {
238 options,
239 cells,
240 widest_cell_length: 0,
241 width_sum: 0,
242 cell_count: 0,
243 }
244 }
245
246 pub fn reserve(&mut self, additional: usize) {
249 self.cells.reserve(additional)
250 }
251
252 pub fn add(&mut self, cell: Cell) {
254 if cell.width > self.widest_cell_length {
255 self.widest_cell_length = cell.width;
256 }
257 self.width_sum += cell.width;
258 self.cell_count += 1;
259 self.cells.push(cell)
260 }
261
262 pub fn fit_into_width(&self, maximum_width: Width) -> Option<Display<'_>> {
268 self.width_dimensions(maximum_width).map(|dims| Display {
269 grid: self,
270 dimensions: dims,
271 })
272 }
273
274 pub fn fit_into_columns(&self, num_columns: usize) -> Display<'_> {
277 Display {
278 grid: self,
279 dimensions: self.columns_dimensions(num_columns),
280 }
281 }
282
283 fn columns_dimensions(&self, num_columns: usize) -> Dimensions {
284 let mut num_lines = self.cells.len() / num_columns;
285 if !self.cells.len().is_multiple_of(num_columns) {
286 num_lines += 1;
287 }
288
289 self.column_widths(num_lines, num_columns)
290 }
291
292 fn column_widths(&self, num_lines: usize, num_columns: usize) -> Dimensions {
293 let mut widths: Vec<Width> = repeat_n(0, num_columns).collect();
294 for (index, cell) in self.cells.iter().enumerate() {
295 let index = match self.options.direction {
296 Direction::LeftToRight => index % num_columns,
297 Direction::TopToBottom => index / num_lines,
298 };
299 widths[index] = max(widths[index], cell.width);
300 }
301
302 Dimensions { num_lines, widths }
303 }
304
305 fn theoretical_max_num_lines(&self, maximum_width: usize) -> usize {
306 let mut theoretical_min_num_cols = 0;
307 let mut col_total_width_so_far = 0;
308
309 let mut cells = self.cells.clone();
310 cells.sort_unstable_by(|a, b| b.width.cmp(&a.width)); for cell in &cells {
313 if cell.width + col_total_width_so_far <= maximum_width {
314 theoretical_min_num_cols += 1;
315 col_total_width_so_far += cell.width;
316 } else {
317 let mut theoretical_max_num_lines = self.cell_count / theoretical_min_num_cols;
318 if !self.cell_count.is_multiple_of(theoretical_min_num_cols) {
319 theoretical_max_num_lines += 1;
320 }
321 return theoretical_max_num_lines;
322 }
323 col_total_width_so_far += self.options.filling.width()
324 }
325
326 1
330 }
331
332 fn width_dimensions(&self, maximum_width: Width) -> Option<Dimensions> {
333 if self.widest_cell_length > maximum_width {
334 return None;
336 }
337
338 if self.cell_count == 0 {
339 return Some(Dimensions {
340 num_lines: 0,
341 widths: Vec::new(),
342 });
343 }
344
345 if self.cell_count == 1 {
346 let the_cell = &self.cells[0];
347 return Some(Dimensions {
348 num_lines: 1,
349 widths: vec![the_cell.width],
350 });
351 }
352
353 let theoretical_max_num_lines = self.theoretical_max_num_lines(maximum_width);
354 if theoretical_max_num_lines == 1 {
355 return Some(Dimensions {
358 num_lines: 1,
359 widths: self
363 .cells
364 .clone()
365 .into_iter()
366 .map(|cell| cell.width)
367 .collect(),
368 });
369 }
370 let mut smallest_dimensions_yet = None;
373 for num_lines in (1..=theoretical_max_num_lines).rev() {
374 let mut num_columns = self.cell_count / num_lines;
377 if !self.cell_count.is_multiple_of(num_lines) {
378 num_columns += 1;
379 }
380 let total_separator_width = (num_columns - 1) * self.options.filling.width();
387 if maximum_width < total_separator_width {
388 continue;
389 }
390
391 let adjusted_width = maximum_width - total_separator_width;
393 let potential_dimensions = self.column_widths(num_lines, num_columns);
394 if potential_dimensions.widths.iter().sum::<Width>() < adjusted_width {
395 smallest_dimensions_yet = Some(potential_dimensions);
396 } else {
397 return smallest_dimensions_yet;
398 }
399 }
400
401 None
402 }
403}
404
405#[derive(Eq, PartialEq, Debug)]
410pub struct Display<'grid> {
411 grid: &'grid Grid,
413
414 dimensions: Dimensions,
416}
417
418impl Display<'_> {
419 pub fn width(&self) -> Width {
422 self.dimensions
423 .total_width(self.grid.options.filling.width())
424 }
425
426 pub fn row_count(&self) -> usize {
428 self.dimensions.num_lines
429 }
430
431 pub fn is_complete(&self) -> bool {
439 self.dimensions.widths.iter().all(|&x| x > 0)
440 }
441}
442
443impl fmt::Display for Display<'_> {
444 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
445 for y in 0..self.dimensions.num_lines {
446 for x in 0..self.dimensions.widths.len() {
447 let num = match self.grid.options.direction {
448 Direction::LeftToRight => y * self.dimensions.widths.len() + x,
449 Direction::TopToBottom => y + self.dimensions.num_lines * x,
450 };
451
452 if num >= self.grid.cells.len() {
454 continue;
455 }
456
457 let cell = &self.grid.cells[num];
458 if x == self.dimensions.widths.len() - 1 {
459 match cell.alignment {
460 Alignment::Left => {
461 write!(f, "{}", cell.contents)?;
464 }
465 Alignment::Right => {
466 let extra_spaces = self.dimensions.widths[x] - cell.width;
467 write!(
468 f,
469 "{}",
470 pad_string(&cell.contents, extra_spaces, Alignment::Right)
471 )?;
472 }
473 }
474 } else {
475 assert!(self.dimensions.widths[x] >= cell.width);
476 match (&self.grid.options.filling, cell.alignment) {
477 (Filling::Spaces(n), Alignment::Left) => {
478 let extra_spaces = self.dimensions.widths[x] - cell.width + n;
479 write!(
480 f,
481 "{}",
482 pad_string(&cell.contents, extra_spaces, cell.alignment)
483 )?;
484 }
485 (Filling::Spaces(n), Alignment::Right) => {
486 let s = spaces(*n);
487 let extra_spaces = self.dimensions.widths[x] - cell.width;
488 write!(
489 f,
490 "{}{}",
491 pad_string(&cell.contents, extra_spaces, cell.alignment),
492 s
493 )?;
494 }
495 (Filling::Text(t), _) => {
496 let extra_spaces = self.dimensions.widths[x] - cell.width;
497 write!(
498 f,
499 "{}{}",
500 pad_string(&cell.contents, extra_spaces, cell.alignment),
501 t
502 )?;
503 }
504 }
505 }
506 }
507
508 writeln!(f)?;
509 }
510
511 Ok(())
512 }
513}
514
515fn spaces(length: usize) -> String {
517 " ".repeat(length)
518}
519
520fn pad_string(string: &str, padding: usize, alignment: Alignment) -> String {
525 if alignment == Alignment::Left {
526 format!("{}{}", string, spaces(padding))
527 } else {
528 format!("{}{}", spaces(padding), string)
529 }
530}
531
532#[cfg(test)]
533mod test {
534 use super::*;
535
536 #[test]
537 fn no_items() {
538 let grid = Grid::new(GridOptions {
539 direction: Direction::TopToBottom,
540 filling: Filling::Spaces(2),
541 });
542
543 let display = grid.fit_into_width(40).unwrap();
544
545 assert_eq!(display.dimensions.num_lines, 0);
546 assert!(display.dimensions.widths.is_empty());
547
548 assert_eq!(display.width(), 0);
549 }
550
551 #[test]
552 fn one_item() {
553 let mut grid = Grid::new(GridOptions {
554 direction: Direction::TopToBottom,
555 filling: Filling::Spaces(2),
556 });
557
558 grid.add(Cell::from("1"));
559
560 let display = grid.fit_into_width(40).unwrap();
561
562 assert_eq!(display.dimensions.num_lines, 1);
563 assert_eq!(display.dimensions.widths, vec![1]);
564
565 assert_eq!(display.width(), 1);
566 }
567
568 #[test]
569 fn one_item_exact_width() {
570 let mut grid = Grid::new(GridOptions {
571 direction: Direction::TopToBottom,
572 filling: Filling::Spaces(2),
573 });
574
575 grid.add(Cell::from("1234567890"));
576
577 let display = grid.fit_into_width(10).unwrap();
578
579 assert_eq!(display.dimensions.num_lines, 1);
580 assert_eq!(display.dimensions.widths, vec![10]);
581
582 assert_eq!(display.width(), 10);
583 }
584
585 #[test]
586 fn one_item_just_over() {
587 let mut grid = Grid::new(GridOptions {
588 direction: Direction::TopToBottom,
589 filling: Filling::Spaces(2),
590 });
591
592 grid.add(Cell::from("1234567890!"));
593
594 assert_eq!(grid.fit_into_width(10), None);
595 }
596
597 #[test]
598 fn two_small_items() {
599 let mut grid = Grid::new(GridOptions {
600 direction: Direction::TopToBottom,
601 filling: Filling::Spaces(2),
602 });
603
604 grid.add(Cell::from("1"));
605 grid.add(Cell::from("2"));
606
607 let display = grid.fit_into_width(40).unwrap();
608
609 assert_eq!(display.dimensions.num_lines, 1);
610 assert_eq!(display.dimensions.widths, vec![1, 1]);
611
612 assert_eq!(display.width(), 1 + 2 + 1);
613 }
614
615 #[test]
616 fn two_medium_size_items() {
617 let mut grid = Grid::new(GridOptions {
618 direction: Direction::TopToBottom,
619 filling: Filling::Spaces(2),
620 });
621
622 grid.add(Cell::from("hello there"));
623 grid.add(Cell::from("how are you today?"));
624
625 let display = grid.fit_into_width(40).unwrap();
626
627 assert_eq!(display.dimensions.num_lines, 1);
628 assert_eq!(display.dimensions.widths, vec![11, 18]);
629
630 assert_eq!(display.width(), 11 + 2 + 18);
631 }
632
633 #[test]
634 fn two_big_items() {
635 let mut grid = Grid::new(GridOptions {
636 direction: Direction::TopToBottom,
637 filling: Filling::Spaces(2),
638 });
639
640 grid.add(Cell::from(
641 "nuihuneihsoenhisenouiuteinhdauisdonhuisudoiosadiuohnteihaosdinhteuieudi",
642 ));
643 grid.add(Cell::from(
644 "oudisnuthasuouneohbueobaugceoduhbsauglcobeuhnaeouosbubaoecgueoubeohubeo",
645 ));
646
647 assert_eq!(grid.fit_into_width(40), None);
648 }
649
650 #[test]
651 fn that_example_from_earlier() {
652 let mut grid = Grid::new(GridOptions {
653 filling: Filling::Spaces(1),
654 direction: Direction::LeftToRight,
655 });
656
657 for s in &[
658 "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
659 "eleven", "twelve",
660 ] {
661 grid.add(Cell::from(*s));
662 }
663
664 let bits = "one two three four\nfive six seven eight\nnine ten eleven twelve\n";
665 assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
666 assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
667 }
668
669 #[test]
670 fn number_grid_with_pipe() {
671 let mut grid = Grid::new(GridOptions {
672 filling: Filling::Text("|".into()),
673 direction: Direction::LeftToRight,
674 });
675
676 for s in &[
677 "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
678 "eleven", "twelve",
679 ] {
680 grid.add(Cell::from(*s));
681 }
682
683 let bits = "one |two|three |four\nfive|six|seven |eight\nnine|ten|eleven|twelve\n";
684 assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
685 assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
686 }
687
688 #[test]
689 fn numbers_right() {
690 let mut grid = Grid::new(GridOptions {
691 filling: Filling::Spaces(1),
692 direction: Direction::LeftToRight,
693 });
694
695 for s in &[
696 "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
697 "eleven", "twelve",
698 ] {
699 let mut cell = Cell::from(*s);
700 cell.alignment = Alignment::Right;
701 grid.add(cell);
702 }
703
704 let bits = " one two three four\nfive six seven eight\nnine ten eleven twelve\n";
705 assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
706 assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
707 }
708
709 #[test]
710 fn numbers_right_pipe() {
711 let mut grid = Grid::new(GridOptions {
712 filling: Filling::Text("|".into()),
713 direction: Direction::LeftToRight,
714 });
715
716 for s in &[
717 "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
718 "eleven", "twelve",
719 ] {
720 let mut cell = Cell::from(*s);
721 cell.alignment = Alignment::Right;
722 grid.add(cell);
723 }
724
725 let bits = " one|two| three| four\nfive|six| seven| eight\nnine|ten|eleven|twelve\n";
726 assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
727 assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
728 }
729
730 #[test]
731 fn huge_separator() {
732 let mut grid = Grid::new(GridOptions {
733 filling: Filling::Spaces(100),
734 direction: Direction::LeftToRight,
735 });
736
737 grid.add("a".into());
738 grid.add("b".into());
739
740 assert_eq!(grid.fit_into_width(99), None);
741 }
742
743 #[test]
744 fn huge_yet_unused_separator() {
745 let mut grid = Grid::new(GridOptions {
746 filling: Filling::Spaces(100),
747 direction: Direction::LeftToRight,
748 });
749
750 grid.add("abcd".into());
751
752 let display = grid.fit_into_width(99).unwrap();
753
754 assert_eq!(display.dimensions.num_lines, 1);
755 assert_eq!(display.dimensions.widths, vec![4]);
756
757 assert_eq!(display.width(), 4);
758 }
759}