nu_term_grid/
grid.rs

1// Thanks to https://github.com/ogham/rust-term-grid for making this available
2
3//! This library arranges textual data in a grid format suitable for
4//! fixed-width fonts, using an algorithm to minimise the amount of space
5//! needed. For example:
6//!
7//! ```rust
8//! use nu_term_grid::grid::{Grid, GridOptions, Direction, Filling, Cell};
9//!
10//! let mut grid = Grid::new(GridOptions {
11//!     filling:    Filling::Spaces(1),
12//!     direction:  Direction::LeftToRight,
13//! });
14//!
15//! for s in &["one", "two", "three", "four", "five", "six", "seven",
16//!            "eight", "nine", "ten", "eleven", "twelve"]
17//! {
18//!     grid.add(Cell::from(*s));
19//! }
20//!
21//! println!("{}", grid.fit_into_width(24).unwrap());
22//! ```
23//!
24//! Produces the following tabular result:
25//!
26//! ```text
27//! one  two three  four
28//! five six seven  eight
29//! nine ten eleven twelve
30//! ```
31//!
32//!
33//! ## Creating a grid
34//!
35//! To add data to a grid, first create a new [`Grid`] value, and then add
36//! cells to them with the `add` function.
37//!
38//! There are two options that must be specified in the [`GridOptions`] value
39//! that dictate how the grid is formatted:
40//!
41//! - `filling`: what to put in between two columns — either a number of
42//!   spaces, or a text string;
43//! - `direction`, which specifies whether the cells should go along
44//!   rows, or columns:
45//!     - `Direction::LeftToRight` starts them in the top left and
46//!       moves *rightwards*, going to the start of a new row after reaching the
47//!       final column;
48//!     - `Direction::TopToBottom` starts them in the top left and moves
49//!       *downwards*, going to the top of a new column after reaching the final
50//!       row.
51//!
52//!
53//! ## Displaying a grid
54//!
55//! When display a grid, you can either specify the number of columns in advance,
56//! or try to find the maximum number of columns that can fit in an area of a
57//! given width.
58//!
59//! Splitting a series of cells into columns — or, in other words, starting a new
60//! row every <var>n</var> cells — is achieved with the [`fit_into_columns`] function
61//! on a `Grid` value. It takes as its argument the number of columns.
62//!
63//! Trying to fit as much data onto one screen as possible is the main use case
64//! for specifying a maximum width instead. This is achieved with the
65//! [`fit_into_width`] function. It takes the maximum allowed width, including
66//! separators, as its argument. However, it returns an *optional* [`Display`]
67//! value, depending on whether any of the cells actually had a width greater than
68//! the maximum width! If this is the case, your best bet is to just output the
69//! cells with one per line.
70//!
71//!
72//! ## Cells and data
73//!
74//! Grids to not take `String`s or `&str`s — they take [`Cell`] values.
75//!
76//! A **Cell** is a struct containing an individual cell’s contents, as a string,
77//! and its pre-computed length, which gets used when calculating a grid’s final
78//! dimensions. Usually, you want the *Unicode width* of the string to be used for
79//! this, so you can turn a `String` into a `Cell` with the `.into()` function.
80//!
81//! However, you may also want to supply your own width: when you already know the
82//! width in advance, or when you want to change the measurement, such as skipping
83//! over terminal control characters. For cases like these, the fields on the
84//! `Cell` values are public, meaning you can construct your own instances as
85//! necessary.
86//!
87//! [`Cell`]: ./struct.Cell.html
88//! [`Display`]: ./struct.Display.html
89//! [`Grid`]: ./struct.Grid.html
90//! [`fit_into_columns`]: ./struct.Grid.html#method.fit_into_columns
91//! [`fit_into_width`]: ./struct.Grid.html#method.fit_into_width
92//! [`GridOptions`]: ./struct.GridOptions.html
93
94use 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/// Alignment indicate on which side the content should stick if some filling
104/// is required.
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum Alignment {
107    /// The content will stick to the left.
108    Left,
109
110    /// The content will stick to the right.
111    Right,
112}
113
114/// A **Cell** is the combination of a string and its pre-computed length.
115///
116/// The easiest way to create a Cell is just by using `string.into()`, which
117/// uses the **unicode width** of the string (see the `unicode_width` crate).
118/// However, the fields are public, if you wish to provide your own length.
119#[derive(PartialEq, Eq, Debug, Clone)]
120pub struct Cell {
121    /// The string to display when this cell gets rendered.
122    pub contents: String,
123
124    /// The pre-computed length of the string.
125    pub width: Width,
126
127    /// The side (left/right) to align the content if some filling is required.
128    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/// Direction cells should be written in — either across, or downwards.
152#[derive(PartialEq, Eq, Debug, Copy, Clone)]
153pub enum Direction {
154    /// Starts at the top left and moves rightwards, going back to the first
155    /// column for a new row, like a typewriter.
156    LeftToRight,
157
158    /// Starts at the top left and moves downwards, going back to the first
159    /// row for a new column, like how `ls` lists files by default.
160    TopToBottom,
161}
162
163/// The width of a cell, in columns.
164pub type Width = usize;
165
166/// The text to put in between each pair of columns.
167/// This does not include any spaces used when aligning cells.
168#[derive(PartialEq, Eq, Debug)]
169pub enum Filling {
170    /// A certain number of spaces should be used as the separator.
171    Spaces(Width),
172
173    /// An arbitrary string.
174    /// `"|"` is a common choice.
175    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/// The user-assignable options for a grid view that should be passed to
188/// [`Grid::new()`](struct.Grid.html#method.new).
189#[derive(PartialEq, Eq, Debug)]
190pub struct GridOptions {
191    /// The direction that the cells should be written in — either
192    /// across, or downwards.
193    pub direction: Direction,
194
195    /// The number of spaces to put in between each column of cells.
196    pub filling: Filling,
197}
198
199#[derive(PartialEq, Eq, Debug)]
200struct Dimensions {
201    /// The number of lines in the grid.
202    num_lines: Width,
203
204    /// The width of each column in the grid. The length of this vector serves
205    /// as the number of columns.
206    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/// Everything needed to format the cells with the grid options.
222///
223/// For more information, see the [`grid` crate documentation](index.html).
224#[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    /// Creates a new grid view with the given options.
235    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    /// Reserves space in the vector for the given number of additional cells
247    /// to be added. (See the `Vec::reserve` function.)
248    pub fn reserve(&mut self, additional: usize) {
249        self.cells.reserve(additional)
250    }
251
252    /// Adds another cell onto the vector.
253    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    /// Returns a displayable grid that’s been packed to fit into the given
263    /// width in the fewest number of rows.
264    ///
265    /// Returns `None` if any of the cells has a width greater than the
266    /// maximum width.
267    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    /// Returns a displayable grid with the given number of columns, and no
275    /// maximum width.
276    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)); // Sort in reverse order
311
312        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        // If we make it to this point, we have exhausted all cells before
327        // reaching the maximum width; the theoretical max number of lines
328        // needed to display all cells is 1.
329        1
330    }
331
332    fn width_dimensions(&self, maximum_width: Width) -> Option<Dimensions> {
333        if self.widest_cell_length > maximum_width {
334            // Largest cell is wider than maximum width; it is impossible to fit.
335            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            // This if—statement is necessary for the function to work correctly
356            // for small inputs.
357            return Some(Dimensions {
358                num_lines: 1,
359                // I clone self.cells twice. Once here, and once in
360                // self.theoretical_max_num_lines. Perhaps not the best for
361                // performance?
362                widths: self
363                    .cells
364                    .clone()
365                    .into_iter()
366                    .map(|cell| cell.width)
367                    .collect(),
368            });
369        }
370        // Instead of numbers of columns, try to find the fewest number of *lines*
371        // that the output will fit in.
372        let mut smallest_dimensions_yet = None;
373        for num_lines in (1..=theoretical_max_num_lines).rev() {
374            // The number of columns is the number of cells divided by the number
375            // of lines, *rounded up*.
376            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            // Early abort: if there are so many columns that the width of the
381            // *column separators* is bigger than the width of the screen, then
382            // don’t even try to tabulate it.
383            // This is actually a necessary check, because the width is stored as
384            // a usize, and making it go negative makes it huge instead, but it
385            // also serves as a speed-up.
386            let total_separator_width = (num_columns - 1) * self.options.filling.width();
387            if maximum_width < total_separator_width {
388                continue;
389            }
390
391            // Remove the separator width from the available space.
392            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/// A displayable representation of a [`Grid`](struct.Grid.html).
406///
407/// This type implements `Display`, so you can get the textual version
408/// of the grid by calling `.to_string()`.
409#[derive(Eq, PartialEq, Debug)]
410pub struct Display<'grid> {
411    /// The grid to display.
412    grid: &'grid Grid,
413
414    /// The pre-computed column widths for this grid.
415    dimensions: Dimensions,
416}
417
418impl Display<'_> {
419    /// Returns how many columns this display takes up, based on the separator
420    /// width and the number and width of the columns.
421    pub fn width(&self) -> Width {
422        self.dimensions
423            .total_width(self.grid.options.filling.width())
424    }
425
426    /// Returns how many rows this display takes up.
427    pub fn row_count(&self) -> usize {
428        self.dimensions.num_lines
429    }
430
431    /// Returns whether this display takes up as many columns as were allotted
432    /// to it.
433    ///
434    /// It’s possible to construct tables that don’t actually use up all the
435    /// columns that they could, such as when there are more columns than
436    /// cells! In this case, a column would have a width of zero. This just
437    /// checks for that.
438    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                // Abandon a line mid-way through if that’s where the cells end
453                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                            // The final column doesn’t need to have trailing spaces,
462                            // as long as it’s left-aligned.
463                            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
515/// Pad a string with the given number of spaces.
516fn spaces(length: usize) -> String {
517    " ".repeat(length)
518}
519
520/// Pad a string with the given alignment and number of spaces.
521///
522/// This doesn’t take the width the string *should* be, rather the number
523/// of spaces to add.
524fn 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}