Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Column Formatting

Each column has flags that control how its data is rendered.

Alignment

Columns default to left-aligned. Set right or center alignment on the column, and all cells in that column will follow. Individual cells can override the column’s alignment via cell.alignment_set().

use cols::{Column, Alignment};

Column::new("SIZE").right(true);            // right-align
Column::new("STATUS").center(true);         // center-align
Column::new("PRICE").align(Alignment::Right); // equivalent to .right(true)

Right alignment

use cols::{Table, Column, print_table};

fn main() {
    let mut t = Table::new();
    t.termwidth_set(40);
    t.add_column(Column::new("LEFT").width_fixed(10));
    t.add_column(Column::new("RIGHT").width_fixed(10).right(true));

    let l1 = t.new_line(None);
    t.line_mut(l1).data_set(0, "a").data_set(1, "1");

    let l2 = t.new_line(None);
    t.line_mut(l2).data_set(0, "bb").data_set(1, "22");

    let l3 = t.new_line(None);
    t.line_mut(l3).data_set(0, "ccc").data_set(1, "333");

    print_table(&t, &mut std::io::stdout().lock()).unwrap();
}
LEFT            RIGHT
a                   1
bb                 22
ccc               333

Truncation

Truncate data that exceeds the column width:

use cols::{Table, Column, TermForce, print_table};

fn main() {
    let mut t = Table::new();
    t.termforce_set(TermForce::Always);
    t.termwidth_set(20);
    t.headings_set(false);
    t.add_column(Column::new("A").width_fixed(8).truncate(true));
    t.add_column(Column::new("B").width_fixed(8));

    let row = t.new_line(None);
    t.line_mut(row)
        .data_set(0, "this-is-very-long")
        .data_set(1, "short");

    print_table(&t, &mut std::io::stdout().lock()).unwrap();
}
this-is-ver short

Wrapping

Wrap long data across multiple output lines:

use cols::{Table, Column, TermForce, print_table};

fn main() {
    let mut t = Table::new();
    t.termwidth_set(30);
    t.termforce_set(TermForce::Always);
    t.add_column(Column::new("ID").width_fixed(4));
    t.add_column(Column::new("TEXT").wrap(true));

    let l1 = t.new_line(None);
    t.line_mut(l1).data_set(0, "1").data_set(1, "Short text");

    let l2 = t.new_line(None);
    t.line_mut(l2)
        .data_set(0, "2")
        .data_set(1, "A longer description that wraps");

    print_table(&t, &mut std::io::stdout().lock()).unwrap();
}
ID   TEXT
1    Short text
2    A longer description that
      wraps

The table’s wrap_set(false) overrides this flag globally.

Word wrap

By default, wrapping splits at character boundaries. Use word_wrap(true) to break at word boundaries instead. Words wider than the column fall back to character-level wrapping.

use cols::{Table, Column, TermForce, print_table};

fn main() {
    let mut t = Table::new();
    t.termwidth_set(30);
    t.termforce_set(TermForce::Always);
    t.add_column(Column::new("ID").width_fixed(4));
    t.add_column(Column::new("TEXT").word_wrap(true));

    let l1 = t.new_line(None);
    t.line_mut(l1).data_set(0, "1").data_set(1, "Short text");

    let l2 = t.new_line(None);
    t.line_mut(l2)
        .data_set(0, "2")
        .data_set(1, "A longer description that wraps at word boundaries");

    let l3 = t.new_line(None);
    t.line_mut(l3)
        .data_set(0, "3")
        .data_set(1, "Superlongwordthatexceedsthecolumnwidth here");

    print_table(&t, &mut std::io::stdout().lock()).unwrap();
}
ID   TEXT
1    Short text
2    A longer description
     that wraps at word
     boundaries
3    Superlongwordthatexceedst
     hecolumnwidth here

Hidden columns

Store data without displaying it. Useful for sorting or filtering by a column that shouldn’t appear in the output:

use cols::{Table, Column, print_table};

fn main() {
    let mut t = Table::new();
    t.termwidth_set(40);
    t.add_column(Column::new("A").width_fixed(5));
    t.add_column(Column::new("B").width_fixed(5).hidden(true));
    t.add_column(Column::new("C").width_fixed(5));

    let l = t.new_line(None);
    t.line_mut(l)
        .data_set(0, "visible")
        .data_set(1, "hidden")
        .data_set(2, "also visible");

    print_table(&t, &mut std::io::stdout().lock()).unwrap();
}
A       C
visible also visible

Strict width

Prevent the layout engine from shrinking a column below its hint:

Column::new("ID").width_fixed(6).strict_width(true)

Normally the layout engine may shrink columns to fit the terminal. strict_width exempts a column from this.

No extremes

Exclude the single largest value when calculating column width:

Column::new("PATH").no_extremes(true)

If one row has a very long value, it won’t force the entire column wide. The outlier value is truncated or overflows instead of dominating the layout.

Encoding

By default, non-printable control characters are encoded as \xHH:

DATA
hello\x01world\x1b[31m

To pass through specific characters, set safechars on the column:

Column::new("DATA").safechars("\t")  // tabs pass through, other control chars encoded

Or disable encoding entirely on the table:

table.encoding_set(false);

Header repeat

For long tables, you can repeat the header row at regular intervals so it stays visible when scrolling. The interval is based on termheight — headers repeat every termheight - 1 data lines.

use cols::{Table, Column, TermForce, print_table};

fn main() {
    let mut t = Table::new();
    t.termwidth_set(30);
    t.termforce_set(TermForce::Always);
    t.termheight_set(5);
    t.header_repeat_set(true);
    t.add_column(Column::new("NAME").width_fixed(10));
    t.add_column(Column::new("VALUE").width_fixed(10).right(true));

    for i in 1..=12 {
        let row = t.new_line(None);
        t.line_mut(row)
            .data_set(0, &format!("item-{i}"))
            .data_set(1, &format!("{}", i * 10));
    }

    print_table(&t, &mut std::io::stdout().lock()).unwrap();
}
NAME            VALUE
item-1             10
item-2             20
item-3             30
item-4             40
NAME            VALUE
item-5             50
item-6             60
item-7             70
item-8             80
NAME            VALUE
item-9             90
item-10           100
item-11           110
item-12           120

This works in both flat and tree modes.