diff --git a/CHANGES b/CHANGES index c65567baef..8986bd2e86 100644 --- a/CHANGES +++ b/CHANGES @@ -41,11 +41,17 @@ _Notes on the upcoming release will go here._ CLI documentation now supports direct linking to specific arguments: -- **Linkable options**: Each `--option` and positional argument has a permanent URL anchor (e.g., `cli/load.html#-d`) +- **Linkable options**: Each `--option` and positional argument has a permanent URL anchor (e.g., `cli/load.html#load-d`) - **Headerlinks**: Hover over any argument to reveal a ¶ link for easy sharing - **Visual styling**: Argument names displayed with syntax-highlighted backgrounds for better readability - **Default value badges**: Default values shown as styled inline code (e.g., `auto`) +#### Improved argument metadata display (#1011) + +- **Cleaner layout**: Default, Type, Choices shown as key-value pairs instead of pipe-separated text +- **Required badge**: Subtle amber tag visible in both light and dark modes +- **Consistent sizing**: All CLI elements match code block font size + ## tmuxp 1.64.0 (2026-01-24) ### Documentation diff --git a/docs/_ext/argparse_lexer.py b/docs/_ext/argparse_lexer.py index 11bf02a19f..14aed55649 100644 --- a/docs/_ext/argparse_lexer.py +++ b/docs/_ext/argparse_lexer.py @@ -56,7 +56,7 @@ class ArgparseUsageLexer(RegexLexer): # Whitespace (r"\s+", Whitespace), # Program name (first lowercase word after usage:) - (r"\b[a-z][-a-z0-9]*\b", Name.Label, "usage_body"), + (r"\b[a-z][-a-z0-9_]*\b", Name.Label, "usage_body"), # Fallback to inline if something unexpected include("inline"), ], @@ -67,14 +67,14 @@ class ArgparseUsageLexer(RegexLexer): (r"\.\.\.", Punctuation), # Long options with = value (e.g., --log-level=VALUE) ( - r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9]*)", + r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call] ), # Long options standalone (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag), # Short options with space-separated value (e.g., -S socket-path) ( - r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9]*)", + r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call] ), # Short options standalone @@ -94,7 +94,7 @@ class ArgparseUsageLexer(RegexLexer): # UPPERCASE meta-variables (COMMAND, FILE, PATH) (r"\b[A-Z][A-Z0-9_]*\b", Name.Variable), # Subcommand/positional names (Name.Function for distinct styling) - (r"\b[a-z][-a-z0-9]*\b", Name.Function), + (r"\b[a-z][-a-z0-9_]*\b", Name.Function), # Catch-all for any other text (r"[^\s\[\]|(){},]+", Text), ], @@ -105,14 +105,14 @@ class ArgparseUsageLexer(RegexLexer): (r"\.\.\.", Punctuation), # Long options with = value (e.g., --log-level=VALUE) ( - r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9]*)", + r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call] ), # Long options standalone (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag), # Short options with space-separated value (e.g., -S socket-path) ( - r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9]*)", + r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call] ), # Short options standalone @@ -132,7 +132,7 @@ class ArgparseUsageLexer(RegexLexer): # UPPERCASE meta-variables (COMMAND, FILE, PATH) (r"\b[A-Z][A-Z0-9_]*\b", Name.Variable), # Positional/command names (lowercase with dashes) - (r"\b[a-z][-a-z0-9]*\b", Name.Label), + (r"\b[a-z][-a-z0-9_]*\b", Name.Label), # Catch-all for any other text (r"[^\s\[\]|(){},]+", Text), ], @@ -214,7 +214,7 @@ class ArgparseHelpLexer(RegexLexer): # Whitespace (r"\s+", Whitespace), # Program name (first lowercase word after usage:) - (r"\b[a-z][-a-z0-9]*\b", Name.Label, "usage"), + (r"\b[a-z][-a-z0-9_]*\b", Name.Label, "usage"), # Fallback to usage if something unexpected include("usage_inline"), ], @@ -234,14 +234,14 @@ class ArgparseHelpLexer(RegexLexer): (r"\.\.\.", Punctuation), # Long options with = value ( - r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9]*)", + r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call] ), # Long options standalone (r"--[a-zA-Z0-9][-a-zA-Z0-9]*", Name.Tag), # Short options with value ( - r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9]*)", + r"(-[a-zA-Z0-9])(\s+)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", bygroups(Name.Attribute, Whitespace, Name.Variable), # type: ignore[no-untyped-call] ), # Short options standalone @@ -259,7 +259,7 @@ class ArgparseHelpLexer(RegexLexer): # UPPERCASE metavars (r"\b[A-Z][A-Z0-9_]*\b", Name.Variable), # Subcommand/positional names (Name.Function for distinct styling) - (r"\b[a-z][-a-z0-9]*\b", Name.Function), + (r"\b[a-z][-a-z0-9_]*\b", Name.Function), # Other text (r"[^\s\[\]|(){},\n]+", Text), ], @@ -271,7 +271,7 @@ class ArgparseHelpLexer(RegexLexer): ), # Long options with = value ( - r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9]*)", + r"(--[a-zA-Z0-9][-a-zA-Z0-9]*)(=)([A-Z][A-Z0-9_]*|[a-z][-a-z0-9_]*)", bygroups(Name.Tag, Operator, Name.Variable), # type: ignore[no-untyped-call] ), # Long options with space-separated metavar diff --git a/docs/_ext/sphinx_argparse_neo/nodes.py b/docs/_ext/sphinx_argparse_neo/nodes.py index f34f864f9c..468b5876a5 100644 --- a/docs/_ext/sphinx_argparse_neo/nodes.py +++ b/docs/_ext/sphinx_argparse_neo/nodes.py @@ -518,30 +518,46 @@ def depart_argparse_argument_html( node : argparse_argument The argument node being departed. """ - # Add metadata (default, choices, type) - metadata: list[str] = [] - + # Build metadata as definition list items default = node.get("default_string") - if default is not None: - # Wrap default value in nv span for yellow/italic styling - metadata.append(f'Default: {self.encode(default)}') - choices = node.get("choices") - if choices: - choices_str = ", ".join(str(c) for c in choices) - metadata.append(f"Choices: {self.encode(choices_str)}") - type_name = node.get("type_name") - if type_name: - metadata.append(f"Type: {self.encode(type_name)}") - required = node.get("required", False) - if required: - metadata.append("Required") - if metadata: - meta_str = " | ".join(metadata) - self.body.append(f'
') + if default is not None or choices or type_name or required: + self.body.append('\n") self.body.append("\n") # Close wrapper div diff --git a/docs/_static/css/argparse-highlight.css b/docs/_static/css/argparse-highlight.css index a0a77ec795..f232c71c8c 100644 --- a/docs/_static/css/argparse-highlight.css +++ b/docs/_static/css/argparse-highlight.css @@ -22,7 +22,7 @@ ========================================================================== */ /* - * Shared monospace font for all CLI inline roles + * Shared monospace font and code font-size for all CLI inline roles */ .cli-option, .cli-metavar, @@ -30,6 +30,7 @@ .cli-default, .cli-choice { font-family: var(--font-stack--monospace); + font-size: var(--code-font-size); } /* @@ -191,6 +192,7 @@ /* Usage block container - match Pygments monokai background and code block styling */ pre.argparse-usage { background: var(--argparse-code-background); + font-size: var(--code-font-size); padding: 0.625rem 0.875rem; line-height: 1.5; border-radius: 0.2rem; @@ -289,6 +291,7 @@ pre.argparse-usage { border-radius: 0.2rem; padding: 0.485rem 0.875rem; font-family: var(--font-stack--monospace); + font-size: var(--code-font-size); width: fit-content; position: relative; } @@ -330,8 +333,9 @@ pre.argparse-usage { /* * Light mode headerlink color overrides + * Needed because code block has dark background regardless of theme */ -body:not([data-theme="dark"]) .argparse-argument-name .headerlink { +body[data-theme="light"] .argparse-argument-name .headerlink { color: #9ca0a5; &:hover:not(:visited) { @@ -339,6 +343,16 @@ body:not([data-theme="dark"]) .argparse-argument-name .headerlink { } } +@media (prefers-color-scheme: light) { + body:not([data-theme="dark"]) .argparse-argument-name .headerlink { + color: #9ca0a5; + + &:hover:not(:visited) { + color: #cfd0d0; + } + } +} + /* * Highlight when targeted via URL fragment * Uses Furo's highlight-on-target color for consistency. @@ -348,18 +362,72 @@ body:not([data-theme="dark"]) .argparse-argument-name .headerlink { } /* - * Default value styling in metadata - * Styled like inline code with monokai background. + * Argument metadata definition list + * + * Renders metadata (Default, Type, Choices, Required) as a horizontal + * flexbox of key-value pairs and standalone tags. */ -.argparse-argument-meta .nv { +.argparse-argument-meta { + margin: 0.5rem 0 0 0; + padding: 0; + display: flex; + flex-wrap: wrap; + gap: 0.5rem 1rem; + align-items: center; +} + +.argparse-meta-item { + display: flex; + align-items: center; + gap: 0.25rem; +} + +.argparse-meta-key { + color: var(--color-foreground-secondary, #6c757d); + font-size: var(--code-font-size); +} + +.argparse-meta-key::after { + content: ":"; +} + +.argparse-meta-value .nv { background: var(--argparse-code-background); border-radius: 0.2rem; - padding: 0.1405rem 0.3rem; + padding: 0.1rem 0.3rem; font-family: var(--font-stack--monospace); - font-size: var(--font-size--small); + font-size: var(--code-font-size); color: #e5c07b; } +/* + * Meta tag (e.g., "Required") - follows Furo's guilabel pattern + * Uses semi-transparent amber background with border for visibility + * without the harshness of solid fills. Amber conveys "needs attention". + */ +.argparse-meta-tag { + background-color: #fef3c780; + border: 1px solid #fcd34d80; + color: var(--color-foreground-primary); + font-size: var(--code-font-size); + padding: 0.1rem 0.4rem; + border-radius: 0.2rem; + font-weight: 500; +} + +/* Dark mode: darker amber with adjusted border */ +body[data-theme="dark"] .argparse-meta-tag { + background-color: #78350f60; + border-color: #b4530980; +} + +@media (prefers-color-scheme: dark) { + body:not([data-theme="light"]) .argparse-meta-tag { + background-color: #78350f60; + border-color: #b4530980; + } +} + /* * Help text description * Adds spacing above for visual separation from argument name. diff --git a/tests/docs/_ext/sphinx_argparse_neo/test_nodes.py b/tests/docs/_ext/sphinx_argparse_neo/test_nodes.py index e30306b6a7..f00594c1dd 100644 --- a/tests/docs/_ext/sphinx_argparse_neo/test_nodes.py +++ b/tests/docs/_ext/sphinx_argparse_neo/test_nodes.py @@ -314,6 +314,8 @@ class ArgumentHTMLCase(t.NamedTuple): metavar: str | None help_text: str | None default: str | None + type_name: str | None + required: bool id_prefix: str expected_patterns: list[str] # Regex patterns to match @@ -325,6 +327,8 @@ class ArgumentHTMLCase(t.NamedTuple): metavar="socket-name", help_text="pass-through for tmux -L", default="None", + type_name=None, + required=False, id_prefix="shell", expected_patterns=[ r'