zhiburt / tabled

An easy to use library for pretty print tables of Rust structs and enums.
MIT License
2.01k stars 84 forks source link

How to set table width to be terminal width #73

Open aharpervc opened 2 years ago

aharpervc commented 2 years ago

Hi, I'm experimenting with this library, and I'm wondering if it's possible to set the table width automatically so that it's the same as the terminal's width. I can get the terminal width using the terminal_size crate or similar, but I'm unsure how to do actually use that to configure the table. This would be very useful for me if possible. Thanks!

zhiburt commented 2 years ago

Hello @aharpervc, Thank you for opening the issue.

Current there's no easy way to handle it. But I think we could add a support for this functionality.

I tried to accomplish this goal via current means it it seems like can be achieved but quite hardly. (Interesting, I was thinking that it's easier than it turned out to be 😥)

You can find an example I've created which may cover some of your needs.

Correct me if I am wrong about these:

  1. You imply that the table always must be rendered on the whole terminal? Which means that we need to expand columns if necessary (make them bigger if the table is too small). And lose content in case it's too big for a terminal.
  2. You also would like to keep things adjusted.

use tabled::{Tabled, TableIteratorExt, Modify, Full};
use terminal_size::{Width, terminal_size};

#[derive(Debug, Tabled)]
struct Issue {
    author: &'static str,
    index: usize,
    title: &'static str,
}

impl Issue {
    fn new(author: &'static str, index: usize, title: &'static str) -> Self { Self { author, index, title } }
}

fn main() {
    let (Width(width), _) = terminal_size().unwrap();

    println!("Terminal has width={}", width);

    let data = vec![
        Issue::new("aharpervc", 73, "How to set table width to be terminal width"),
        Issue::new("zhiburt", 67, "Is it valuable to support ordering in derive macro?"),
        Issue::new("111", 0, ""),
        Issue::new("", 0, "111"),
    ];

    let table = data.table().with(Modify::new(Full).with(move |s: &str| make_term_str(width as usize, 3, s)));

    println!("{}", table);
}

fn make_term_str(term_width: usize, part: usize, s: &str) -> String {
    // fixme: not totally correct
    // for our example we lose 2 character of width.
    let width = (term_width / part) - 4;
    let str_length = s.chars().count();
    if str_length >= width {
        s.chars().take(width).collect()
    } else {
        let remaining = width - str_length;
        format!("{}{}", " ".repeat(remaining), s)
    }
}

You can check this out seems like works. And it's not probably exactly what you'd love to see.

aharpervc commented 2 years ago

Thanks for the response. That's an interesting approach. For your questions above, the idea is to print a table where the max width of the table is less than or equal to the terminal width; eg, rows never wrap. However, cell contents can wrap if necessary; but not wrap if not necessary.

aharpervc commented 2 years ago

This seems to work fairly well (might be off by 1/2 chars due to padding math, but it's basically fine)

let dimensions = terminal_size();
let (terminal_size::Width(w), terminal_size::Height(_)) = dimensions.unwrap_or_else(|| (terminal_size::Width(80 * 2), terminal_size::Height(0)));

let variable_size_column_count = 4;
let available_width_for_variable_sizing = w - (6 + 24 + 3);
let variable_size_column_max_width = (available_width_for_variable_sizing / variable_size_column_count) - 2;

let table = Table::new(items)
    .with(Modify::new(Full)
        .with(Alignment::left())
        .with(Alignment::top())
        .with(MaxWidth::wrapping(variable_size_column_max_width.into()).keep_words()))
    .with(Modify::new(Column(0..1)).with(MaxWidth::wrapping(4)))
    .with(Modify::new(Column(2..3)).with(MaxWidth::wrapping(22)))
    .with(Modify::new(Column(6..7)).with(MaxWidth::wrapping(1)));
zhiburt commented 2 years ago

Hi @aharpervc

I think it's a valuable feature to have at the end of the day. So I tried to add a support for this.

Could you check add-total-width-support branch?

You can use it by fixing Cargo.toml

[dependencies]
tabled = { git = "https://github.com/zhiburt/tabled", branch = "add-total-width-support" }

You can also inspect by cloning this repo and running terminal_table example.

Looking forward to your response.

PS: TotalWidth misses a correct Panel handling yet. Also the name of the struct may be not the best....

zhiburt commented 2 years ago

Here's the code from the former mentioned example


#[derive(Tabled)]
struct Release {
    version: &'static str,
    published_date: &'static str,
    is_active: bool,
    major_feature: &'static str,
}

const DATA: [Release; 3] = [
    Release {
        version: "0.2.1",
        published_date: "2021-06-23",
        is_active: true,
        major_feature: "#[header(inline)] attribute",
    },
    Release {
        version: "0.2.0",
        published_date: "2021-06-19",
        is_active: false,
        major_feature: "API changes",
    },
    Release {
        version: "0.1.4",
        published_date: "2021-06-07",
        is_active: false,
        major_feature: "display_with attribute",
    },
];

fn main() {
    let (terminal_size::Width(width), _) = terminal_size::terminal_size().unwrap();

    let table = DATA
        .table()
        .with(Style::extended())
        .with(Modify::new(Full).with(Alignment::center_horizontal()))
        .with(TotalWidth::new(width as usize));

    println!("{}", table);
}
zhiburt commented 2 years ago

ping @aharpervc

aharpervc commented 2 years ago

Nice, that definitely getting closer. I can see it sizing the max width how I hoped. Can this be used in conjunction with cell content wrapping instead of truncation? Eg, cells can always be extended to fill the max table width if necessary, but a cell whose contents extends the cell bounds should have word wrapping, so the content is always shown

zhiburt commented 2 years ago

Can this be used in conjunction with cell content wrapping instead of truncation? Eg, cells can always be extended to fill the max table width if necessary, but a cell whose contents extends the cell bounds should have word wrapping, so the content is always shown

I think it's reasonable 👍, we could set whether truncating or wrapping will be used as a optional argument to TotalWidth.

zhiburt commented 2 years ago

Hi @aharpervc I did a couple of fixes and add a support for wrapping, could you check this out Using terminal_table example on add-total-width-support branch?

Also could I ask you what do you think about cases like, TotalWidth::new(0) should it render an empty table? (no borders) Сurrently TotalWidth doesn't "clears" borders in such case. Therefore it's the case where actual width won't be 0.

zhiburt commented 2 years ago

ping

zhiburt commented 2 years ago

ping.

Sorry if it's bothering you.

aharpervc commented 2 years ago

Yes, I see your updates, I'm subscribed to this discussion. I haven't had time to look at it again yet this week. I'll let you know when I'm able to give it a try.

zhiburt commented 2 years ago

Hi @aharpervc I hope you'll get a chance to take a look at the example I've provided.

I just want to let you know that we've merged TotalWidth in master so you can use it from there

zhiburt commented 2 years ago

I came with a conclusion that TotalWidth name is not very explicit. So I split the functionality between MinWidth and MaxWidth.

Look at this example.

https://github.com/zhiburt/tabled/blob/a37078a0cc835755b181856d426ac7ff9ccb51e4/examples/terminal_table/src/main.rs#L37-L48

zhiburt commented 2 years ago

@aharpervc Hi once again I really don't want to bother you including the fact that you've already mentioned that. And I neither want to spam your inbox.

Just wan't to verify if we fixed your original issues. Or maybe you have some thoughts.

Sorry...

aharpervc commented 2 years ago

No problem, it's not spam if it's my own issue 😁. I had a limited amount of time to look into this question, and ran out of time

I came with a conclusion that TotalWidth name is not very explicit. So I split the functionality between MinWidth and MaxWidth.

This seems reasonable to me! My recommendation is to go with this and not wait on me, and if something else comes up in the future I'll put in a separate issue