diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md
index 3a0e7170ad..b9f9d45070 100644
--- a/docs/tutorial/commands/help.md
+++ b/docs/tutorial/commands/help.md
@@ -503,3 +503,42 @@ $ python main.py --help
```
+
+## Expand or Fit
+
+By default, the help panels all expand to match the width of your terminal window.
+
+Sometimes, you might prefer that all panels fit their contents instead. This means that they will probably have different widths.
+
+You can do this by initializing your Typer with `rich_expand=False`, like this:
+
+{* docs_src/commands/help/tutorial009.py hl[5] *}
+
+When you now check the `--help` option, it will look like:
+
+
+
+```console
+$ python main.py create --help
+
+ Usage: main.py create [OPTIONS] USERNAME [LASTNAME]
+
+ Create a new user. ✨
+
+╭─ Arguments ──────────────────────────────────────╮
+│ * username TEXT The username [required] │
+╰──────────────────────────────────────────────────╯
+╭─ Secondary Arguments ─────────────────────╮
+│ lastname [LASTNAME] The last name │
+╰───────────────────────────────────────────╯
+╭─ Options ────────────────────────────────────────────────╮
+│ --force --no-force Force the creation [required] │
+│ --help Show this message and exit. │
+╰──────────────────────────────────────────────────────────╯
+╭─ Additional Data ───────────────────────────────────╮
+│ --age INTEGER The age │
+│ --favorite-color TEXT The favorite color │
+╰─────────────────────────────────────────────────────╯
+```
+
+
diff --git a/docs_src/commands/help/tutorial009.py b/docs_src/commands/help/tutorial009.py
new file mode 100644
index 0000000000..373e14c2e3
--- /dev/null
+++ b/docs_src/commands/help/tutorial009.py
@@ -0,0 +1,39 @@
+from typing import Union
+
+import typer
+
+app = typer.Typer(rich_markup_mode="rich", rich_expand=False)
+
+
+@app.command()
+def create(
+ username: str = typer.Argument(..., help="The username"),
+ lastname: str = typer.Argument(
+ "", help="The last name", rich_help_panel="Secondary Arguments"
+ ),
+ force: bool = typer.Option(..., help="Force the creation"),
+ age: Union[int, None] = typer.Option(
+ None, help="The age", rich_help_panel="Additional Data"
+ ),
+ favorite_color: Union[str, None] = typer.Option(
+ None,
+ help="The favorite color",
+ rich_help_panel="Additional Data",
+ ),
+):
+ """
+ [green]Create[/green] a new user. :sparkles:
+ """
+ print(f"Creating user: {username}")
+
+
+@app.command(rich_help_panel="Utils and Configs")
+def config(configuration: str):
+ """
+ [blue]Configure[/blue] the system. :gear:
+ """
+ print(f"Configuring the system with: {configuration}")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/tests/test_tutorial/test_commands/test_help/test_tutorial009.py b/tests/test_tutorial/test_commands/test_help/test_tutorial009.py
new file mode 100644
index 0000000000..ab75d40d66
--- /dev/null
+++ b/tests/test_tutorial/test_commands/test_help/test_tutorial009.py
@@ -0,0 +1,39 @@
+import os
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.commands.help import tutorial009 as mod
+
+app = mod.app
+
+runner = CliRunner()
+
+
+def test_main_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "create" in result.output
+ assert "Create a new user. ✨" in result.output
+ assert "Utils and Configs" in result.output
+ assert "config" in result.output
+ assert "Configure the system. ⚙" in result.output
+
+
+def test_call():
+ # Mainly for coverage
+ result = runner.invoke(app, ["create", "Morty", "--force"])
+ assert result.exit_code == 0
+ result = runner.invoke(app, ["config", "Morty"])
+ assert result.exit_code == 0
+
+
+def test_script():
+ result = subprocess.run(
+ [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
+ capture_output=True,
+ encoding="utf-8",
+ env={**os.environ, "PYTHONIOENCODING": "utf-8"},
+ )
+ assert "Usage" in result.stdout
diff --git a/typer/core.py b/typer/core.py
index e9631e56cf..a7ee068889 100644
--- a/typer/core.py
+++ b/typer/core.py
@@ -166,6 +166,7 @@ def _main(
standalone_mode: bool = True,
windows_expand_args: bool = True,
rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
+ rich_expand: bool,
**extra: Any,
) -> Any:
# Typer override, duplicated from click.main() to handle custom rich exceptions
@@ -212,7 +213,7 @@ def _main(
if HAS_RICH and rich_markup_mode is not None:
from . import rich_utils
- rich_utils.rich_format_error(e)
+ rich_utils.rich_format_error(e, expand=rich_expand)
else:
e.show()
# Typer override end
@@ -674,6 +675,7 @@ def __init__(
# Rich settings
rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
rich_help_panel: Union[str, None] = None,
+ rich_expand: bool = True,
) -> None:
super().__init__(
name=name,
@@ -691,6 +693,7 @@ def __init__(
)
self.rich_markup_mode: MarkupMode = rich_markup_mode
self.rich_help_panel = rich_help_panel
+ self.rich_expand = rich_expand
def format_options(
self, ctx: click.Context, formatter: click.HelpFormatter
@@ -724,6 +727,7 @@ def main(
standalone_mode=standalone_mode,
windows_expand_args=windows_expand_args,
rich_markup_mode=self.rich_markup_mode,
+ rich_expand=self.rich_expand,
**extra,
)
@@ -736,6 +740,7 @@ def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> Non
obj=self,
ctx=ctx,
markup_mode=self.rich_markup_mode,
+ expand=self.rich_expand,
)
@@ -750,12 +755,14 @@ def __init__(
# Rich settings
rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
rich_help_panel: Union[str, None] = None,
+ rich_expand: bool = True,
suggest_commands: bool = True,
**attrs: Any,
) -> None:
super().__init__(name=name, commands=commands, **attrs)
self.rich_markup_mode: MarkupMode = rich_markup_mode
self.rich_help_panel = rich_help_panel
+ self.rich_expand = rich_expand
self.suggest_commands = suggest_commands
def format_options(
@@ -808,6 +815,7 @@ def main(
standalone_mode=standalone_mode,
windows_expand_args=windows_expand_args,
rich_markup_mode=self.rich_markup_mode,
+ rich_expand=self.rich_expand,
**extra,
)
@@ -820,6 +828,7 @@ def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> Non
obj=self,
ctx=ctx,
markup_mode=self.rich_markup_mode,
+ expand=self.rich_expand,
)
def list_commands(self, ctx: click.Context) -> List[str]:
diff --git a/typer/main.py b/typer/main.py
index 2e3b1223cc..f20af63cba 100644
--- a/typer/main.py
+++ b/typer/main.py
@@ -136,6 +136,7 @@ def __init__(
add_completion: bool = True,
# Rich settings
rich_markup_mode: MarkupMode = Default(DEFAULT_MARKUP_MODE),
+ rich_expand: bool = True,
rich_help_panel: Union[str, None] = Default(None),
suggest_commands: bool = True,
pretty_exceptions_enable: bool = True,
@@ -144,6 +145,7 @@ def __init__(
):
self._add_completion = add_completion
self.rich_markup_mode: MarkupMode = rich_markup_mode
+ self.rich_expand = rich_expand
self.rich_help_panel = rich_help_panel
self.suggest_commands = suggest_commands
self.pretty_exceptions_enable = pretty_exceptions_enable
@@ -334,6 +336,7 @@ def get_group(typer_instance: Typer) -> TyperGroup:
TyperInfo(typer_instance),
pretty_exceptions_short=typer_instance.pretty_exceptions_short,
rich_markup_mode=typer_instance.rich_markup_mode,
+ rich_expand=typer_instance.rich_expand,
suggest_commands=typer_instance.suggest_commands,
)
return group
@@ -367,6 +370,7 @@ def get_command(typer_instance: Typer) -> click.Command:
single_command,
pretty_exceptions_short=typer_instance.pretty_exceptions_short,
rich_markup_mode=typer_instance.rich_markup_mode,
+ rich_expand=typer_instance.rich_expand,
)
if typer_instance._add_completion:
click_command.params.append(click_install_param)
@@ -463,6 +467,7 @@ def get_group_from_info(
pretty_exceptions_short: bool,
suggest_commands: bool,
rich_markup_mode: MarkupMode,
+ rich_expand: bool,
) -> TyperGroup:
assert group_info.typer_instance, (
"A Typer instance is needed to generate a Click Group"
@@ -473,6 +478,7 @@ def get_group_from_info(
command_info=command_info,
pretty_exceptions_short=pretty_exceptions_short,
rich_markup_mode=rich_markup_mode,
+ rich_expand=rich_expand,
)
if command.name:
commands[command.name] = command
@@ -481,6 +487,7 @@ def get_group_from_info(
sub_group_info,
pretty_exceptions_short=pretty_exceptions_short,
rich_markup_mode=rich_markup_mode,
+ rich_expand=rich_expand,
suggest_commands=suggest_commands,
)
if sub_group.name:
@@ -528,6 +535,7 @@ def get_group_from_info(
hidden=solved_info.hidden,
deprecated=solved_info.deprecated,
rich_markup_mode=rich_markup_mode,
+ rich_expand=rich_expand,
# Rich settings
rich_help_panel=solved_info.rich_help_panel,
suggest_commands=suggest_commands,
@@ -563,6 +571,7 @@ def get_command_from_info(
*,
pretty_exceptions_short: bool,
rich_markup_mode: MarkupMode,
+ rich_expand: bool,
) -> click.Command:
assert command_info.callback, "A command must have a callback function"
name = command_info.name or get_command_name(command_info.callback.__name__)
@@ -597,6 +606,7 @@ def get_command_from_info(
hidden=command_info.hidden,
deprecated=command_info.deprecated,
rich_markup_mode=rich_markup_mode,
+ rich_expand=rich_expand,
# Rich settings
rich_help_panel=command_info.rich_help_panel,
)
diff --git a/typer/rich_utils.py b/typer/rich_utils.py
index d4c3676aea..d1408bd8da 100644
--- a/typer/rich_utils.py
+++ b/typer/rich_utils.py
@@ -234,7 +234,8 @@ def _get_parameter_help(
param: Union[click.Option, click.Argument, click.Parameter],
ctx: click.Context,
markup_mode: MarkupMode,
-) -> Columns:
+ rich_expand: bool,
+) -> Union[Table, Columns]:
"""Build primary help text for a click option or argument.
Returns the prose help text for an option or argument, rendered either
@@ -313,9 +314,16 @@ def _get_parameter_help(
if param.required:
items.append(Text(REQUIRED_LONG_STRING, style=STYLE_REQUIRED_LONG))
- # Use Columns - this allows us to group different renderable types
- # (Text, Markdown) onto a single line.
- return Columns(items)
+ if rich_expand:
+ # Use Columns - this allows us to group different renderable types
+ # (Text, Markdown) onto a single line.
+ return Columns(items)
+
+ # Use Table - this allows us to group different renderable types
+ # (Text, Markdown) onto a single line without using the full screen width.
+ help_table = Table.grid(padding=(0, 1), expand=False)
+ help_table.add_row(*items)
+ return help_table
def _make_command_help(
@@ -351,6 +359,7 @@ def _print_options_panel(
params: Union[List[click.Option], List[click.Argument]],
ctx: click.Context,
markup_mode: MarkupMode,
+ expand: bool,
console: Console,
) -> None:
options_rows: List[List[RenderableType]] = []
@@ -433,6 +442,7 @@ class MetavarHighlighter(RegexHighlighter):
param=param,
ctx=ctx,
markup_mode=markup_mode,
+ rich_expand=expand,
),
]
)
@@ -468,6 +478,7 @@ class MetavarHighlighter(RegexHighlighter):
options_table,
border_style=STYLE_OPTIONS_PANEL_BORDER,
title=name,
+ expand=expand,
title_align=ALIGN_OPTIONS_PANEL,
)
)
@@ -478,6 +489,7 @@ def _print_commands_panel(
name: str,
commands: List[click.Command],
markup_mode: MarkupMode,
+ expand: bool,
console: Console,
cmd_len: int,
) -> None:
@@ -544,6 +556,7 @@ def _print_commands_panel(
commands_table,
border_style=STYLE_COMMANDS_PANEL_BORDER,
title=name,
+ expand=expand,
title_align=ALIGN_COMMANDS_PANEL,
)
)
@@ -554,6 +567,7 @@ def rich_format_help(
obj: Union[click.Command, click.Group],
ctx: click.Context,
markup_mode: MarkupMode,
+ expand: bool,
) -> None:
"""Print nicely formatted help text using rich.
@@ -607,6 +621,7 @@ def rich_format_help(
params=default_arguments,
ctx=ctx,
markup_mode=markup_mode,
+ expand=expand,
console=console,
)
for panel_name, arguments in panel_to_arguments.items():
@@ -618,6 +633,7 @@ def rich_format_help(
params=arguments,
ctx=ctx,
markup_mode=markup_mode,
+ expand=expand,
console=console,
)
default_options = panel_to_options.get(OPTIONS_PANEL_TITLE, [])
@@ -626,6 +642,7 @@ def rich_format_help(
params=default_options,
ctx=ctx,
markup_mode=markup_mode,
+ expand=expand,
console=console,
)
for panel_name, options in panel_to_options.items():
@@ -637,6 +654,7 @@ def rich_format_help(
params=options,
ctx=ctx,
markup_mode=markup_mode,
+ expand=expand,
console=console,
)
@@ -667,6 +685,7 @@ def rich_format_help(
name=COMMANDS_PANEL_TITLE,
commands=default_commands,
markup_mode=markup_mode,
+ expand=expand,
console=console,
cmd_len=max_cmd_len,
)
@@ -678,6 +697,7 @@ def rich_format_help(
name=panel_name,
commands=commands,
markup_mode=markup_mode,
+ expand=expand,
console=console,
cmd_len=max_cmd_len,
)
@@ -691,7 +711,7 @@ def rich_format_help(
console.print(Padding(Align(epilogue_text, pad=False), 1))
-def rich_format_error(self: click.ClickException) -> None:
+def rich_format_error(self: click.ClickException, expand: bool) -> None:
"""Print richly formatted click errors.
Called by custom exception handler to print richly formatted click errors.
@@ -718,6 +738,7 @@ def rich_format_error(self: click.ClickException) -> None:
Panel(
highlighter(self.format_message()),
border_style=STYLE_ERRORS_PANEL_BORDER,
+ expand=expand,
title=ERRORS_PANEL_TITLE,
title_align=ALIGN_ERRORS_PANEL,
)