Repository: pallets/click

Issue:
Single-character wrapped lines with long options
When I run the script at the bottom I get the following output. I would, obviously, expect ALSOLONG to be all together on the second row.

```
Usage: example.py relatively_long example [OPTIONS] FIRST SECOND LONGISH
                                          A
                                          L
                                          S
                                          O
                                          L
                                          O
                                          N
                                          G

Error: Missing argument "first".
```
## 

``` py
import click


@click.group()
def foo():
    pass


@foo.group()
def relatively_long():
    pass


@relatively_long.command()
@click.argument('first')
@click.argument('second')
@click.argument('longish')
@click.argument('alsolong')
def example():
    pass


foo()
```



Discussion:
This is on click 3.3 and Python 2.7.

I've just done a bit of digging, and the problem is that click passes a subsequent_indent in to textwrap.TextWrapper; this is subtracted from the total width available on all but the first line to determine how wide they can be. In this example, that means that all but the first line end up with a potential width of -5, hence the single character lines.

I have just run into this issue too. I'll try to have a stab at a PR for it tomorrow.


Resolving patch:
diff --git a/click/formatting.py b/click/formatting.py
--- a/click/formatting.py
+++ b/click/formatting.py
@@ -18,6 +18,12 @@ def iter_rows(rows, col_count):
         yield row + ('',) * (col_count - len(row))
 
 
+def add_subsequent_indent(text, subsequent_indent):
+    lines = text.splitlines()
+    lines = [lines[0]] + [subsequent_indent + line for line in lines[1:]]
+    return '\n'.join(lines)
+
+
 def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
               preserve_paragraphs=False):
     """A helper function that intelligently wraps text.  By default, it
@@ -40,11 +46,13 @@ def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
     """
     from ._textwrap import TextWrapper
     text = text.expandtabs()
+    post_wrap_indent = subsequent_indent[:-1]
+    subsequent_indent = subsequent_indent[-1:]
     wrapper = TextWrapper(width, initial_indent=initial_indent,
                           subsequent_indent=subsequent_indent,
                           replace_whitespace=False)
     if not preserve_paragraphs:
-        return wrapper.fill(text)
+        return add_subsequent_indent(wrapper.fill(text), post_wrap_indent)
 
     p = []
     buf = []
@@ -75,9 +83,11 @@ def _flush_par():
     for indent, raw, text in p:
         with wrapper.extra_indent(' ' * indent):
             if raw:
-                rv.append(wrapper.indent_only(text))
+                rv.append(add_subsequent_indent(wrapper.indent_only(text),
+                                                post_wrap_indent))
             else:
-                rv.append(wrapper.fill(text))
+                rv.append(add_subsequent_indent(wrapper.fill(text),
+                                                post_wrap_indent))
 
     return '\n\n'.join(rv)
 


Test patch:
diff --git a/tests/test_formatting.py b/tests/test_formatting.py
--- a/tests/test_formatting.py
+++ b/tests/test_formatting.py
@@ -48,3 +48,42 @@ def cli():
         'Options:',
         '  --help  Show this message and exit.',
     ]
+
+
+def test_wrapping_long_options_strings(runner):
+    @click.group()
+    def cli():
+        """Top level command
+        """
+
+    @cli.group()
+    def a_very_long():
+        """Second level
+        """
+
+    @a_very_long.command()
+    @click.argument('first')
+    @click.argument('second')
+    @click.argument('third')
+    @click.argument('fourth')
+    @click.argument('fifth')
+    @click.argument('sixth')
+    def command():
+        """A command.
+        """
+
+    # 54 is chosen as a lenthg where the second line is one character
+    # longer than the maximum length.
+    result = runner.invoke(cli, ['a_very_long', 'command', '--help'],
+                           terminal_width=54)
+    assert not result.exception
+    assert result.output.splitlines() == [
+        'Usage: cli a_very_long command [OPTIONS] FIRST SECOND',
+        '                               THIRD FOURTH FIFTH',
+        '                               SIXTH',
+        '',
+        '  A command.',
+        '',
+        'Options:',
+        '  --help  Show this message and exit.',
+    ]