fdehau / tui-rs

Build terminal user interfaces and dashboards using Rust

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How about another style of paragraph wrapping?

WestXu opened this issue · comments

Currently according to the docs of Paragraph.wrap, we have two options:

let bullet_points = Text::from(r#"Some indented points:
    - First thing goes here and is long so that it wraps
    - Here is another point that is long enough to wrap"#);

// With leading spaces trimmed (window width of 30 chars):
Paragraph::new(bullet_points.clone()).wrap(Wrap { trim: true });
// Some indented points:
// - First thing goes here and is
// long so that it wraps
// - Here is another point that
// is long enough to wrap

// But without trimming, indentation is preserved:
Paragraph::new(bullet_points).wrap(Wrap { trim: false });
// Some indented points:
//     - First thing goes here
// and is long so that it wraps
//     - Here is another point
// that is long enough to wrap

They both are a bit counter-intuitive to me. What I expected is like this:

// Some indented points:
//     - First thing goes here
//     and is long so that it 
//     wraps
//     - Here is another point
//     that is long enough to 
//     wrap

To be specific, I expect the wrapped new lines to inherit the same indentation of the original line, which seems much clearer to me, especially in some multi-level indentation paragraphs like codes. Is it possible to achieve this?

So I've made an implementation of this, but I don't know how to integrate it to the repo's wrapping mechanism to make a PR.

It's a simple idea. The String-based implementation is like this:

fn wrap_line(ori: String, width: usize) -> String {
    fn wrap(line: String, indent: usize, width: usize) -> Vec<String> {
        if (line.len() <= width) || (indent >= width) {
            vec![line]
        } else {
            let (a, b) = line.split_at(width);
            let b = " ".repeat(indent) + b;
            vec![vec![a.to_owned()], wrap(b, indent, width)].concat()
        }
    }

    ori.split('\n')
        .into_iter()
        .map(|line| wrap(line.to_owned(), line.len() - line.trim().len(), width))
        .flatten()
        .collect::<Vec<String>>()
        .join("\n")
}

fn main() {
    println!(
        "{}",
        wrap_line(
            String::from(
                r#"Some indented points:
    - First thing goes here and is long so that it wraps
    - Here is another point that is long enough to wrap"#
            ),
            28
        )
    )
}

And the output is like:

Some indented points:
    - First thing goes here 
    and is long so that it w
    raps
    - Here is another point 
    that is long enough to w
    rap

Then here is the actual implementation working on tui_rs::text::Text:

fn wrap_text(ori: Text, width: usize) -> Text {
    fn wrap(line: Spans, indent: usize, width: usize) -> Vec<Spans> {
        if (line.width() <= width) || (indent >= width) {
            vec![line]
        } else {
            let grs = line
                .0
                .iter()
                .map(|span| span.styled_graphemes(span.style))
                .flatten()
                .collect::<Vec<StyledGrapheme>>();

            let mut a = grs;
            let b = a.split_off(width);
            let b = vec![
                vec![
                    StyledGrapheme {
                        symbol: " ",
                        style: a[0].style,
                    };
                    indent
                ],
                b,
            ]
            .concat();
            let spans_a: Spans = a
                .into_iter()
                .map(|g| Span::styled(g.symbol.to_owned(), g.style))
                .collect::<Vec<Span>>()
                .into();
            let spans_b: Spans = b
                .into_iter()
                .map(|g| Span::styled(g.symbol.to_owned(), g.style))
                .collect::<Vec<Span>>()
                .into();

            vec![vec![spans_a], wrap(spans_b, indent, width)].concat()
        }
    }

    ori.into_iter()
        .map(|line| {
            wrap(
                line.to_owned(),
                line.width()
                    - line
                        .0
                        .iter()
                        .map(|span| &span.content)
                        .join("")
                        .trim_start()
                        .len(),
                width,
            )
        })
        .flatten()
        .collect::<Vec<Spans>>()
        .into()
}

I don't know if people out there ever need a wrapping style like this other than me. It appears pretty neat when showing some code or yaml on a dashboard, by keeping all the hierarchy intact, being easily parsable by human eyes. Let me know if you need a PR on this (with some instructions on how to integrate it).