Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lines in help output can be wider than the terminal width #2065

Open
mkantor opened this issue Aug 14, 2020 · 9 comments
Open

Lines in help output can be wider than the terminal width #2065

mkantor opened this issue Aug 14, 2020 · 9 comments
Labels
A-help Area: documentation, including docs.rs, readme, examples, etc... C-bug Category: Updating dependencies S-waiting-on-design Status: Waiting on user-facing design to be resolved before implementing

Comments

@mkantor
Copy link
Contributor

mkantor commented Aug 14, 2020

Code

use clap::App;

fn main() {
    let mut app = App::new("This string is 34 characters long.")
        .version("v12.34.56")
        .term_width(35);

    app.print_help();
}

Steps to reproduce the issue

  1. Run cargo run
  2. Observe that the first line of the output (This string is 34 characters long. v12.34.56) is wider than the desired width (35), even though there are places where it could have been wrapped.

See this diff for additional test cases.

Version

  • Rust: rustc 1.45.2 (d3fb005a3 2020-07-31)
  • Clap: master

Actual Behavior Summary

When getting help output that has been line-wrapped to fit the terminal width the output can be wider than the terminal, even if there are places where wrapping could have been applied.

Expected Behavior Summary

Lines should never be wider than specified terminal width unless they contain no word breaks.

Additional context

Wrapping is currently applied to individual bits and pieces of the overall help message without taking into account their context. For example, as demonstrated above the version number and binary name are displayed on the same line by default, but the binary name is always wrapped as if its last character is at the end of the line. Similarly, some template tags have their output wrapped, but without preceding/trailing characters being accounted for.

@mkantor mkantor added the C-bug Category: Updating dependencies label Aug 14, 2020
@mkantor
Copy link
Contributor Author

mkantor commented Aug 14, 2020

One way to solve this would be to buffer the entire help message into a string, then apply wrapping to it all at once. This could have a performance impact (I haven't tried it).

A more sophisticated approach could be to buffer the help message in chunks, flushing it to output whenever wrapping occurs (or when an explicit newline is encountered). That could be fairly complex, though.

@mkantor
Copy link
Contributor Author

mkantor commented Aug 14, 2020

Expanding on the second paragraph in my previous comment, here's a sketch of what a "buffer in chunks" wrapping algorithm could look like. This hasn't been tested and there are likely edge cases I've missed:

  • When writing chunks of the help message, append them to an internal buffer.
  • If the buffer's width ever exceeds the terminal width, start wrapping:
    • Scan from the beginning of the buffer up to the terminal width, then backtrack to the previous word break, then flush everything up to that point plus a newline.
    • Repeat until the buffer's width is less than the terminal width.
    • This process would also need to handle lines that are wider than the terminal but which can't be broken up (e.g. the whole line is one long word).
    • It'd also need to handle strings with literal newlines, I guess by stopping each scan early and flushing immediately if a newline is encountered.
  • At the end of the help message, flush whatever is left in the buffer.

The HelpWriter::Normal vs HelpWriter::Buffer distinction complicates this. Maybe Normal wouldn't get wrapping, just like it doesn't get colors currently? ¯\_(ツ)_/¯

Anyway, it's probably worth measuring the performance impact of a "buffer it all" strategy first as it should be much simpler. Or maybe there's a better approach than either of these which I've missed.

mkantor added a commit to mkantor/clap that referenced this issue Aug 14, 2020
This makes some changes to the template system:

- Template tags for optional items (like {author}) now expand to
  nothing when the value is unset instead of a default string (like
  "unknown author").
- Many template tags now emit line-wrapped output to match
  write_default_help.
- Items with long variants now expand to the appropriate thing for -h
  vs --help.
- The now-obsolete {long-about} tag has been removed.
- A few new tags have been added.

These are externally-visible changes, but if this makes it into 3.0
that's probably reasonable?

Note that line-wrapping can have some odd edge cases since it does not
account for preceding/trailing characters on the same line as the tag.
This is already the case in master, but will affect some additional
tags with this changeset. See clap-rs#2065 for details.

Closes clap-rs#2002.
@CreepySkeleton
Copy link
Contributor

If you don't mind, a couple of questions:

  • Why on earth would you create an app with "horrifyingly and obscenely long binary name" in the first place?
  • How did you end up with a display width this tiny? Some sort of mobile device?

That was out of curiosity. For the record, I'd like to see this fixed, not ignored, even if the decided fix will be "don't support width less than N and assert that".

This hasn't been tested and there are likely edge cases I've missed:

Aha. Word wrap.

Maybe Normal wouldn't get wrapping, just like it doesn't get colors currently

No. I don't see any reason why lack of color should imply lack of wrapping.

Anyway, it's probably worth measuring the performance impact of a "buffer it all" strategy first as it should be much simpler. Or maybe there's a better approach than either of these which I've missed.

And this is not-too-far description of my "Refactor" PR. I've done only some preliminary measurements, but they didn't show any significant difference.

@mkantor
Copy link
Contributor Author

mkantor commented Aug 14, 2020

Why on earth would you create an app with "horrifyingly and obscenely long binary name" in the first place?

I realized this issue existed while hacking on #2067. At first I thought it was only a problem with custom templates, but then I noticed the default help can also exhibit this behavior (albeit only in pathological cases like the super long binary name). Custom templates can put an arbitrary amount of stuff on a single line which makes this issue more pronounced (it can affect all tag(s), not just binary name & version).

How did you end up with a display width this tiny? Some sort of mobile device?

The tiny width was just to make the example short. I don't set an explicit width in any of my real uses of clap.

@mkantor
Copy link
Contributor Author

mkantor commented Aug 14, 2020

Maybe Normal wouldn't get wrapping, just like it doesn't get colors currently

No. I don't see any reason why lack of color should imply lack of wrapping.

I agree. I think the variant names are throwing me off; since it's Buffered vs Normal it sounds like the latter shouldn't do any buffering (even though it already does).

@pksunkara pksunkara added this to the 3.1 milestone Aug 23, 2020
@pksunkara pksunkara added the A-help Area: documentation, including docs.rs, readme, examples, etc... label Aug 23, 2020
@pksunkara
Copy link
Member

@mkantor I think this has been fixed with the recent changes to master. Do you think you can look into the test cases?

@mkantor
Copy link
Contributor Author

mkantor commented Oct 27, 2020

@pksunkara I rebased the tests and they still fail, so it appears this issue hasn't been fixed.

@pksunkara
Copy link
Member

wrapping_items_on_same_line test case should be easy to fix now if you want to look into it.

@epage
Copy link
Member

epage commented Dec 10, 2021

So it looks like we are wrapping individual fields in places (e.g. bin name, version, etc) rather than wrapping the result.

Part of this comes from the fact that we are processing a template. Resolving this would take some work in balancing how to wrap the aggregation of fields without interfering with areas that might not want wrapping.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-help Area: documentation, including docs.rs, readme, examples, etc... C-bug Category: Updating dependencies S-waiting-on-design Status: Waiting on user-facing design to be resolved before implementing
Projects
None yet
Development

No branches or pull requests

4 participants